Skip to content

Cet outil n'est ni affilié à The Linux Foundation, à la Cloud Native Computing Foundation (CNCF) ou au projet Kubernetes, ni approuvé ni sponsorisé par eux. Kubernetes et K8s sont des marques déposées de The Linux Foundation. EKS, GKE, AKS et les autres noms sont des marques de leurs propriétaires respectifs.

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.

Publié le 5 min de lecture

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: Metadata fourre-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

PreuveNiveau minimumPourquoi
Qui a appelé quoi, d'où, avec quel résultatMetadata sur toutIdentité, IP source, verbe, objet, code : l'ossature de toute chronologie
Specs des pods et workloads (pods, deployments, daemonsets, statefulsets, jobs, cronjobs) en create / update / patchRequestSans le corps, impossible de voir privileged, hostPID, un hostPath ou l'image
Écritures RBAC (roles, rolebindings, clusterroles, clusterrolebindings)Request ou RequestResponseLe roleRef et les subjects disent si un binding accorde cluster-admin et à qui
pods/exec, pods/attach, pods/portforward, nodes/proxyMetadata suffit, Request convientLa commande est dans requestURI ; la session elle-même n'est jamais enregistrée
serviceaccounts/tokenRequest, pas RequestResponseLa requête montre la durée et l'audience demandées ; la réponse contient le token lui-même
secrets, configmaps, tokenreviewsMetadata uniquementLes corps contiennent les identifiants ; les journaliser transforme le log d'audit en coffre à identifiants
Suppression d'eventsMetadataSupprimer 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 Metadata même en create et update : 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. Pour serviceaccounts/token, RequestResponse journaliserait un bearer token valide.
  • Les mises à jour de statut des kubelets (patchs de nodes/status, pods/status) tombent dans la règle 6, en Metadata. 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 des list est 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.log et 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.

Articles liés

Articles liés

Angles morts des logs d'audit Kubernetes : activité dans les conteneurs, kubelet et etcd, trous de politique, champs falsifiables, et preuves complémentaires.
Le format des logs d'audit Kubernetes champ par champ : stages, niveaux, user, sourceIPs, objectRef, responseStatus, annotations, utiles en forensique.
Incident Kubernetes fictif pas à pas dans le log d'audit : token de dashboard exposé, recon can-i, vol de secrets, DaemonSet privilégié, cluster-admin, XMRig.

Cet outil n'est ni affilié à The Linux Foundation, à la Cloud Native Computing Foundation (CNCF) ou au projet Kubernetes, ni approuvé ni sponsorisé par eux. Kubernetes et K8s sont des marques déposées de The Linux Foundation. EKS, GKE, AKS et les autres noms sont des marques de leurs propriétaires respectifs.