Aller au contenu
Conteneurs & Orchestration medium

Audit Logs Kubernetes - Tracer toutes les actions du cluster

23 min de lecture

Les logs d'audit Kubernetes enregistrent chaque requête à l'API Server pour la traçabilité et l'investigation de sécurité. Ils répondent aux questions "qui a fait quoi, quand, et via quelle méthode". Ce guide vous montre comment configurer une politique d'audit, activer les logs sur l'API Server, et analyser les événements pour détecter les comportements suspects ou reconstituer un incident.

  • Accès à la configuration de l'API Server (fichiers manifests ou flags)
  • Pour les clusters managés (EKS/GKE/AKS) : accès à la console cloud
  • Compréhension des ressources Kubernetes (pods, secrets, configmaps)

L'audit se compose de trois éléments :

Flux d'audit Kubernetes : requête API vers API Server avec Audit Policy vers backend d'audit

  1. Audit Policy : définit quels événements capturer et à quel niveau de détail
  2. API Server : applique la politique et génère les événements
  3. Backend : stocke les logs (fichier local ou webhook vers système externe)

Chaque règle de la politique spécifie un niveau qui détermine la quantité d'information enregistrée :

NiveauCe qui est enregistréUsage
NoneRienExclure des ressources du logging (éviter le bruit)
MetadataMetadata de la requête (user, timestamp, resource, verb) mais pas le bodyAudit de base, faible volume
RequestMetadata + body de la requêteVoir ce qui a été envoyé
RequestResponseMetadata + requête + réponseDebug complet, volume élevé

Un événement peut être enregistré à différentes étapes de son traitement :

StageMomentUsage
RequestReceivedDès réception, avant traitementLogging exhaustif
ResponseStartedHeaders de réponse envoyés (watch/long-running)Requêtes longues
ResponseCompleteRéponse terminéeStandard, le plus utilisé
PanicSi l'API Server panicDebugging uniquement

Une audit policy est un fichier YAML composé d'une liste de règles évaluées de haut en bas : la première règle qui correspond à la requête l'emporte, les suivantes sont ignorées. L'ordre est donc structurant, une règle générale placée en tête neutralise tout ce qui suit. Sans politique, l'API Server ne journalise rien du tout.

Cette politique de référence traite les cas dans l'ordre du plus spécifique au plus général : elle écarte d'abord le bruit des sondes de santé, protège ensuite les ressources sensibles en Metadata, puis se termine par une règle attrape-tout.

/etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
# Règle par défaut si aucune autre ne match
omitStages:
- "RequestReceived" # Évite les doublons
rules:
# Les règles sont évaluées dans l'ordre, première qui match gagne
# 1. Ne pas logger les health checks (bruit)
- level: None
users: ["system:kube-probe", "system:serviceaccount:kube-system:kube-proxy"]
resources:
- group: ""
resources: ["endpoints", "services"]
# 2. Secrets/ConfigMaps/TokenRequests : Metadata uniquement (jamais le contenu)
# IMPORTANT : RequestResponse exposerait les valeurs sensibles dans les logs !
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps", "serviceaccounts/token"]
- group: authentication.k8s.io
resources: ["tokenreviews"]
omitStages:
- "RequestReceived"
# 4. Logger les modifications RBAC
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# 5. Logger les actions sur pods au niveau Metadata
- level: Metadata
resources:
- group: ""
resources: ["pods", "pods/exec", "pods/portforward"]
# 6. Règle par défaut : Metadata pour tout le reste
- level: Metadata

Pour l'examen CKS, voici une politique simple mais fonctionnelle :

/etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Ignorer les endpoints health
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
# Secrets, ConfigMaps, tokens : Metadata uniquement (pas le contenu)
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps", "serviceaccounts/token"]
- group: authentication.k8s.io
resources: ["tokenreviews"]
# RBAC : RequestResponse pour traçabilité complète
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
# Metadata pour tout le reste
- level: Metadata

Les critères d'une règle se cumulent avec un ET logique : la requête doit satisfaire tous les champs présents pour que la règle s'applique. À l'intérieur d'un même champ, en revanche, les valeurs listées fonctionnent en OU.

