📍 Article principal du cluster : CKS Certified Kubernetes Security Specialist — guide pratique 2026
Ce tutoriel fait partie du cluster certification CKS. Pour la vue d’ensemble, lisez d’abord le pilier.
Introduction
Le domaine 4 « Minimize Microservice Vulnerabilities » pèse 20 % de l’examen CKS. Il teste votre capacité à appliquer des politiques d’admission strictes qui bloquent à la création tout Pod ne respectant pas les standards de sécurité — capabilities excessives, runAsRoot, hostNetwork, mounts sensibles. Deux outils sont incontournables : Pod Security Standards (intégré nativement à Kubernetes depuis 1.25) et OPA Gatekeeper (policies déclaratives en Rego, plus flexibles). Ce tutoriel applique les deux sur un cluster kind 1.34, montre les différences, et configure une stratégie défense-en-profondeur.
Prérequis
- Cluster kind 1.34 fonctionnel
- Tutoriel kube-bench/CIS terminé
- 40 minutes
Étape 1 — Activer Pod Security Standards Restricted sur un namespace
Pod Security Standards (PSS) est le successeur de PodSecurityPolicy. Il s’active via labels au niveau namespace, sans installation additionnelle. Trois niveaux : Privileged (aucune restriction), Baseline (anti-escalation), Restricted (durcissement maximal).
kubectl create namespace secure-zone
kubectl label namespace secure-zone \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
# Tester avec un Pod non-conforme
kubectl run nginx --image=nginx:1.27 -n secure-zone
# Doit échouer : Restricted exige runAsNonRoot, capabilities drop, etc.
Le Pod nginx classique est rejeté car l’image tourne en root par défaut. C’est le comportement attendu — vous devez fournir un manifest qui satisfait les contraintes Restricted pour qu’il soit accepté.
Étape 2 — Créer un Pod conforme Restricted
Un Pod Restricted-conforme demande : runAsNonRoot, runAsUser non-zéro, allowPrivilegeEscalation false, capabilities drop ALL, seccompProfile RuntimeDefault.
cat > /tmp/pod-restricted.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: secure-zone
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginxinc/nginx-unprivileged:1.27
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
EOF
kubectl apply -f /tmp/pod-restricted.yaml
kubectl get pod secure-app -n secure-zone
Notez l’utilisation de `nginxinc/nginx-unprivileged` — l’image officielle nginx tourne en root sur le port 80, alors que la version unprivileged tourne en uid 101 sur le port 8080. Pour les workloads CKS, privilégiez les images conçues pour s’exécuter sans privilèges.
Étape 3 — Installer OPA Gatekeeper
OPA (Open Policy Agent) Gatekeeper étend Kubernetes avec un admission webhook qui évalue chaque création/modification de ressource contre des policies Rego. Plus flexible que PSS qui est limité aux contraintes prédéfinies.
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.18.0/deploy/gatekeeper.yaml
sleep 30
kubectl get pods -n gatekeeper-system
kubectl wait --for=condition=ready pod -l control-plane=controller-manager -n gatekeeper-system --timeout=120s
Une fois Gatekeeper démarré, le webhook intercepte toutes les requêtes API. Sans ConstraintTemplate ni Constraint, il laisse tout passer. C’est la prochaine étape qui ajoute les règles.
Étape 4 — Première policy — interdire les images depuis Docker Hub
Cas d’usage classique : forcer toutes les images à venir d’un registry interne (Harbor, ECR, GCR). On commence par un ConstraintTemplate qui définit la logique en Rego, puis on l’instancie via une Constraint.
cat > /tmp/template-allowed-repos.yaml <<'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
satisfied := [good | repo = input.parameters.repos[_]; good = startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("container image %v not allowed from repos %v", [container.image, input.parameters.repos])
}
EOF
kubectl apply -f /tmp/template-allowed-repos.yaml
cat > /tmp/constraint-allowed-repos.yaml <<'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: only-corporate-registry
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system","gatekeeper-system"]
parameters:
repos:
- "registry.example.com/"
- "ghcr.io/myorg/"
EOF
kubectl apply -f /tmp/constraint-allowed-repos.yaml
sleep 5
# Tester : un Pod nginx public doit être refusé
kubectl run test-block --image=nginx:1.27 -n default
# Doit échouer avec violation Gatekeeper
Le Pod est rejeté avec un message explicite « container image nginx:1.27 not allowed from repos ». C’est exactement le comportement attendu en production : aucune image non-vérifiée ne peut atteindre le cluster.
Étape 5 — Policy interdire hostPath
Les volumes hostPath sont une porte ouverte : un Pod compromis peut lire/écrire sur le filesystem du nœud. Une bonne policy CKS interdit hostPath sauf cas exceptionnels documentés.
cat > /tmp/template-no-hostpath.yaml <<'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snohostpath
spec:
crd:
spec:
names:
kind: K8sNoHostPath
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8snohostpath
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
volume.hostPath
msg := sprintf("hostPath volumes are forbidden, found %v", [volume.name])
}
EOF
kubectl apply -f /tmp/template-no-hostpath.yaml
cat > /tmp/constraint-no-hostpath.yaml <<'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoHostPath
metadata:
name: forbid-hostpath
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces: ["kube-system","gatekeeper-system"]
EOF
kubectl apply -f /tmp/constraint-no-hostpath.yaml
Test : tentez d’appliquer un Pod avec un hostPath volume — il est refusé immédiatement par Gatekeeper. Cette policy seule bloque environ 30 % des escapes container documentés.
Étape 6 — Audit mode pour découverte avant enforcement
Avant de basculer en `enforce`, beaucoup d’équipes commencent en `audit` : Gatekeeper logue les violations sans les bloquer, ce qui permet de quantifier l’impact d’une nouvelle policy.
# Voir les violations détectées (audit mode)
kubectl get k8snohostpath forbid-hostpath -o yaml | grep -A 20 status
Le bloc `status.violations` liste toutes les ressources qui violent la policy. En enforcement mode, ces violations bloquent les nouvelles créations mais ne suppriment pas les Pods existants — vous devez les corriger un par un.
Étape 7 — Combiner PSS et OPA Gatekeeper
PSS et Gatekeeper sont complémentaires. PSS gère les contraintes standardisées de Kubernetes (security context, capabilities, host namespaces). Gatekeeper gère les contraintes business (registry whitelist, labels obligatoires, ResourceQuotas custom). Combinez les deux : PSS au niveau namespace, Gatekeeper pour les règles globales.
# Vérifier la cohérence des deux systèmes sur un Pod test
cat > /tmp/test-combined.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: combined-test
namespace: secure-zone
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: registry.example.com/nginx:1.27
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
EOF
kubectl apply -f /tmp/test-combined.yaml
# Refusé par Gatekeeper (registry pas dans la liste autorisée)
Si on tente avec une image registry approuvée (ghcr.io/myorg/nginx) ET un security context PSS-conforme, le Pod passe. C’est la posture défense-en-profondeur qu’on cherche.
Étape 8 — Sandbox runtime gVisor (introduction)
gVisor est un runtime conteneur user-space qui isole davantage que runc en interceptant les syscalls. Recommandé pour les workloads non-confiés. La configuration complète demande modification du containerd config et création d’une RuntimeClass.
cat > /tmp/runtimeclass-gvisor.yaml <<'EOF'
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
EOF
# Note : nécessite gVisor installé sur les nœuds avec containerd configuré
# kubectl apply -f /tmp/runtimeclass-gvisor.yaml
# Pod utilisant gVisor :
cat > /tmp/pod-gvisor.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: untrusted-workload
spec:
runtimeClassName: gvisor
containers:
- name: app
image: nginx:1.27
EOF
Pour kind, gVisor n’est pas pré-installé. En production, c’est une option pour héberger du code utilisateur ou des workloads non-vérifiés. Tombe en domaine 4 du syllabus comme « sandbox runtime ».
Comprendre la différence Validating vs Mutating Admission Webhook
Kubernetes a deux types de webhooks d’admission. Validating dit oui ou non à une création/modification — c’est ce qu’utilise Gatekeeper. Mutating peut modifier la ressource avant qu’elle soit persistée — c’est ce que fait Istio pour injecter des sidecars Envoy. Les deux peuvent coexister : Mutating s’exécute en premier (transforme la ressource), puis Validating (valide la version finale).
OPA Gatekeeper supporte les deux modes depuis la v3.10. Pour CKS v1.34, attendez-vous à des questions principalement sur Validating, occasionnellement sur Mutating pour la transformation de manifests.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| PSS Restricted refuse mes Pods existants | Images qui tournent en root | Migrer vers `nginx-unprivileged`, `bitnami/postgresql` etc |
| Gatekeeper laisse passer mes Pods | ConstraintTemplate appliqué sans Constraint | Les deux objets sont nécessaires : Template puis Constraint qui l’instancie |
| Rego policy ne match jamais | Mauvais chemin dans input.review.object | Tester via `gator test` localement avant déploiement |
| PSS et Gatekeeper en conflit | Règles redondantes mais non synchronisées | Documenter le partage de responsabilité : PSS pour standards K8s, Gatekeeper pour business |
| RuntimeClass gvisor non trouvée | gVisor pas installé sur les nœuds | Suivre la doc gvisor.dev/docs/user_guide/install pour containerd |
Adaptation au contexte ouest-africain
Pour les équipes qui montent un cluster k8s en production en Afrique de l’Ouest, OPA Gatekeeper est un investissement très rentable : 1-2 jours de configuration initiale, puis des années de protection automatique. Les premiers Templates à déployer : registry whitelist (force les images depuis votre Harbor interne), labels obligatoires (équipe, environnement, criticité), ResourceQuota par namespace, et anti hostPath. Ces 4 templates couvrent 80 % des incidents de sécurité observés en pratique. Hostinger Cloud Startup a la capacité RAM nécessaire pour faire tourner Gatekeeper (200-400 Mo) et un cluster k3s léger.
Tutoriels frères
- Durcir un cluster Kubernetes avec kube-bench et CIS
- Falco runtime security — détecter les attaques en temps réel (à venir)
Pour aller plus loin
- 🔝 Retour au pilier : CKS — guide pratique 2026
- OPA Gatekeeper : open-policy-agent.github.io/gatekeeper
- Pod Security Standards : kubernetes.io/docs/concepts/security/pod-security-standards
- Bibliothèque de templates : open-policy-agent.github.io/gatekeeper-library
FAQ
Faut-il OPA Gatekeeper ou Kyverno ?
Les deux sont valides. Gatekeeper utilise Rego (puissant mais courbe d’apprentissage), Kyverno utilise YAML déclaratif (plus simple). Pour CKS, Gatekeeper est dans le syllabus officiel.
Peut-on faire tout avec PSS sans Gatekeeper ?
Non. PSS gère uniquement les contraintes prédéfinies de Kubernetes. Pour des règles business (registry, labels, quotas custom), un admission webhook est requis.
Mots-clés secondaires : opa gatekeeper cks, pod security standards, restricted namespace, rego policy kubernetes, validating admission webhook, gvisor runtimeclass