Default deny for Kubernetes networks
- Cloud-native security guidance around Kubernetes keeps converging on one baseline: make pod traffic default-deny first, then open only the paths you mean. - The key detail is behavioral, not branding — in Kubernetes, pods are effectively default-allow until a matching NetworkPolicy isolates ingress or egress. - That matters because zero-trust inside clusters fails fast without enforcement, and some CNIs still differ in how far policy can really go.
Kubernetes networking starts from a surprisingly permissive place. If no NetworkPolicy selects a pod, that pod can usually talk to other pods freely. That is why “default deny” keeps showing up as the first serious hardening move in production clusters. It is not a trendy extra — it is the line between “we hope services stay in their lane” and “the cluster enforces it.” ### Why is default deny the baseline? Because Kubernetes does not isolate pods by default. A pod only becomes isolated for ingress or egress when at least one matching NetworkPolicy says so for that direction. From that point on, only the traffic explicitly allowed by applicable policies gets through, with reply traffic allowed for approved connections. So the baseline pattern is simple — select everything in a namespace, deny by default, then add narrow allow rules. (kubernetes.io) ### What does that change in practice? It flips the burden of proof. In a default-allow cluster, every new workload can often reach far more than its owner intended. In a default-deny cluster, a new workload reaches nothing until someone writes the policy. That is much closer to zero-trust thinking — every flow has to be named, and broad lateral movement gets much harder. Calico’s docs frame this as the safer posture because pods without policy, or with broken policy, are not allowed traffic until the right rules exist. (kubernetes.io) ### Why do teams trip over this? Because “deny everything” also denies the boring stuff you forgot was traffic. DNS is the classic example. Kubernetes docs call out that a default deny-all egress policy blocks DNS resolution unless you add a separate allow rule to the cluster DNS service. Cilium’s own default-deny example bakes in a DNS exception for exactly that reason. The catch is that many outages blamed on “NetworkPolicy broke the app” are really “we denied dependencies we never mapped.” (docs.tigera.io) ### Does Kubernetes itself enforce this? Not by itself. NetworkPolicy is an API, but enforcement comes from the network plugin — the CNI. Kubernetes is explicit about that: creating a NetworkPolicy object has no effect unless the cluster uses a networking solution that supports NetworkPolicy. That means your zero-trust story is only as real as the CNI underneath it. ### Are all policy engines the same? (kubernetes.io) Not really. The standard Kubernetes NetworkPolicy model covers L3/L4 controls — IPs, ports, and protocols. But popular engines add more. Cilium supports clusterwide policies and explicit deny policies that take precedence over allow rules. Calico supports broader policy layers too, including global policy constructs. So “default deny” is the common foundation, but the tooling above that foundation can be much richer depending on what runs your cluster. (kubernetes.io) ### Should you deny the whole cluster at once? Usually no. Calico’s guidance is to be careful with global default deny, especially around system pods. A clusterwide lockout sounds clean, but it is easy to break control-plane or platform services if you apply it too broadly. The safer rollout is namespace by namespace, starting with non-system workloads, then adding explicit allows for app-to-app traffic, DNS, metrics, and ingress paths. (docs.cilium.io) ### So what is the real takeaway? Default deny is not the whole Kubernetes security model, but it is the part that makes network intent real. Without it, “least privilege” is mostly documentation. With it, every allowed connection becomes deliberate — and that is the practical starting point for zero-trust inside a cluster. (kubernetes.io) (docs.tigera.io)