rules:
- level: Metadata
# Filtrer par utilisateur
users: ["admin", "system:serviceaccount:default:myapp"]
userGroups: ["system:authenticated"]
# Filtrer par verbe
verbs: ["create", "update", "delete"]
# Filtrer par ressource
resources:
- group: "" # core API group
resources: ["pods", "services"]
- group: "apps"
resources: ["deployments"]
# Filtrer par namespace
namespaces: ["production", "finance"]
# URLs non-ressources (healthz, metrics)
nonResourceURLs: ["/api*", "/version"]
# Stages à omettre pour cette règle
omitStages: ["RequestReceived"]

Depuis Kubernetes 1.36, l'API d'audit accepte la valeur "*" dans le champ group pour cibler tous les groupes d'API d'un coup, sans avoir à les énumérer. Pratique pour une politique de fond qui doit couvrir des CRDs ajoutés au fil du temps.

rules:
- level: Metadata
# Toutes les ressources de tous les groupes
resources:
- group: "*"
resources: ["*"]
# Combinable avec un filtre de verbe pour limiter le volume
verbs: ["create", "update", "delete", "patch"]
omitStages: ["RequestReceived"]

Écrire la politique ne suffit pas : il faut la faire lire par l'API Server et lui indiquer où déposer les événements. Sur un cluster installé avec kubeadm, cela passe par le manifest statique du composant, que le kubelet surveille en permanence. Toute erreur de syntaxe dans ce fichier empêche l'API Server de redémarrer, et donc tout le cluster de répondre : travaillez avec une copie de sauvegarde du manifest.

Ces cinq étapes doivent être menées sur chaque nœud du plan de contrôle, car chaque API Server possède son propre manifest et son propre fichier de journal.

  1. Créer le fichier de politique

    Fenêtre de terminal
    sudo mkdir -p /etc/kubernetes/audit
    sudo vim /etc/kubernetes/audit/audit-policy.yaml

    Coller la politique YAML.

  2. Modifier le manifest de l'API Server

    Fenêtre de terminal
    sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml

    Ajouter les flags :

    spec:
    containers:
    - command:
    - kube-apiserver
    # ... autres flags existants ...
    - --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
    - --audit-log-path=/var/log/kubernetes/audit/audit.log
    - --audit-log-maxage=30 # Rétention en jours
    - --audit-log-maxbackup=10 # Nombre de fichiers à conserver
    - --audit-log-maxsize=100 # Taille max en Mo avant rotation
  3. Ajouter les volumes et volumeMounts

    spec:
    containers:
    - volumeMounts:
    # ... mounts existants ...
    - mountPath: /etc/kubernetes/audit
    name: audit-policy
    readOnly: true
    - mountPath: /var/log/kubernetes/audit
    name: audit-logs
    volumes:
    # ... volumes existants ...
    - name: audit-policy
    hostPath:
    path: /etc/kubernetes/audit
    type: DirectoryOrCreate
    - name: audit-logs
    hostPath:
    path: /var/log/kubernetes/audit
    type: DirectoryOrCreate
  4. Créer le répertoire de logs

    Fenêtre de terminal
    sudo mkdir -p /var/log/kubernetes/audit
  5. Vérifier le redémarrage de l'API Server

    Fenêtre de terminal
    # L'API Server redémarre automatiquement (kubelet surveille /etc/kubernetes/manifests)
    kubectl get pods -n kube-system | grep api
    # Vérifier les logs
    sudo tail -f /var/log/kubernetes/audit/audit.log | jq .

Voici le résultat des trois premières étapes réunies dans un seul fichier, pour vérifier que rien ne manque : les cinq drapeaux --audit-*, les deux volumeMounts et les deux hostPath correspondants. Un montage oublié se traduit par un API Server en CrashLoopBackOff.

/etc/kubernetes/manifests/kube-apiserver.yaml (extrait)
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --advertise-address=10.0.0.10
- --allow-privileged=true
- --authorization-mode=Node,RBAC
# ... autres flags ...
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=7
- --audit-log-maxbackup=5
- --audit-log-maxsize=100
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-logs
volumes:
- hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
name: audit-policy
- hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
name: audit-logs

