kubectl port-forward looks like it establishes an ordinary network connection to a pod, but the real path never goes through the cluster network: traffic passes through the Kubernetes API server, which relays it to the concerned node’s kubelet, which injects it directly into the pod’s network namespace. That detour has a direct security consequence, rarely anticipated: several network controls already in place never see this traffic.
The real path, very different from a classic network connection
A normal connection to a pod (via a Service, an Ingress) crosses the cluster network, where the CNI enforces active NetworkPolicy rules. port-forward takes an entirely different path: kubectl client → API server → node’s kubelet → pod’s network namespace, a tunnel existing only between these four precise points, never visible as ordinary network traffic between pods or from outside.
# Traffic goes through the API server,
# never through the cluster network NetworkPolicy watches
kubectl port-forward pod/my-app 8080:8080
Why NetworkPolicy never sees this traffic
A NetworkPolicy restricting inbound access to a pod (ingress limited to certain source pods, or even a full deny-all) has no effect on port-forward, since this traffic never enters through the network interface the CNI monitors: it gets injected directly into the container’s network namespace by the kubelet, a path NetworkPolicy structurally doesn’t cover. A pod perfectly isolated by NetworkPolicy stays entirely reachable via port-forward, by anyone holding the necessary RBAC permission.
The specific RBAC verb governing access
port-forward isn’t subject to the same permissions as reading or modifying a pod: it requires a distinct RBAC verb, create on the pods/portforward subresource, independent of the usual get or list permissions.
# A role can read pods without ever being able
# to establish a port-forward, or the reverse
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
rules:
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create"]
An RBAC audit checking only the usual get/list/watch permissions systematically misses this distinct verb: an identity technically unable to read a pod’s content can still establish a direct tunnel to it, if this precise verb was granted by mistake or by habit of an overly broad role.
What this means for network observability
A network detection tool like Falco, which watches syscalls and network traffic at the kernel level, does see the connection established on the kubelet side, but that connection doesn’t resemble any usual network traffic pattern between pods: it requires specific rules to be distinguished from legitimate traffic, a nuance often absent from a base Falco configuration designed for classic pod-to-pod traffic.
Takeaway
kubectl port-forward goes through the API server and the kubelet, never the cluster network, which makes NetworkPolicy structurally blind to this traffic: a perfectly isolated pod stays reachable through this mechanism. This path is governed by its own RBAC verb (create on pods/portforward), distinct from usual read permissions, a detail a surface-level RBAC audit easily misses. Explicitly restricting this verb, rather than assuming a read-only role permits no direct access, is one of the security habits that complete the RBAC/NetworkPolicy/Falco trilogy the moment a Kubernetes migration needs to close the blind spots between controls.