Aller au contenu

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 (selfHeal actif), 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é

  1. 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é ... ... ...
  1. Répartissez les rôles : animateur, attaquant, défense, observateur chargé du chronomètre et des preuves.

  2. 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é

  1. 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)"'
  1. 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.

  1. 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
  1. Haute disponibilité : passez à trois réplicas avec un PodDisruptionBudget, tuez un pod pendant une rafale de déploiements, et comptez les refus.

  2. 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
  1. 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é

  1. 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.

  2. 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
  1. 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}
  1. 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é

  1. Arrêtez le collecteur des journaux d'audit pendant dix minutes. Constatez qu'aucune alerte de sécurité ne peut plus se déclencher.

  2. 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}
  1. 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é

  1. L'équipe « attaquant » rejoue, sans prévenir, le script security-tests/runtime-detection.sh sur staging, puis une tentative de lecture d'un secret de production avec une identité de staging.

  2. L'équipe « défense » mesure, pour chaque action : temps de détection, temps de confinement, cause établie, preuves conservées.

  3. 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.

  1. 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é

  1. 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
  1. 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
  1. 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.

  2. 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é

  1. Complétez le rapport de résilience des contrôles : hypothèse, résultat, écart, preuve, action corrective, responsable.

  2. Rédigez le postmortem blameless de l'expérience la plus révélatrice.

  3. Automatisez les expériences rejouables (Chaos Mesh Workflow, scripts security-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: Fail ne 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 ?