Pour envoyer les événements vers un système externe (SIEM, Elasticsearch) :

/etc/kubernetes/audit/audit-webhook-config.yaml
apiVersion: v1
kind: Config
clusters:
- name: audit-webhook
cluster:
server: https://siem.example.com/k8s-audit
certificate-authority: /etc/kubernetes/pki/ca.crt
contexts:
- name: audit-webhook
context:
cluster: audit-webhook
current-context: audit-webhook

Flags API Server pour webhook :

Fenêtre de terminal
--audit-webhook-config-file=/etc/kubernetes/audit/audit-webhook-config.yaml
--audit-webhook-initial-backoff=5s
--audit-webhook-batch-max-size=1000

Le fichier d'audit contient un objet JSON par ligne, ce qui le rend directement exploitable avec jq. Chaque ligne est un événement complet et autonome : elle porte l'identité de l'appelant, l'action demandée, la ressource visée et le code de retour. Savoir lire ces quatre champs suffit à mener la majorité des investigations.

Les champs à repérer en priorité sont user.username (qui), verb (quoi), objectRef (sur quelle ressource) et responseStatus.code (avec quel résultat). Le champ sourceIPs complète le tableau en donnant l'origine réseau de la requête.

{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "RequestResponse",
"auditID": "abc123",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/default/secrets",
"verb": "create",
"user": {
"username": "admin",
"groups": ["system:masters", "system:authenticated"]
},
"sourceIPs": ["10.0.0.50"],
"userAgent": "kubectl/v1.30.0",
"objectRef": {
"resource": "secrets",
"namespace": "default",
"name": "my-secret",
"apiVersion": "v1"
},
"responseStatus": {
"code": 201
},
"requestObject": {
"kind": "Secret",
"data": { "password": "***" }
},
"stageTimestamp": "2025-06-19T10:30:00Z"
}

Ces filtres jq répondent chacun à une question précise posée en incident. Les accès refusés (code 403) méritent une attention particulière : une rafale de 403 sur un même compte de service trahit souvent une tentative d'élévation de privilèges.

Fenêtre de terminal
# Toutes les créations de secrets
cat audit.log | jq 'select(.verb=="create" and .objectRef.resource=="secrets")'
# Actions d'un utilisateur spécifique
cat audit.log | jq 'select(.user.username=="suspicious-user")'
# Accès refusés (403)
cat audit.log | jq 'select(.responseStatus.code==403)'
# Toutes les actions sur un namespace
cat audit.log | jq 'select(.objectRef.namespace=="production")'
# Exec dans des pods
cat audit.log | jq 'select(.objectRef.subresource=="exec")'
# Top 10 des utilisateurs les plus actifs
cat audit.log | jq -r '.user.username' | sort | uniq -c | sort -rn | head -10
# Actions des 5 dernières minutes
cat audit.log | jq --arg d "$(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%SZ)" \
'select(.stageTimestamp > $d)'

Une chronologie exploitable s'obtient en croisant trois filtres : le groupe d'API concerné, une fenêtre temporelle et l'exclusion des verbes de lecture (get, list, watch), qui noieraient les modifications sous le trafic normal.

Fenêtre de terminal
# Exemple : qui a modifié le RBAC hier ?
cat audit.log | jq --arg start "2025-06-18T00:00:00Z" --arg end "2025-06-19T00:00:00Z" \
'select(
.objectRef.apiGroup=="rbac.authorization.k8s.io" and
.verb!="get" and .verb!="list" and .verb!="watch" and
.stageTimestamp > $start and .stageTimestamp < $end
) | {time: .stageTimestamp, user: .user.username, verb, resource: .objectRef.resource, name: .objectRef.name}'

Falco peut analyser les audit logs Kubernetes pour des détections avancées. Voir le guide Falco pour la configuration du plugin K8s Audit.

