GameDay sécurité : Security Chaos Engineering sur Kubernetes¶
Objectifs pédagogiques¶
À la fin de ce TP, vous devez être capables de :
- formuler des hypothèses vérifiables sur le comportement de vos contrôles de sécurité
- mesurer le comportement réel du contrôleur d'admission, du coffre et des certificats en cas de panne
- détecter une détection devenue aveugle
- éprouver la détection et la réponse face à un attaquant simulé et à une dérive de configuration
- chronométrer une restauration complète après destruction de données
- restituer un GameDay sous forme de rapport et d'actions correctives
Prérequis¶
- La plateforme de lab : cluster k3d
securepay, ArgoCD (selfHealactif), kube-prometheus-stack, Loki - Kyverno avec au moins une politique en
Deny, OpenBao et External Secrets Operator, cert-manager, Falco et Falco Talon, Velero et un stockage objet - Les cours Chaos Engineering avec Chaos Mesh et Sécurité runtime avec Falco réalisés
Staging d'abord
Chaque expérience est jouée en staging avant la production, avec une condition d'arrêt définie à l'avance et une personne chargée de l'interrompre. Aucune expérience destructrice n'est lancée sans sauvegarde vérifiée.
Testé
Les comportements de Kyverno (1.19.2), de la ValidatingAdmissionPolicy native et de cert-manager (v1.21.3) décrits dans ce TP ont été mesurés sur des clusters de test.
Contexte¶
La plateforme SecurePay accumule les contrôles : admission, coffre, certificats, détection runtime, sauvegardes. Personne ne sait comment elle se comporte quand l'un d'eux tombe. Le comité sécurité demande un GameDay et un rapport de résilience des contrôles.
Atelier 1 : Préparer le GameDay¶
Travail demandé¶
- Rédigez le catalogue d'expériences dans
docs/security-chaos.md:
| Expérience | Hypothèse | Condition d'arrêt | Mesures |
|---|---|---|---|
| Kyverno injoignable | ... | ... | ... |
| Arrêt propre de Kyverno | ... | ... | ... |
| OpenBao production indisponible | ... | ... | ... |
| Certificat perdu | ... | ... | ... |
| Collecte des journaux arrêtée | ... | ... | ... |
| Attaquant simulé | ... | ... | ... |
| Dérive de configuration | ... | ... | ... |
| Rançongiciel simulé | ... | ... | ... |
-
Répartissez les rôles : animateur, attaquant, défense, observateur chargé du chronomètre et des preuves.
-
Pour chaque contrôle, consignez dans un ADR le choix fail-open ou fail-closed attendu, avant toute expérience.
Critère de réussite¶
Chaque expérience a une hypothèse vérifiable et une condition d'arrêt, avant d'être lancée.
Atelier 2 : Le contrôleur d'admission¶
Travail demandé¶
- Vérifiez le webhook généré pour votre politique et sa
failurePolicy:
kubectl get validatingwebhookconfigurations -o json \
| jq -r '.items[].webhooks[] | select(.name | contains("finegrained")) | "\(.name) \(.failurePolicy)"'
- Partition : rendez Kyverno injoignable sans arrêter ses pods, en vidant temporairement le sélecteur de son Service :
SEL=$(kubectl -n kyverno get svc kyverno-svc -o jsonpath='{.spec.selector}')
kubectl -n kyverno patch svc kyverno-svc --type=merge -p '{"spec":{"selector":{"partition":"simulee"}}}'
# ... expériences ...
kubectl -n kyverno patch svc kyverno-svc --type=json \
-p "[{\"op\":\"replace\",\"path\":\"/spec/selector\",\"value\":$SEL}]"
Pendant la partition, déployez un pod conforme puis un pod non conforme, d'abord avec failurePolicy: Fail, puis avec Ignore.
- Arrêt propre : passez le contrôleur d'admission à zéro réplica, comptez les webhooks de Kyverno avant et après, puis déployez un pod non conforme :
kubectl get validatingwebhookconfigurations -o name | grep -c kyverno
kubectl -n kyverno scale deploy kyverno-admission-controller --replicas=0
kubectl get validatingwebhookconfigurations -o name | grep -c kyverno
-
Haute disponibilité : passez à trois réplicas avec un PodDisruptionBudget, tuez un pod pendant une rafale de déploiements, et comptez les refus.
-
Couche native : ajoutez une
ValidatingAdmissionPolicy(et son binding) pour la règle la plus critique, puis rejouez l'arrêt propre :
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: disallow-latest-tag-native
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "object.spec.containers.all(c, !c.image.endsWith(':latest'))"
message: "Tag latest interdit"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: disallow-latest-tag-native
spec:
policyName: disallow-latest-tag-native
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: securepay-prod
- Ajoutez une alerte sur l'indisponibilité du contrôleur d'admission, et une alerte si ses webhooks disparaissent alors qu'il devrait tourner.
Ce que vous devez observer¶
| Scénario | Résultat constaté en test |
|---|---|
Partition, Fail | même un pod conforme est refusé (failed calling webhook) |
Partition, Ignore | un pod non conforme est admis sans contrôle |
| Arrêt propre (0 réplica) | Kyverno supprime ses webhooks : tout est admis, quel que soit failurePolicy |
| Trois réplicas, un pod tué | aucun refus, le contrôle est maintenu |
Règle native ValidatingAdmissionPolicy | évaluée par l'API Server seul : l'image latest est refusée même sur un cluster sans Kyverno |
Critère de réussite¶
Le comportement de chaque politique en cas de panne est connu, documenté dans l'ADR et couvert par une alerte ; les règles critiques ont une couche native.
Atelier 3 : Le coffre et les certificats¶
Travail demandé¶
-
Rejouez la panne d'OpenBao de production sous charge nominale (voir l'atelier 7 du TP secrets) et tentez une rotation pendant la panne. Chronométrez le descellement au redémarrage.
-
Supprimez le Secret TLS d'un certificat géré par cert-manager, puis vérifiez sa réémission :
kubectl -n securepay-prod get secret api-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -serial
kubectl -n securepay-prod delete secret api-tls
kubectl -n securepay-prod get secret api-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -serial
- Ajoutez les alertes d'expiration et de certificat non prêt :
- alert: CertificateExpiringSoon
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 7 * 24 * 3600
for: 1h
labels: {severity: warning, component: cert-manager, cause: CertificateExpiring}
- alert: CertificateNotReady
expr: certmanager_certificate_ready_status{condition="False"} == 1
for: 10m
labels: {severity: critical, component: cert-manager, cause: CertificateNotReady}
- Réfléchissez à l'expiration de l'autorité de certification interne elle-même : quand arrive-t-elle, et que se passe-t-il ce jour-là ?
Ce que vous devez observer¶
- L'API continue sans OpenBao, mais aucune rotation ne passe, et l'instance revient scellée
- Le certificat est réémis en quelques secondes, avec un nouveau numéro de série
Critère de réussite¶
Les deux comportements sont mesurés et alertés, et le descellement fait partie du runbook.
Atelier 4 : La détection aveugle¶
Travail demandé¶
-
Arrêtez le collecteur des journaux d'audit pendant dix minutes. Constatez qu'aucune alerte de sécurité ne peut plus se déclencher.
-
Ajoutez une règle d'alerte Loki sur l'absence de données :
groups:
- name: detection-aveugle
rules:
- alert: KubeAuditLogsMissing
expr: absent_over_time({job="kube-audit"}[10m])
labels: {severity: critical, component: log-pipeline, cause: DetectionBlind}
- Faites de même pour Falco et Falcosidekick : une alerte sur leur disponibilité, et le test de détection du cours Falco rejoué à intervalle régulier.
Critère de réussite¶
Une détection qui ne reçoit plus de données déclenche elle-même une alerte.
Atelier 5 : Attaquant simulé et dérive de configuration¶
Travail demandé¶
-
L'équipe « attaquant » rejoue, sans prévenir, le script
security-tests/runtime-detection.shsur staging, puis une tentative de lecture d'un secret de production avec une identité de staging. -
L'équipe « défense » mesure, pour chaque action : temps de détection, temps de confinement, cause établie, preuves conservées.
-
Injectez ensuite des dérives, une à une :
kubectl -n securepay-prod delete networkpolicy default-deny
kubectl label namespace securepay-prod pod-security.kubernetes.io/enforce-
kubectl create clusterrolebinding derive-test --clusterrole=view --user=attaquant
kubectl -n securepay-prod run privilegie --image=busybox:1.37 --privileged -- sleep 300
Pour chacune, relevez le contrôle qui réagit (ArgoCD, admission, alerte d'audit) et la fenêtre d'exposition entre la dérive et sa correction.
- Supprimez le ClusterRoleBinding de test s'il n'est pas géré par GitOps : c'est une donnée en soi.
Critère de réussite¶
Chaque dérive est bloquée ou corrigée, et sa fenêtre d'exposition est mesurée.
Atelier 6 : Rançongiciel simulé et reprise¶
Travail demandé¶
- Vérifiez qu'une sauvegarde récente et complète existe :
velero backup create avant-gameday \
--include-namespaces securepay-prod --default-volumes-to-fs-backup --wait
velero backup describe avant-gameday
- Sous charge k6, supprimez le namespace et chronométrez la restauration complète :
kubectl delete namespace securepay-prod
time velero restore create --from-backup avant-gameday --wait
-
Mesurez le RTO réel jusqu'au retour au SLO, en incluant la resynchronisation des secrets et, si OpenBao était touché, son descellement. Déduisez le RPO de la fréquence de vos sauvegardes planifiées.
-
Vérifiez que l'identité de Velero ne peut pas supprimer les sauvegardes existantes dans le stockage objet.
Critère de réussite¶
Le RTO et le RPO constatés sont comparés aux objectifs, et chaque écart donne lieu à une action corrective.
Atelier 7 : Restituer¶
Travail demandé¶
-
Complétez le rapport de résilience des contrôles : hypothèse, résultat, écart, preuve, action corrective, responsable.
-
Rédigez le postmortem blameless de l'expérience la plus révélatrice.
-
Automatisez les expériences rejouables (Chaos Mesh
Workflow, scriptssecurity-tests/) en staging.
Critère de réussite¶
Chaque écart à l'hypothèse a une action corrective, et chaque correction sera vérifiée par la même expérience.
Questions de synthèse¶
- Pourquoi
failurePolicy: Failne protège-t-il pas lors d'un arrêt propre de Kyverno ? - Pour quelles règles accepteriez-vous un comportement fail-open, et à quelles conditions ?
- Comment détecter qu'une détection est devenue aveugle ?
- Pourquoi le RTO réel est-il presque toujours supérieur au temps de restauration affiché par l'outil ?
- Qu'apporte le Security Chaos Engineering par rapport à un pentest annuel ?