
Entraînement CKS format examen. Ces exercices reproduisent les tâches de sécurisation Kubernetes demandées à la CKS : isolation réseau, RBAC, hardening, supply chain, runtime. Chronométrez-vous et visez 80% de réussite.
Réflexes CKS
Section intitulée « Réflexes CKS »Configurez votre environnement avant de commencer :
# Alias essentielsalias k=kubectlalias kn='kubectl config set-context --current --namespace'alias kgp='kubectl get pods'export do="--dry-run=client -o yaml"export now="--force --grace-period=0"
# Autocomplétionsource <(kubectl completion bash)complete -F __start_kubectl k
# Namespace de travailkubectl create ns cks-exerciseskn cks-exercisesBloc 1 — CKS cœur d’examen (15 exercices)
Section intitulée « Bloc 1 — CKS cœur d’examen (15 exercices) »Ces exercices couvrent les tâches universelles de la CKS, réalisables dans n’importe quel cluster Kubernetes.
Exercice 1 : NetworkPolicy Deny All
Section intitulée « Exercice 1 : NetworkPolicy Deny All »Temps cible : 4 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer une NetworkPolicy deny-all qui bloque tout trafic entrant et sortant pour tous les Pods du namespace |
| Validation | kubectl describe netpol deny-all montre podSelector: {} et policyTypes: [Ingress, Egress] |
Solution
kubectl apply -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: deny-all namespace: cks-exercisesspec: podSelector: {} policyTypes: - Ingress - EgressEOF
# Validationkubectl get netpol deny-all -n cks-exerciseskubectl describe netpol deny-all -n cks-exercisesPoint clé : podSelector: {} sélectionne tous les Pods. Sans règle ingress ni egress, tout est bloqué par défaut.
Exercice 2 : NetworkPolicy Allow Specific (avec Service)
Section intitulée « Exercice 2 : NetworkPolicy Allow Specific (avec Service) »Temps cible : 6 minutes
| Contexte | Namespace cks-exercises, policy deny-all en place |
|---|---|
| Tâche | Créer un Deployment api (nginx:1.25, label app=api), un Service api-svc port 80, et une NetworkPolicy allow-frontend autorisant uniquement les Pods app=frontend à joindre le Service |
| Validation | kubectl exec frontend -- wget -qO- --timeout=2 api-svc réussit, kubectl exec other -- wget ... échoue |
Solution
# Deployment api avec label explicitekubectl create deploy api --image=nginx:1.25 -n cks-exerciseskubectl label deploy api -n cks-exercises app=apikubectl patch deploy api -n cks-exercises -p '{"spec":{"template":{"metadata":{"labels":{"app":"api"}}}}}'
# Service devant apikubectl expose deploy api --port=80 --name=api-svc -n cks-exercises
# NetworkPolicy autorisant frontend vers apikubectl apply -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-frontend namespace: cks-exercisesspec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 80EOF
# Pods de testkubectl run frontend --image=busybox:1.36 --labels="app=frontend" -n cks-exercises -- sleep 3600kubectl run other --image=busybox:1.36 -n cks-exercises -- sleep 3600
# Validation (attendre que les pods soient Running)kubectl exec frontend -n cks-exercises -- wget -qO- --timeout=2 api-svc# doit réussir (ou timeout si deny-all bloque egress de frontend aussi)
kubectl exec other -n cks-exercises -- wget -qO- --timeout=2 api-svc 2>&1# doit timeoutPoint clé : La NetworkPolicy allow-frontend crée une exception à deny-all pour le trafic ingress vers les Pods app=api.
Exercice 3 : RBAC lecture seule
Section intitulée « Exercice 3 : RBAC lecture seule »Temps cible : 5 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer un ServiceAccount readonly-sa, un Role pod-reader (get, list sur pods), et lier les deux |
| Validation | kubectl auth can-i list pods -n cks-exercises --as=system:serviceaccount:cks-exercises:readonly-sa retourne yes, delete retourne no |
Solution
# ServiceAccountkubectl create sa readonly-sa -n cks-exercises
# Rolekubectl create role pod-reader \ --verb=get,list \ --resource=pods \ -n cks-exercises
# RoleBindingkubectl create rolebinding readonly-binding \ --role=pod-reader \ --serviceaccount=cks-exercises:readonly-sa \ -n cks-exercises
# Validationkubectl auth can-i list pods -n cks-exercises \ --as=system:serviceaccount:cks-exercises:readonly-sa# yes
kubectl auth can-i delete pods -n cks-exercises \ --as=system:serviceaccount:cks-exercises:readonly-sa# no
kubectl auth can-i list secrets -n cks-exercises \ --as=system:serviceaccount:cks-exercises:readonly-sa# noExercice 4 : Désactiver automount token
Section intitulée « Exercice 4 : Désactiver automount token »Temps cible : 4 minutes
| Contexte | ServiceAccount readonly-sa créé |
|---|---|
| Tâche | Créer un Pod no-token-pod (nginx:1.25) utilisant readonly-sa avec automountServiceAccountToken: false |
| Validation | kubectl exec no-token-pod -- ls /var/run/secrets/kubernetes.io/serviceaccount retourne “No such file” |
Solution
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: no-token-pod namespace: cks-exercisesspec: serviceAccountName: readonly-sa automountServiceAccountToken: false containers: - name: nginx image: nginx:1.25EOF
# Validationkubectl exec no-token-pod -n cks-exercises -- ls /var/run/secrets/kubernetes.io/serviceaccount 2>&1# No such file or directory
# Double-check dans le YAMLkubectl get pod no-token-pod -n cks-exercises -o yaml | grep automountExercice 5 : SecurityContext restrictif complet
Section intitulée « Exercice 5 : SecurityContext restrictif complet »Temps cible : 6 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer un Pod hardened-pod (nginx:1.25) avec : runAsNonRoot, runAsUser 1000, allowPrivilegeEscalation false, readOnlyRootFilesystem true, capabilities drop ALL |
| Contrainte | nginx nécessite des emptyDir pour /tmp, /var/cache/nginx, /var/run |
| Validation | kubectl exec hardened-pod -- id retourne uid=1000, le Pod est Running |
Solution
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: hardened-pod namespace: cks-exercisesspec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 containers: - name: nginx image: nginx:1.25 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache/nginx - name: run mountPath: /var/run volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} - name: run emptyDir: {}EOF
# Validationkubectl get pod hardened-pod -n cks-exerciseskubectl exec hardened-pod -n cks-exercises -- id# uid=1000 gid=1000Point clé : Certaines images nécessitent des volumes writable pour fonctionner avec readOnlyRootFilesystem: true.
Exercice 6 : Seccomp RuntimeDefault
Section intitulée « Exercice 6 : Seccomp RuntimeDefault »Temps cible : 3 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer un Pod seccomp-pod (nginx:1.25) avec profil Seccomp RuntimeDefault |
| Validation | kubectl get pod seccomp-pod -o jsonpath='{.spec.securityContext.seccompProfile.type}' retourne RuntimeDefault |
Solution
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: seccomp-pod namespace: cks-exercisesspec: securityContext: seccompProfile: type: RuntimeDefault containers: - name: nginx image: nginx:1.25EOF
# Validationkubectl get pod seccomp-pod -n cks-exercises -o jsonpath='{.spec.securityContext.seccompProfile.type}'# RuntimeDefaultExercice 7 : Pod Security Admission — restricted
Section intitulée « Exercice 7 : Pod Security Admission — restricted »Temps cible : 5 minutes
| Contexte | Nouveau namespace psa-test |
|---|---|
| Tâche | Créer un namespace psa-test avec PSA enforce=restricted (v1.34), puis tester avec un Pod non-conforme et un Pod conforme |
| Validation | Le Pod non-conforme est rejeté, le Pod conforme démarre |
Solution
# Créer namespace avec PSAkubectl create ns psa-testkubectl label namespace psa-test \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/enforce-version=v1.34
# Vérifier les labelskubectl get ns psa-test --show-labels
# Test Pod non-conformekubectl run non-compliant --image=nginx -n psa-test 2>&1# Error: violates PodSecurity "restricted:v1.34"
# Pod conforme (tous les critères restricted)kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: compliant-pod namespace: psa-testspec: securityContext: runAsNonRoot: true runAsUser: 1000 seccompProfile: type: RuntimeDefault containers: - name: app image: busybox:1.36 command: ['sh', '-c', 'sleep 3600'] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"]EOF
# Validationkubectl get pod compliant-pod -n psa-testPoint clé : Le niveau restricted exige runAsNonRoot, seccompProfile, capabilities drop ALL, et allowPrivilegeEscalation false.
Exercice 8 : Secret en volume readonly
Section intitulée « Exercice 8 : Secret en volume readonly »Temps cible : 4 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer un Secret app-secret (API_KEY=secret123), puis un Pod secret-pod (busybox:1.36) qui monte ce Secret dans /etc/secrets en lecture seule |
| Validation | kubectl exec secret-pod -- cat /etc/secrets/API_KEY retourne secret123, écriture impossible |
Solution
# Créer le Secretkubectl create secret generic app-secret \ --from-literal=API_KEY=secret123 \ -n cks-exercises
# Pod avec montage readonlykubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: secret-pod namespace: cks-exercisesspec: containers: - name: app image: busybox:1.36 command: ['sh', '-c', 'cat /etc/secrets/API_KEY && sleep 3600'] volumeMounts: - name: secrets mountPath: /etc/secrets readOnly: true volumes: - name: secrets secret: secretName: app-secretEOF
# Validationkubectl exec secret-pod -n cks-exercises -- cat /etc/secrets/API_KEY# secret123
kubectl exec secret-pod -n cks-exercises -- touch /etc/secrets/test 2>&1# Read-only file systemExercice 9 : Identifier pods privilégiés
Section intitulée « Exercice 9 : Identifier pods privilégiés »Temps cible : 5 minutes
| Contexte | Cluster complet |
|---|---|
| Tâche | Lister tous les Pods du cluster qui ont privileged: true ou allowPrivilegeEscalation: true |
| Validation | Commande fonctionnelle retournant namespace/pod |
Solution
# Pods avec privileged: truekubectl get pods -A -o json | jq -r ' .items[] | select( .spec.containers[]?.securityContext?.privileged == true ) | "\(.metadata.namespace)/\(.metadata.name)"'
# Pods avec allowPrivilegeEscalation: true (ou non défini = true par défaut)kubectl get pods -A -o json | jq -r ' .items[] | select( .spec.containers[]?.securityContext?.allowPrivilegeEscalation == true or (.spec.containers[]?.securityContext?.allowPrivilegeEscalation == null and .spec.containers[]?.securityContext?.privileged != false) ) | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u
# Version simplifiée : pods explicitement privilegedecho "=== Pods privileged ==="kubectl get pods -A -o json | jq -r ' .items[] | select(.spec.containers[].securityContext.privileged == true) | "\(.metadata.namespace)/\(.metadata.name)"'Point clé : Les expressions jq peuvent échouer si securityContext est null. Utiliser ? pour gérer les champs optionnels.
Exercice 10 : Image pinned by digest
Section intitulée « Exercice 10 : Image pinned by digest »Temps cible : 5 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Obtenir le digest SHA256 de nginx:1.25, puis créer un Pod pinned-pod utilisant l’image avec ce digest |
| Validation | kubectl get pod pinned-pod -o jsonpath='{.spec.containers[0].image}' contient @sha256: |
Solution
# Obtenir le digest (plusieurs méthodes selon les outils disponibles)
# Option 1 : kubectl debug image (si disponible)kubectl run digest-check --image=nginx:1.25 --rm -it --restart=Never -- cat /dev/null 2>/dev/nullkubectl get pod digest-check -o jsonpath='{.status.containerStatuses[0].imageID}' 2>/dev/null
# Option 2 : crane (si installé)crane digest nginx:1.25
# Option 3 : skopeo (si installé)skopeo inspect docker://nginx:1.25 | jq -r '.Digest'
# Option 4 : docker pull + inspectdocker pull nginx:1.25docker inspect nginx:1.25 --format='{{index .RepoDigests 0}}'
# Exemple de digest (à remplacer par le vrai)# nginx@sha256:a8281ce42034b078dc8d0abf0d3ec56a9e5c41a3f8a95a5a8c8e12b7a3a0d1e5
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: pinned-pod namespace: cks-exercisesspec: containers: - name: nginx # Remplacer par le digest réel obtenu ci-dessus image: nginx@sha256:a8281ce42034b078dc8d0abf0d3ec56a9e5c41a3f8a95a5a8c8e12b7a3a0d1e5EOF
# Validationkubectl get pod pinned-pod -n cks-exercises -o jsonpath='{.spec.containers[0].image}'# Doit contenir @sha256:Point clé : Un digest garantit l’immutabilité de l’image, contrairement aux tags qui peuvent être écrasés.
Exercice 11 : Identifier images non-immutables
Section intitulée « Exercice 11 : Identifier images non-immutables »Temps cible : 5 minutes
| Contexte | Cluster complet |
|---|---|
| Tâche | Lister toutes les images du cluster utilisant le tag :latest ou sans tag (donc implicitement latest) |
| Validation | Commande fonctionnelle retournant namespace/pod: image |
Solution
# Images avec :latest ou sans tagkubectl get pods -A -o json | jq -r ' .items[] | . as $pod | .spec.containers[] | select(.image | (test(":latest$") or (test(":") | not))) | "\($pod.metadata.namespace)/\($pod.metadata.name): \(.image)"'
# Alternative avec initContainers aussikubectl get pods -A -o json | jq -r ' .items[] | . as $pod | (.spec.containers + (.spec.initContainers // []))[] | select(.image | (test(":latest$") or (test(":") | not))) | "\($pod.metadata.namespace)/\($pod.metadata.name): \(.image)"' | sort -uRecommandation : Ces images présentent un risque de dérive. Préférer des tags versionnés ou des digests.
Exercice 12 : Sécuriser un Pod existant
Section intitulée « Exercice 12 : Sécuriser un Pod existant »Temps cible : 8 minutes
| Contexte | Pod insecure-app avec problèmes de sécurité |
|---|---|
| Tâche | Identifier les problèmes et créer une version sécurisée secure-app |
| Validation | kubectl get pod secure-app Running, tous les SecurityContext restrictifs appliqués |
Créez d’abord le Pod problématique :
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: insecure-app namespace: cks-exercisesspec: containers: - name: app image: nginx:latest securityContext: privileged: trueEOFSolution
Problèmes identifiés :
image: nginx:latest— tag non-immutableprivileged: true— accès root complet au host- Pas de
runAsNonRoot - Pas de
readOnlyRootFilesystem - Capabilities non supprimées
- Pas de profil Seccomp
Version sécurisée :
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: secure-app namespace: cks-exercisesspec: securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: app image: nginx:1.25 securityContext: privileged: false allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache/nginx - name: run mountPath: /var/run volumes: - name: tmp emptyDir: {} - name: cache emptyDir: {} - name: run emptyDir: {}EOF
# Validationkubectl get pod secure-app -n cks-exerciseskubectl get pod secure-app -n cks-exercises -o jsonpath='{.spec.securityContext}'Exercice 13 : Isolation complète d’un namespace
Section intitulée « Exercice 13 : Isolation complète d’un namespace »Temps cible : 10 minutes
| Contexte | Nouveau namespace production |
|---|---|
| Tâche | Créer production avec : (1) PSA restricted enforced, (2) NetworkPolicy deny-all, (3) SA app-sa sans automount, (4) Role lecture ConfigMaps, (5) Pod conforme utilisant ce SA |
| Validation | kubectl auth can-i list configmaps -n production --as=system:serviceaccount:production:app-sa = yes, secrets = no |
Solution
# 1. Namespace avec PSAkubectl create ns productionkubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/enforce-version=v1.34
# 2. NetworkPolicy deny-allkubectl apply -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: deny-all namespace: productionspec: podSelector: {} policyTypes: - Ingress - EgressEOF
# 3. ServiceAccount sans automountkubectl create sa app-sa -n productionkubectl patch sa app-sa -n production -p '{"automountServiceAccountToken": false}'
# 4. Role ConfigMap reader + bindingkubectl create role configmap-reader \ --verb=get,list \ --resource=configmaps \ -n production
kubectl create rolebinding app-configmap-binding \ --role=configmap-reader \ --serviceaccount=production:app-sa \ -n production
# 5. Pod conforme PSA restrictedkubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: prod-app namespace: productionspec: serviceAccountName: app-sa automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1000 seccompProfile: type: RuntimeDefault containers: - name: app image: busybox:1.36 command: ['sh', '-c', 'sleep 3600'] securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"]EOF
# Validationkubectl get ns production --show-labelskubectl get netpol -n productionkubectl auth can-i list configmaps -n production --as=system:serviceaccount:production:app-sa# yeskubectl auth can-i list secrets -n production --as=system:serviceaccount:production:app-sa# nokubectl get pod prod-app -n productionExercice 14 : Ingress TLS (avec backend)
Section intitulée « Exercice 14 : Ingress TLS (avec backend) »Temps cible : 6 minutes
| Contexte | Namespace cks-exercises |
|---|---|
| Tâche | Créer un Deployment web (nginx:1.25), un Service web-svc, un Secret TLS auto-signé pour secure.example.com, et un Ingress utilisant ce Secret |
| Validation | kubectl describe ingress secure-ingress montre le TLS configuré |
Solution
# Backendkubectl create deploy web --image=nginx:1.25 -n cks-exerciseskubectl expose deploy web --port=80 --name=web-svc -n cks-exercises
# Certificat auto-signéopenssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /tmp/tls.key -out /tmp/tls.crt \ -subj "/CN=secure.example.com"
# Secret TLSkubectl create secret tls secure-tls \ --cert=/tmp/tls.crt \ --key=/tmp/tls.key \ -n cks-exercises
# Ingresskubectl apply -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: Ingressmetadata: name: secure-ingress namespace: cks-exercisesspec: ingressClassName: nginx tls: - hosts: - secure.example.com secretName: secure-tls rules: - host: secure.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-svc port: number: 80EOF
# Validationkubectl describe ingress secure-ingress -n cks-exercises | grep -A3 TLS
# Nettoyage fichiers temporairesrm /tmp/tls.key /tmp/tls.crtExercice 15 : Identifier ClusterRoleBindings cluster-admin
Section intitulée « Exercice 15 : Identifier ClusterRoleBindings cluster-admin »Temps cible : 5 minutes
| Contexte | Cluster complet |
|---|---|
| Tâche | Lister tous les ServiceAccounts liés à un ClusterRoleBinding vers cluster-admin |
| Validation | Commande retournant namespace/sa |
Solution
# Méthode 1 : jq completkubectl get clusterrolebindings -o json | jq -r ' .items[] | select(.roleRef.name == "cluster-admin") | .subjects[]? | select(.kind == "ServiceAccount") | "\(.namespace)/\(.name)"'
# Méthode 2 : grep simple pour identifier rapidementkubectl get clusterrolebindings -o wide | grep cluster-admin
# Méthode 3 : détail par bindingkubectl get clusterrolebindings -o json | jq -r ' .items[] | select(.roleRef.name == "cluster-admin") | "Binding: \(.metadata.name)\nSubjects: \(.subjects // [] | map("\(.kind)/\(.namespace // "cluster")/\(.name)") | join(", "))\n"'Point clé : Les ServiceAccounts avec cluster-admin ont des droits très élevés. Auditez régulièrement.
Bloc 2 — Outils et audit (3 exercices)
Section intitulée « Bloc 2 — Outils et audit (3 exercices) »Ces exercices nécessitent des outils supplémentaires. Ils sont moins universels mais pertinents pour la préparation.
Exercice 16 : Scanner une image avec Trivy
Section intitulée « Exercice 16 : Scanner une image avec Trivy »Temps cible : 4 minutes
| Contexte | Trivy installé |
|---|---|
| Tâche | Scanner nginx:1.24 et lister les vulnérabilités CRITICAL et HIGH |
| Validation | Sortie Trivy avec tableau des vulnérabilités |
Solution
# Scan avec filtre severitytrivy image --severity HIGH,CRITICAL nginx:1.24
# Scan en mode quiet (tableau uniquement)trivy image --severity HIGH,CRITICAL --format table nginx:1.24
# Compter les vulnérabilités (attention : résultats variables selon la date)trivy image --severity CRITICAL --format json nginx:1.24 | jq '[.Results[]?.Vulnerabilities // [] | length] | add'Note : Le nombre de vulnérabilités varie selon la base CVE du jour. L’objectif est de savoir utiliser Trivy, pas d’obtenir un compte exact.
Exercice 17 : Analyser les logs d’audit Kubernetes
Section intitulée « Exercice 17 : Analyser les logs d’audit Kubernetes »Temps cible : 6 minutes
| Contexte | Accès aux logs d’audit du control plane |
|---|---|
| Tâche | Filtrer les créations de Secrets des 10 dernières minutes avec l’utilisateur associé |
| Contrainte | Exercice dépendant d’un cluster avec audit logs activés (path typique : /var/log/kubernetes/audit/audit.log) |
| Validation | Liste timestamp/user/namespace/secret |
Solution
# Sur le control plane (ssh si nécessaire)AUDIT_LOG="/var/log/kubernetes/audit/audit.log"
# Vérifier que le fichier existels -la $AUDIT_LOG
# Filtrer créations de secretscat $AUDIT_LOG | jq -r ' select( .verb == "create" and .objectRef.resource == "secrets" ) | { timestamp: .requestReceivedTimestamp, user: .user.username, namespace: .objectRef.namespace, name: .objectRef.name }'
# Avec filtre temporel (dernières 10 minutes)SINCE=$(date -d "10 minutes ago" -Iseconds 2>/dev/null || date -v-10M -Iseconds)cat $AUDIT_LOG | jq --arg since "$SINCE" -r ' select( .verb == "create" and .objectRef.resource == "secrets" and .requestReceivedTimestamp >= $since ) | "\(.requestReceivedTimestamp) \(.user.username) \(.objectRef.namespace)/\(.objectRef.name)"'Point clé : L’audit logging doit être configuré sur l’API server. Les paths varient selon l’installation.
Exercice 18 : Audit de sécurité simplifié
Section intitulée « Exercice 18 : Audit de sécurité simplifié »Temps cible : 8 minutes
| Contexte | Namespace cks-exercises avec plusieurs Pods |
|---|---|
| Tâche | Produire une liste des Pods sans SecurityContext restrictif (privileged, no readOnlyRootFilesystem, allowPrivilegeEscalation true) |
| Validation | Liste namespace/pod avec le problème identifié |
Solution
NS="cks-exercises"
echo "=== Pods sans SecurityContext restrictif dans $NS ==="
# Pods privilegedecho "-- Privileged:"kubectl get pods -n $NS -o json | jq -r ' .items[] | select(.spec.containers[]?.securityContext?.privileged == true) | .metadata.name'
# Pods sans readOnlyRootFilesystemecho "-- Sans readOnlyRootFilesystem:"kubectl get pods -n $NS -o json | jq -r ' .items[] | select(.spec.containers[]?.securityContext?.readOnlyRootFilesystem != true) | .metadata.name' | sort -u
# Pods avec allowPrivilegeEscalation (explicite ou implicite)echo "-- Avec allowPrivilegeEscalation possible:"kubectl get pods -n $NS -o json | jq -r ' .items[] | select(.spec.containers[]?.securityContext?.allowPrivilegeEscalation != false) | .metadata.name' | sort -u
# NetworkPolicies en placeecho ""echo "=== NetworkPolicies actives ==="kubectl get netpol -n $NSBloc 3 — Runtime avancé (2 exercices bonus)
Section intitulée « Bloc 3 — Runtime avancé (2 exercices bonus) »Ces exercices sont plus spécialisés et dépendent d’outils runtime (Falco, kube-bench). Utiles pour approfondir, moins prioritaires pour l’examen.
Exercice 19 : Détection shell avec audit logs (sans Falco)
Section intitulée « Exercice 19 : Détection shell avec audit logs (sans Falco) »Temps cible : 5 minutes
| Contexte | Audit logs activés |
|---|---|
| Tâche | Utiliser les audit logs pour détecter les kubectl exec vers des Pods |
| Validation | Trouver les exec récents dans les logs |
Solution
# Filtrer les exec dans les audit logscat /var/log/kubernetes/audit/audit.log | jq -r ' select( .verb == "create" and .objectRef.subresource == "exec" ) | "\(.requestReceivedTimestamp) \(.user.username) exec into \(.objectRef.namespace)/\(.objectRef.name)"'
# Alternative : si responseStatus est logguécat /var/log/kubernetes/audit/audit.log | jq -r ' select(.objectRef.subresource == "exec") | "\(.requestReceivedTimestamp) \(.verb) \(.user.username) → \(.objectRef.namespace)/\(.objectRef.name)"'Point clé : La détection d’exec peut aussi se faire avec Falco, mais les audit logs sont toujours disponibles si configurés.
Exercice 20 : Vérification kube-bench (analyse)
Section intitulée « Exercice 20 : Vérification kube-bench (analyse) »Temps cible : 6 minutes
| Contexte | kube-bench installé, accès control plane |
|---|---|
| Tâche | Exécuter kube-bench sur le control plane et identifier les checks FAIL de la section 1.1 (API Server) |
| Validation | Liste des checks FAIL avec leurs IDs |
Solution
# Exécuter kube-bench (sur control plane)kube-bench run --targets master
# Filtrer la section 1.1kube-bench run --targets master --check "1.1" 2>/dev/null | grep -E "\[FAIL\]|\[WARN\]"
# Exemple de sortie :# [FAIL] 1.1.1 Ensure that the API server pod specification file permissions are set to 600 or more restrictive# [FAIL] 1.1.2 Ensure that the API server pod specification file ownership is set to root:root
# Vérification manuelle d'un checkls -la /etc/kubernetes/manifests/kube-apiserver.yamlNote : Les remédiations kube-bench doivent être appliquées avec discernement. Certaines peuvent casser le cluster. Cet exercice est d’analyse, pas de correction automatique.
Récapitulatif des temps
Section intitulée « Récapitulatif des temps »| # | Exercice | Bloc | Temps | Indépendant |
|---|---|---|---|---|
| 1 | NetworkPolicy Deny All | CKS cœur | 4 min | ✅ |
| 2 | NetworkPolicy Allow Specific | CKS cœur | 6 min | ⚠️ Ex.1 |
| 3 | RBAC lecture seule | CKS cœur | 5 min | ✅ |
| 4 | Désactiver automount token | CKS cœur | 4 min | ⚠️ Ex.3 |
| 5 | SecurityContext restrictif | CKS cœur | 6 min | ✅ |
| 6 | Seccomp RuntimeDefault | CKS cœur | 3 min | ✅ |
| 7 | Pod Security Admission | CKS cœur | 5 min | ✅ |
| 8 | Secret volume readonly | CKS cœur | 4 min | ✅ |
| 9 | Identifier pods privilégiés | CKS cœur | 5 min | ✅ |
| 10 | Image pinned by digest | CKS cœur | 5 min | ✅ |
| 11 | Images non-immutables | CKS cœur | 5 min | ✅ |
| 12 | Sécuriser Pod existant | CKS cœur | 8 min | ✅ |
| 13 | Isolation namespace | CKS cœur | 10 min | ✅ |
| 14 | Ingress TLS | CKS cœur | 6 min | ✅ |
| 15 | ClusterRoleBindings cluster-admin | CKS cœur | 5 min | ✅ |
| 16 | Trivy scan | Outils | 4 min | ✅ |
| 17 | Audit logs | Outils | 6 min | ⚠️ Env |
| 18 | Audit sécurité simplifié | Outils | 8 min | ✅ |
| 19 | Détection exec (audit) | Avancé | 5 min | ⚠️ Env |
| 20 | kube-bench analyse | Avancé | 6 min | ⚠️ Env |
Temps total Bloc 1 (CKS cœur) : ~81 minutes Temps total complet : ~110 minutes
L’examen CKS dure 2 heures (120 min). Concentrez-vous sur le Bloc 1 pour un entraînement réaliste. Les Blocs 2 et 3 permettent d’approfondir après avoir maîtrisé le cœur.
Nettoyage
Section intitulée « Nettoyage »kubectl delete ns cks-exerciseskubectl delete ns psa-test 2>/dev/nullkubectl delete ns production 2>/dev/nullÀ retenir
Section intitulée « À retenir »- NetworkPolicy deny-all : première étape de tout namespace de production
- RBAC minimal : Role (namespace) vs ClusterRole (cluster), toujours
kubectl auth can-ipour vérifier - SecurityContext : runAsNonRoot, allowPrivilegeEscalation: false, readOnlyRootFilesystem, capabilities drop ALL
- Seccomp RuntimeDefault : profil standard sécurisé pour la plupart des workloads
- Pod Security Admission : labels namespace pour enforcer les standards restricted/baseline
- automountServiceAccountToken: false : désactiver sauf si nécessaire
- Images immutables : préférer digest, éviter :latest
- Audit :
kubectl auth can-i --list,kubectl get pods -o json | jq