Détecter kubectl exec, attach et port-forward (logs d'audit)
Comment kubectl exec, attach, cp, port-forward et nodes/proxy apparaissent dans les logs d'audit Kubernetes, et comment distinguer admin légitime et abus.
TL;DR. kubectl exec et kubectl attach sont des requêtes sur les sous-ressources pods/exec et pods/attach ; kubectl port-forward touche pods/portforward. Filtrez sur objectRef.subresource, acceptez create et get comme verbes, attendez-vous au code de réponse 101, et lisez la commande dans les paramètres command= de requestURI. Un exec par un compte de service est rare et doit être traité en sévérité élevée. L'accès à nodes/proxy atteint directement le kubelet, c'est pire. Rien de ce qui est tapé dans la session n'est journalisé.
Exécuter une commande dans un conteneur correspond à la technique ATT&CK T1609, Container Administration Command. C'est aussi ce que fait le plus souvent un administrateur légitime pendant une panne. Le but n'est pas de signaler chaque exec, mais de rendre chaque exec explicable.
À quoi ressemble un exec dans le log d'audit
kubectl exec -it api-6c9f7d8b5-q2w8x -n shop -- sh produit un événement comme celui-ci (abrégé, noms fictifs) :
{
"verb": "create",
"stage": "ResponseStarted",
"requestURI": "/api/v1/namespaces/shop/pods/api-6c9f7d8b5-q2w8x/exec?command=sh&container=api&stdin=true&stdout=true&tty=true",
"user": { "username": "alice@example.com" },
"objectRef": { "resource": "pods", "namespace": "shop", "name": "api-6c9f7d8b5-q2w8x", "subresource": "exec" },
"responseStatus": { "code": 101 }
}
À savoir :
- Verbe. L'endpoint exec est traditionnellement autorisé et audité comme
create. Depuis Kubernetes 1.31, kubectl passe par WebSockets par défaut, et un upgrade WebSocket commence par unGETHTTP : selon les versions, vous pouvez voirget. Cherchez les deux. - Stage. L'exec est une requête longue. L'événement
ResponseStartedest écrit à l'ouverture de la session ;ResponseCompleteseulement à sa fermeture. Si la politique n'omet rien, vous avez les deux. - Code de réponse.
101 Switching Protocolssignifie que la session a été établie. Un403signifie que quelqu'un a essayé et que RBAC a refusé, ce qui est intéressant en soi. - Commande. Chaque argument est un paramètre
command=distinct, encodé en URL :command=chroot&command=%2Fhost&command=shdonnechroot /host sh. Comme elle est dans l'URI, elle est journalisée même au niveauMetadata.
Voir l'anatomie d'un événement d'audit pour les autres champs.
Attach, cp, debug et port-forward
| Action côté client | Trace d'audit | Ce qu'il faut regarder |
|---|---|---|
kubectl attach | pods/attach | Pas de commande : s'attache au processus principal du conteneur |
kubectl cp | pods/exec avec command=tar | Le sens (tar cf = copie hors du pod, tar -x… = copie vers le pod) et les chemins |
kubectl debug (conteneur éphémère) | patch sur pods/ephemeralcontainers, puis pods/attach | L'image, et si elle partage l'espace de processus de la cible |
kubectl port-forward | pods/portforward avec ports= | Quel port : 5432, 6379, 3306, ce sont des bases de données |
kubectl proxy / proxy d'API vers un nœud | nodes/proxy | Accès direct à l'API du kubelet |
kubectl cp mérite un examen à part : copier un fichier vers un pod permet de déposer des outils sans tirer de nouvelle image, et copier depuis un pod, c'est de l'exfiltration via l'API server.
kubectl port-forward ouvre un tunnel depuis la machine du client vers le réseau des pods. Il contourne les ingress controllers et, pour le trafic du client, les network policies qui ne filtrent que le trafic entre pods. Un port-forward vers un pod de base de données par une identité qui ne le fait jamais est une piste sérieuse.
nodes/proxy : un exec sans événement exec
La sous-ressource nodes/proxy permet de parler à l'API du kubelet via l'API server. Le kubelet peut exécuter des commandes dans n'importe quel conteneur du nœud et lire ses logs. L'API server audite la requête nodes/proxy, mais pas comme un pods/exec : les détections basées sur l'exec la ratent. Les bonnes pratiques RBAC de Kubernetes classent l'accès à la sous-ressource proxy des nœuds parmi les chemins d'élévation de privilèges.
Pire : quiconque peut joindre le port du kubelet d'un nœud avec des identifiants valides lui parle directement ; ces appels n'atteignent jamais l'API server et n'apparaissent jamais dans le log d'audit. Les réglages d'authentification et d'autorisation du kubelet sont le contrôle à cet endroit.
Distinguer travail d'admin et abus
L'analyseur de la page d'accueil découpe l'exec en quatre règles :
| Règle | Sévérité | Logique |
|---|---|---|
| Commande interactive dans un conteneur (exec / attach) | Moyenne | pods/exec ou pods/attach autorisé, par une identité humaine non système |
| Un compte de service a exécuté une commande dans un conteneur | Élevée | Idem, par une identité system:serviceaccount: |
| Port-forward vers un pod | Moyenne | pods/portforward autorisé, identité non système |
| API kubelet atteinte via nodes/proxy | Élevée | nodes/proxy autorisé, identité non système |
Pourquoi ce découpage ? Les workloads ne font presque jamais d'exec dans d'autres pods. Quand un compte de service le fait, c'est en général un token sorti d'un pod et rejoué par une personne, souvent avec un user agent kubectl/ depuis une IP inattendue, ce que deux autres règles détectent (voir vol de tokens de compte de service). Des systèmes de CI et certains opérateurs font des exec légitimes ; mettez-les explicitement en liste blanche après vérification.
Pour les identités humaines, triez chaque session avec quatre questions :
- Qui et d'où ? Admin connu, plage d'IP connue, user agent habituel ?
- Quand ? Pendant une fenêtre de changement ou un incident, ou à une heure bizarre ?
- Où ? Quel namespace et quel pod ; s'agit-il d'un pod privilégié ou d'un composant de
kube-system? - Quoi ? La commande :
sh/bashinteractif, ou quelque chose commecat /var/run/secrets/kubernetes.io/serviceaccount/token,curl … | sh,chroot /host,nsenter?
Un chroot /host ou un nsenter --target 1 dans un pod avec un hostPath sur / ou hostPID, c'est une évasion de conteneur ; voir pods privilégiés et évasion de conteneur.
Requêtes de chasse
Lignes JSON brutes avec jq :
jq -c 'select(.objectRef.subresource == "exec" or .objectRef.subresource == "attach"
or .objectRef.subresource == "portforward")
| {t: .requestReceivedTimestamp, user: .user.username, ip: .sourceIPs,
ns: .objectRef.namespace, pod: .objectRef.name,
sub: .objectRef.subresource, uri: .requestURI, code: .responseStatus.code}' audit.log
EKS, CloudWatch Logs Insights :
fields @timestamp, user.username, sourceIPs.0, objectRef.namespace, objectRef.name, requestURI
| filter @logStream like "kube-apiserver-audit"
| filter objectRef.subresource in ["exec", "attach", "portforward"]
| sort @timestamp desc
AKS, Log Analytics (table spécifique à la ressource) :
AKSAudit
| where RequestUri has "/exec" or RequestUri has "/attach" or RequestUri has "/portforward"
| project TimeGenerated, User, SourceIps, UserAgent, RequestUri, ResponseStatus
Sur GKE, filtrez Cloud Logging sur protoPayload.methodName:"pods.exec" (ainsi que pods.attach et pods.portforward).
Réduire la surface d'attaque
- Réservez
pods/exec,pods/attachetpods/portforwardà un petit groupe « break-glass » ; ce sont des ressources RBAC distinctes, vous pouvez donc accordergetsur les pods sans elles. - N'accordez pas
nodes/proxyà des personnes ni à des workloads. - Alertez sur chaque exec par un compte de service, et sur chaque exec dans
kube-system. - Conservez le log d'audit : les sessions exec sont courtes et le pod aura peut-être disparu demain.
FAQ
Comment détecter kubectl exec dans les logs d'audit Kubernetes ?
Cherchez les événements dont objectRef.subresource vaut exec (ou attach) sur la ressource pods. Selon les versions du client et du serveur, le verbe est create ou get, et le code de réponse est 101 quand l'upgrade de streaming réussit. La commande figure dans requestURI sous forme de paramètres command= répétés.
Peut-on voir ce qui a été tapé dans un shell kubectl exec ?
Non. Le log d'audit enregistre la requête qui a ouvert la session et sa commande initiale, pas les octets échangés ensuite. Les frappes et les processus dans le conteneur nécessitent un outillage runtime comme Falco ou un EDR sur le nœud.
kubectl cp apparaît-il dans les logs d'audit ?
Oui, sous forme d'exec. kubectl cp lance tar dans le conteneur via pods/exec : l'événement d'audit montre command=tar et ses arguments dans requestURI.