Sur les clusters managés, les audit logs sont gérés par le fournisseur cloud. Vous ne pouvez généralement pas personnaliser la politique d'audit, mais les logs sont automatiquement centralisés et consultables via les outils natifs.

AWS EKS envoie les audit logs vers CloudWatch Logs. Par défaut, seuls les logs API Server et Authenticator sont activés.

Fenêtre de terminal
# Activer les audit logs (control plane logging)
eksctl utils update-cluster-logging \
--cluster my-cluster \
--enable-types audit,api,authenticator \
--region eu-west-1

Ou via AWS CLI :

Fenêtre de terminal
aws eks update-cluster-config \
--name my-cluster \
--logging '{"clusterLogging":[{"types":["audit","api","authenticator"],"enabled":true}]}'

Consulter les logs :

Fenêtre de terminal
# Les logs sont dans le groupe /aws/eks/<cluster>/cluster
aws logs filter-log-events \
--log-group-name /aws/eks/my-cluster/cluster \
--log-stream-name-prefix kube-apiserver-audit \
--filter-pattern '{ $.verb = "create" && $.objectRef.resource = "secrets" }'

Deux symptômes dominent après l'activation de l'audit : l'API Server refuse de redémarrer, ou il démarre sans produire le moindre fichier. Le premier cas se diagnostique dans les journaux du kubelet, puisque l'API Server n'est plus joignable par kubectl ; le second vient presque toujours d'un drapeau absent ou d'un répertoire non monté.

Quand l'API Server est à terre, kubectl ne répond plus : la seule source d'information est le kubelet local, qui journalise l'échec de démarrage du pod statique.

Fenêtre de terminal
# Vérifier les erreurs
sudo journalctl -u kubelet | grep -i apiserver | tail -50
# Erreurs YAML courantes
sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | yq .

Causes fréquentes :

SymptômeCauseSolution
"audit policy file not found"Chemin incorrect ou fichier absentVérifier le path et les permissions
"invalid audit policy"YAML invalideValider le YAML avec yq . policy.yaml ou un linter
CrashLoopBackOff API ServerVolume mount incorrectVérifier hostPath et mountPath

Un fichier vide n'est pas forcément une panne : la politique peut simplement ne rien capturer. Vérifiez d'abord que les drapeaux sont bien passés au processus, puis provoquez une action volontairement journalisée pour confirmer.

Fenêtre de terminal
# Vérifier que l'audit est actif
ps aux | grep kube-apiserver | grep audit
# Les flags doivent apparaître
# --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
# --audit-log-path=/var/log/kubernetes/audit/audit.log
# Vérifier les permissions du répertoire
ls -la /var/log/kubernetes/audit/
# Générer une action pour créer des logs
kubectl create secret generic test-audit --from-literal=key=value
kubectl delete secret test-audit

L'audit se règle sur un compromis entre volume et valeur de preuve. Une politique trop large sature le disque du plan de contrôle et rend les recherches inutilisables ; une politique trop étroite laisse des angles morts au moment où vous en avez besoin. Les quatre réflexes ci-dessous tiennent ce compromis.

Exclure le bruit

Utiliser level: None pour les health checks, metrics, et reads fréquents.

RequestResponse avec parcimonie

Réserver ce niveau aux secrets, RBAC, et pods/exec.

Rotation des logs

Configurer maxage, maxbackup, maxsize pour éviter de remplir le disque.

Centraliser les logs

Utiliser un webhook vers SIEM/Elasticsearch pour la rétention longue.

ConceptDescription
Audit PolicyFichier YAML définissant quoi logger et à quel niveau
NiveauxNone < Metadata < Request < RequestResponse
StagesRequestReceived, ResponseStarted, ResponseComplete, Panic
Backend fichier--audit-log-path pour logs locaux
Backend webhookPour envoyer vers systèmes externes (SIEM)
Rotation--audit-log-maxage/maxbackup/maxsize

Falco Runtime Security

Détecter les comportements suspects en temps réel. Configurer Falco

Dix questions issues de la banque CKS sur le domaine journalisation et audit, à faire en huit minutes avec 70 % de bonnes réponses pour valider.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
8 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn