Politique d'audit Kubernetes : que journaliser en forensique
Écrire une politique d'audit Kubernetes qui capture les preuves utiles (exec, RBAC, workloads, tokens) sans journaliser les secrets ni crouler sous le bruit.
TL;DR. Une politique d'audit est une liste ordonnée de règles ; la première qui correspond fixe le niveau d'audit de chaque requête. Pour la forensique, il vous faut Metadata sur tout, Request ou RequestResponse sur les écritures d'objets RBAC, de pods et de contrôleurs de workloads, Request sur pods/exec, pods/attach, pods/portforward, nodes/proxy et serviceaccounts/token, et strictement Metadata sur les secrets, configmaps et token reviews. Écartez les health checks et le bavardage des kubelets et de kube-proxy. Envoyez le résultat hors du cluster et conservez-le au moins 90 jours.
Un API server autogéré n'écrit rien tant que vous ne lui donnez pas de politique : la documentation sur l'audit Kubernetes est explicite, sans --audit-policy-file aucun événement n'est journalisé. Sur EKS, GKE et AKS, le fournisseur fixe la politique pour vous, et votre seul choix est d'exporter ou non les logs (voir le guide d'export EKS, GKE et AKS). Dans les deux cas, savoir à quoi ressemble une bonne politique vous dit quelles preuves vous pouvez espérer.
Comment les règles sont évaluées
Chaque règle peut filtrer sur users, userGroups, verbs, resources (avec le groupe d'API et éventuellement resourceNames), namespaces et nonResourceURLs. Les règles sont évaluées dans l'ordre et la première qui correspond l'emporte. Une requête qui ne correspond à aucune règle n'est pas journalisée. Deux conséquences pratiques :
- Placez vos exclusions (
level: None) et vos règles « jamais de corps » avant les règles larges. - Terminez par un
level: Metadatafourre-tout pour que rien ne passe en silence.
omitStages supprime des stages globalement ou par règle. Presque tout le monde omet RequestReceived, qui double l'événement final. omitManagedFields: true retire le volumineux metadata.managedFields des corps journalisés.
Ce dont les enquêteurs ont besoin, et pourquoi
| Preuve | Niveau minimum | Pourquoi |
|---|---|---|
| Qui a appelé quoi, d'où, avec quel résultat | Metadata sur tout | Identité, IP source, verbe, objet, code : l'ossature de toute chronologie |
Specs des pods et workloads (pods, deployments, daemonsets, statefulsets, jobs, cronjobs) en create / update / patch | Request | Sans le corps, impossible de voir privileged, hostPID, un hostPath ou l'image |
Écritures RBAC (roles, rolebindings, clusterroles, clusterrolebindings) | Request ou RequestResponse | Le roleRef et les subjects disent si un binding accorde cluster-admin et à qui |
pods/exec, pods/attach, pods/portforward, nodes/proxy | Metadata suffit, Request convient | La commande est dans requestURI ; la session elle-même n'est jamais enregistrée |
serviceaccounts/token | Request, pas RequestResponse | La requête montre la durée et l'audience demandées ; la réponse contient le token lui-même |
secrets, configmaps, tokenreviews | Metadata uniquement | Les corps contiennent les identifiants ; les journaliser transforme le log d'audit en coffre à identifiants |
Suppression d'events | Metadata | Supprimer les Events est une mesure anti-forensique bon marché |
Une politique orientée forensique
Cette politique est un point de départ, pas un copier-coller. Testez-la sur un cluster hors production et surveillez le volume de logs.
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# 1. Noise: health checks and high-frequency system reads
- level: None
nonResourceURLs: ["/healthz*", "/livez*", "/readyz*", "/version"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services", "services/status"]
- level: None
userGroups: ["system:nodes"]
verbs: ["get"]
resources:
- group: ""
resources: ["nodes", "nodes/status"]
# 2. Credentials: never log bodies
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- group: "authentication.k8s.io"
resources: ["tokenreviews"]
# 3. Interactive access and token minting (request only: the
# TokenRequest response contains the token)
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward",
"nodes/proxy", "serviceaccounts/token"]
# 4. Events: keep deletions, drop the rest
- level: Metadata
verbs: ["delete", "deletecollection"]
resources:
- group: ""
resources: ["events"]
- group: "events.k8s.io"
resources: ["events"]
- level: None
resources:
- group: ""
resources: ["events"]
- group: "events.k8s.io"
resources: ["events"]
# 5. Writes to workloads, RBAC, service accounts, admission: full bodies
- level: RequestResponse
verbs: ["create", "update", "patch", "delete", "deletecollection"]
resources:
- group: ""
resources: ["pods", "serviceaccounts", "namespaces", "nodes"]
- group: "apps"
- group: "batch"
- group: "rbac.authorization.k8s.io"
- group: "admissionregistration.k8s.io"
# 6. Everything else
- level: Metadata
Quelques choix à expliquer :
- La règle 2 avant la règle 5. Les secrets doivent rester en
Metadatamême encreateetupdate: la règle des secrets doit donc correspondre en premier. - La règle 3 utilise
Request. Les sous-ressources de streaming n'ont pas de corps significatif ; ce qu'il vous faut (la commande, le conteneur) est dans l'URI. Pourserviceaccounts/token,RequestResponsejournaliserait un bearer token valide. - Les mises à jour de statut des kubelets (patchs de
nodes/status,pods/status) tombent dans la règle 6, enMetadata. C'est largement suffisant pour enquêter et bien moins coûteux que les corps. - Les lectures restent en
Metadata. Journaliser les corps de réponse deslistest ce qui fait exploser la taille des logs d'audit ; cela aide rarement une enquête.
À titre de comparaison, la politique qu'AWS publie dans le guide des bonnes pratiques EKS suit la même structure : Metadata pour les secrets, configmaps et token reviews, Request pour serviceaccounts/token, corps complets pour la plupart des groupes d'API connus. Une différence notable : elle ignore totalement les events, donc les suppressions d'Events ne sont pas visibles sur EKS.
L'activer sur un API server autogéré
Sur un plan de contrôle de type kubeadm, l'API server tourne en pod statique. Ajoutez les flags dans /etc/kubernetes/manifests/kube-apiserver.yaml et montez la politique et le répertoire de logs :
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
maxage est en jours, maxsize en mégaoctets. Le kubelet redémarre l'API server quand le manifeste change. Des distributions comme k3s et RKE2 exposent les mêmes flags via leur propre configuration ; consultez leur documentation pour les clés exactes.
La documentation note aussi que l'audit augmente la consommation mémoire de l'API server, proportionnellement à ce que vous journalisez. Raison de plus pour réserver les corps aux écritures qui comptent.
Sortir les logs de la machine
Un attaquant avec cluster-admin et un pod privilégié peut lire et modifier les fichiers des nœuds du plan de contrôle. Des logs qui n'existent que là sont des logs que l'attaquant contrôle. Deux options :
- Le backend webhook (
--audit-webhook-config-file) envoie des lots vers un endpoint HTTP : un collecteur de logs, un SIEM, une file de messages. - Le backend log plus un agent d'expédition (Fluent Bit, Vector, l'agent cloud) lit
audit.loget l'envoie vers un stockage central.
Le guide de durcissement Kubernetes de la NSA et de la CISA recommande lui aussi d'activer l'audit et de stocker les logs hors du cluster. Gardez au moins 90 jours : l'accès initial précède souvent la détection de plusieurs semaines.
Vérifier votre politique face à de vraies détections
Un bon test : jouez une séquence malveillante connue sur un cluster de lab (exec dans un pod, création d'un pod privilégié avec un hostPath sur /, binding d'un compte de service à cluster-admin), exportez le log et déposez-le dans l'analyseur de logs d'audit. Si les constats de pod privilégié ou de cluster-admin n'apparaissent pas, les corps correspondants ne sont pas journalisés. La section de référence de l'analyseur liste les détections ; le guide pas à pas explique comment lire le résultat.