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.

Format des logs d'audit Kubernetes : anatomie d'un event

Le format des logs d'audit Kubernetes champ par champ : stages, niveaux, user, sourceIPs, objectRef, responseStatus, annotations, utiles en forensique.

Publié le 6 min de lecture

TL;DR. Un événement d'audit Kubernetes est un objet JSON de kind Event (audit.k8s.io/v1) qui décrit une requête à un stage donné. Les champs utiles dans toute enquête sont user, impersonatedUser, sourceIPs, userAgent, verb, objectRef, requestURI, responseStatus.code, les annotations authorization.k8s.io/decision et reason et, quand le niveau d'audit le permet, requestObject. Dédupliquez les stages sur auditID, ne faites confiance qu'à la dernière IP source, et souvenez-vous qu'au niveau Metadata il n'y a pas de corps.

La plupart des gens découvrent le format des logs d'audit en plein incident, dans une fenêtre CloudWatch ou Log Analytics, face à des milliers de lignes presque identiques. Savoir quels champs portent la preuve et lesquels sont du bruit fait gagner des heures.

Un événement complet

Voici un événement abrégé pour un kubectl exec dans un pod, tel que l'API server l'écrit avec le backend log (un objet JSON par ligne). Les noms et adresses sont fictifs.

{
  "kind": "Event",
  "apiVersion": "audit.k8s.io/v1",
  "level": "Metadata",
  "auditID": "4f1e0a52-8c1b-4d0e-9d3e-2b7a5c9e1f00",
  "stage": "ResponseStarted",
  "requestURI": "/api/v1/namespaces/shop/pods/api-6c9f7d8b5-q2w8x/exec?command=sh&container=api&stdin=true&stdout=true&tty=true",
  "verb": "create",
  "user": {
    "username": "alice@example.com",
    "groups": ["platform-admins", "system:authenticated"]
  },
  "sourceIPs": ["198.51.100.23"],
  "userAgent": "kubectl/v1.30.2 (linux/amd64) kubernetes/3968350",
  "objectRef": {
    "resource": "pods",
    "namespace": "shop",
    "name": "api-6c9f7d8b5-q2w8x",
    "apiVersion": "v1",
    "subresource": "exec"
  },
  "responseStatus": { "metadata": {}, "code": 101 },
  "requestReceivedTimestamp": "2026-09-14T09:12:03.412000Z",
  "stageTimestamp": "2026-09-14T09:12:03.448000Z",
  "annotations": {
    "authorization.k8s.io/decision": "allow",
    "authorization.k8s.io/reason": "RBAC: allowed by ClusterRoleBinding \"platform-admins\" of ClusterRole \"cluster-admin\" to Group \"platform-admins\""
  }
}

Le schéma complet est dans la référence de configuration d'audit de kube-apiserver. Voici ce que vaut chaque champ pour un enquêteur.

Stages : une requête, plusieurs événements

Une requête peut produire jusqu'à quatre événements, un par stage :

StageQuand il est émisValeur forensique
RequestReceivedDès que le handler reçoit la requêteDoublon de l'événement final, sans réponse
ResponseStartedEn-têtes envoyés, corps pas encore (requêtes longues : watch, exec, attach, port-forward)Le seul événement pour une session exec encore ouverte
ResponseCompleteRéponse terminéeLa ligne « une requête » normale
PanicLe handler a paniquéRare ; à regarder si vous en voyez

Tous les événements d'une même requête partagent le même auditID. Beaucoup de politiques (dont celles, managées, d'EKS et de GKE) omettent RequestReceived. Pour compter, regroupez les stages sur auditID, sinon chaque exec sera compté deux fois. C'est ce que fait l'analyseur de la page d'accueil : il garde un événement par requête et, pour exec, attach et port-forward, garde l'événement ResponseStarted.

Niveaux : quelle part de la requête vous obtenez

La politique d'audit attribue un des quatre niveaux à chaque requête :

  • None : rien n'est journalisé.
  • Metadata : utilisateur, source, verbe, objet, code de réponse. Pas de corps.
  • Request : métadonnées plus le corps de la requête (requestObject).
  • RequestResponse : plus le corps de la réponse (responseObject).

Le niveau figure dans le champ level de l'événement : vous pouvez donc savoir, à partir des données elles-mêmes, si un corps absent signifie « non envoyé » ou « non journalisé ». C'est important : un create sur daemonsets au niveau Metadata vous dit qu'un DaemonSet a été créé, pas qu'il était privilégié. L'article sur la politique d'audit montre comment obtenir les corps là où ils comptent.

Qui : user, groups, impersonation

