
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.25@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059cEOF
# 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@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c 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.25@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059cEOF
# 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:1.25 -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@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 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@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 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}}'
# Digest de nginx:1.25 relevé au 2026-09-14, utilisable tel quel.# Le vôtre différera si le tag a été republié depuis : c'est tout l'intérêt.# nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c
kubectl apply -f - <<'EOF'apiVersion: v1kind: Podmetadata: name: pinned-pod namespace: cks-exercisesspec: containers: - name: nginx # Remplacez par le digest que vous venez d'obtenir image: nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059cEOF
# 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:1.31.3@sha256:5a88c9c45479443d7be2eadc894b4ed0a9801bae03d97a5760ae13b5c2005942 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@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c 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@sha256:73aaf090f3d85aa34ee199857f03fa3a95c8ede2ffd4cc2cdb5b94e566b11662 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.
Capstone : ouvrir une enclave à une équipe qui n'est pas de confiance
Section intitulée « Capstone : ouvrir une enclave à une équipe qui n'est pas de confiance »Les exercices ci-dessus durcissent un mécanisme à la fois. Le capstone pose une situation entière : une équipe extérieure a besoin de place dans votre cluster, et il faut lui donner un espace qui reste sûr même si elle ne l'est pas. Le cluster doit refuser de lui-même ce qu'elle ne devrait pas déployer, son identité ne doit presque rien pouvoir, et rien hors de l'enclave ne doit atteindre ce qui y tourne.
Comptez 45 minutes, et traitez-le en dernier : il mobilise l'admission, le RBAC et l'isolation réseau ensemble, alors que chacun a son exercice séparé plus haut.
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/nullContrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À 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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Sécuriser : durcissement, supply chain, détection : Continuer sur la sécurité au-delà du périmètre Kubernetes.
- Examens et quiz : D'autres examens blancs pour entretenir le rythme de l'entraînement.