kubectl port-forward semble établir une connexion réseau ordinaire vers un pod, mais le chemin réel ne passe jamais par le réseau du cluster : le trafic transite par le serveur API Kubernetes, qui le relaie vers le kubelet du nœud concerné, qui l’injecte directement dans le namespace réseau du pod. Ce détour a une conséquence de sécurité directe, rarement anticipée : plusieurs contrôles réseau déjà en place ne voient jamais ce trafic.

Le chemin réel, très différent d’une connexion réseau classique

Une connexion normale vers un pod (via un Service, un Ingress) traverse le réseau du cluster, où le CNI applique les NetworkPolicy en vigueur. port-forward emprunte un chemin entièrement différent : client kubectl → serveur API → kubelet du nœud → namespace réseau du pod, un tunnel qui n’existe qu’entre ces quatre points précis, jamais visible comme du trafic réseau ordinaire entre pods ou depuis l’extérieur.

# Le trafic transite par le serveur API,
# jamais par le réseau du cluster que NetworkPolicy surveille
kubectl port-forward pod/my-app 8080:8080

Pourquoi NetworkPolicy ne voit jamais ce trafic

Une NetworkPolicy qui restreint l’accès entrant à un pod (ingress limité à certains pods sources, ou même un deny-all complet) n’a aucun effet sur port-forward, puisque ce trafic n’entre jamais par l’interface réseau que le CNI surveille : il est injecté directement dans le namespace réseau du conteneur par le kubelet, un chemin que NetworkPolicy ne couvre structurellement pas. Un pod parfaitement isolé par NetworkPolicy reste entièrement accessible via port-forward, à quiconque a la permission RBAC nécessaire.

Le verbe RBAC spécifique qui gouverne l’accès

port-forward n’est pas soumis aux mêmes permissions que la lecture ou la modification d’un pod : il exige un verbe RBAC distinct, create sur le sous-ressource pods/portforward, indépendant des permissions get ou list habituelles.

# Un rôle peut lire les pods sans jamais
# pouvoir établir de port-forward, ou l'inverse
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
rules:
  - apiGroups: [""]
    resources: ["pods/portforward"]
    verbs: ["create"]

Un audit RBAC qui ne vérifie que les permissions get/list/watch habituelles manque systématiquement ce verbe distinct : une identité qui ne peut techniquement pas lire le contenu d’un pod peut malgré tout établir un tunnel direct vers lui, si ce verbe précis lui a été accordé par erreur ou par habitude d’un rôle trop large.

Ce que ça implique pour l’observabilité réseau

Un outil de détection réseau comme Falco, qui observe les appels système et le trafic réseau au niveau kernel, voit bien la connexion établie côté kubelet, mais cette connexion ne ressemble à aucun schéma de trafic réseau habituel entre pods : elle nécessite des règles spécifiques pour être distinguée d’un trafic légitime, une nuance souvent absente d’une configuration Falco de base pensée pour le trafic pod-à-pod classique.

À retenir

kubectl port-forward transite par le serveur API et le kubelet, jamais par le réseau du cluster, ce qui rend NetworkPolicy structurellement aveugle à ce trafic : un pod parfaitement isolé reste accessible via ce mécanisme. Ce chemin est gouverné par son propre verbe RBAC (create sur pods/portforward), distinct des permissions de lecture habituelles, un détail qu’un audit RBAC superficiel manque facilement. Restreindre explicitement ce verbe, plutôt que de supposer qu’un rôle en lecture seule ne permet aucun accès direct, fait partie des réflexes de sécurité qui complètent le triptyque RBAC/NetworkPolicy/Falco dès qu’une migration Kubernetes doit fermer les angles morts entre contrôles.