user.username est l'identité authentifiée. Sa forme en dit long :

  • system:serviceaccount:<namespace>:<name> : un token de compte de service.
  • system:node:<node-name> : un kubelet.
  • system:anonymous : une requête non authentifiée qui a été acceptée (accès anonyme).
  • Un e-mail, un mapping d'ARN IAM ou un objet Entra ID : un humain ou une identité cloud, selon la plateforme.

user.groups explique beaucoup de décisions d'autorisation. user.extra peut porter des identifiants utiles : pour les tokens de compte de service, les versions récentes ajoutent un identifiant de credential (le JTI du token), qui permet de distinguer deux tokens d'un même compte de service, comme le décrit la documentation des comptes de service.

Quand l'appelant utilise des en-têtes d'impersonation, impersonatedUser contient l'identité sous laquelle la requête a été autorisée. Lisez toujours les deux champs ensemble.

D'où : sourceIPs et userAgent

sourceIPs est construit à partir de X-Forwarded-For, puis de X-Real-Ip, puis de l'adresse distante de la connexion. La référence est explicite : toutes les IP sauf la dernière peuvent être fixées par le client. Utilisez la dernière, et sachez ce qui se trouve devant votre API server (un load balancer cloud, un saut Konnectivity ou VPN) avant de conclure.

userAgent est déclaré par le client et trivial à falsifier, mais les attaquants s'en donnent rarement la peine. L'agent habituel d'un contrôleur ressemble à kube-controller-manager/v1.30.4 … ; un workload qui utilise la bibliothèque cliente affiche le nom de son binaire ; une personne affiche kubectl/…. Un compte de service qui parle soudain kubectl/ ou curl/ est un signal fort de token rejoué à la main.

Quoi : verb, objectRef, requestURI

verb est le verbe Kubernetes (get, list, watch, create, update, patch, delete, deletecollection), pas la méthode HTTP. objectRef contient resource, namespace, name, apiGroup, apiVersion et subresource.

Trois détails piègent souvent :

  1. Un list ou un watch sans objectRef.namespace couvre tout le cluster. Sur secrets, cela renvoie tous les secrets du cluster.
  2. Sur un create, objectRef.name peut manquer, car le nom est dans le corps. Au niveau Metadata, vous ne verrez parfois que la collection.
  3. Les sous-ressources portent l'action. pods/exec, pods/attach, pods/portforward, pods/log, serviceaccounts/token, nodes/proxy sont « la partie dangereuse » d'une ressource par ailleurs banale.

requestURI conserve la query string. Pour un exec, cela inclut chaque argument command=, même au niveau Metadata. Cela signifie aussi que des secrets passés en ligne de commande d'un exec finissent dans le log d'audit, un point soulevé dans l'issue kubernetes/kubernetes #97795.

Résultat : responseStatus et annotations

responseStatus.code est le code HTTP : 200/201 succès, 101 changement de protocole pour le streaming (exec, attach, port-forward), 401 non authentifié, 403 interdit, 404 introuvable, 409 conflit. Une rafale de 403 pour une même identité, c'est quelqu'un qui cartographie ses permissions.

Les annotations authorization.k8s.io/decision (allow / forbid) et authorization.k8s.io/reason nomment le binding RBAC qui a accordé l'accès. Le guide des bonnes pratiques EKS recommande de s'en servir pour comprendre pourquoi un appel a été autorisé. En incident, la raison pointe directement le binding à supprimer. Pod Security Admission écrit aussi des annotations ici lorsqu'un pod viole une politique en mode audit.

Horodatages

requestReceivedTimestamp est l'arrivée de la requête ; stageTimestamp le moment où ce stage a été atteint. Les deux sont en UTC, à la microseconde. Pour une session exec, l'écart entre ResponseStarted et ResponseComplete donne la durée de la session quand les deux sont journalisés.

Les enveloppes cloud changent l'emballage, pas l'événement

Sur EKS, l'événement est le message d'un enregistrement CloudWatch Logs. Sur AKS, c'est une chaîne JSON dans properties.log (compte de stockage, Event Hubs) ou dans la colonne log_s (AzureDiagnostics), ou éclatée en colonnes dans la table AKSAudit. Sur GKE, il est réécrit en entrée Cloud Audit Logs : protoPayload.methodName comme io.k8s.core.v1.pods.exec.create, protoPayload.resourceName, authenticationInfo.principalEmail, requestMetadata.callerIp. Le guide d'export couvre chacun. L'analyseur les ramène tous aux mêmes champs : le reste de cette série s'applique quelle que soit la source.

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.
Écrire une politique d'audit Kubernetes qui capture les preuves utiles (exec, RBAC, workloads, tokens) sans journaliser les secrets ni crouler sous le bruit.
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.