Skip to content

Apply Kubernetes network policies

Severity: highApplies to: Kubernetes 1.29+ (with a CNI that enforces NetworkPolicy)Applies to: Kubernetes 1.34
The fix
# Default-deny all ingress in a namespace — the foundation
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: orders
spec:
podSelector: {}
policyTypes: [Ingress]

Then add allow-policies for the traffic that should exist. Default-deny first, allow-specific second.

Kubernetes networking is flat by default: every pod can open a connection to every other pod, in every namespace, with nothing in the way. Your frontend can reach your database directly, one team’s workload can reach another’s, and — the part that matters for security — a single compromised pod can reach the entire cluster’s internal services.

That flat network is what turns one foothold into lateral movement. An attacker who compromises a low-value pod (a public-facing web app) can, with no further exploit, scan and connect to every internal service: databases, caches, the metadata endpoint, other apps’ APIs. Network policies are the segmentation that stops that — the pod can only reach what you explicitly allowed.

A NetworkPolicy is allow-only and additive: policies grant traffic, and the moment any policy selects a pod, everything not allowed is denied for that pod. So the pattern is:

  1. Default-deny ingress (and egress) in the namespace — a policy selecting all pods (podSelector: {}) that allows nothing.
  2. Allow-specific policies layered on top — “the API pods may receive traffic from the frontend on port 8080.”

Without the default-deny, a namespace with a few allow-policies still lets unselected pods talk to anything. The deny-all is what flips the namespace from “allow by default” to “deny by default,” which is the posture you want.

Egress policies matter as much as ingress here: default-deny egress stops a compromised pod from reaching the metadata service, exfiltrating to the internet, or scanning internal ranges — often more valuable than ingress rules.

This is the trap that makes network policies silently useless: NetworkPolicy objects do nothing unless your CNI plugin enforces them. The API accepts the object, kubectl get networkpolicy shows it, and it has zero effect if the network plugin ignores it.

  • Calico, Cilium, Antrea, Weave enforce them.
  • Flannel does not — a very common CNI with no NetworkPolicy support.
  • Some managed clusters need network policy explicitly enabled at creation.

So before writing policies, confirm the CNI enforces them, or you’re writing security theatre. The verify block below tests this directly.

A default-deny egress policy blocks DNS, because DNS is a pod-to-pod call to CoreDNS in kube-system. Every allow-egress policy needs to also permit DNS, or every pod in the namespace loses name resolution — which looks like everything breaking at once:

egress:
- to:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: kube-system }
ports:
- { protocol: UDP, port: 53 }
- { protocol: TCP, port: 53 }

Forgetting this is the single most common network-policy mistake.