Apply Kubernetes network policies
# Default-deny all ingress in a namespace — the foundationapiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-ingress namespace: ordersspec: podSelector: {} policyTypes: [Ingress]Then add allow-policies for the traffic that should exist. Default-deny first, allow-specific second.
Why it matters
Section titled “Why it matters”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.
Default-deny is the foundation
Section titled “Default-deny is the foundation”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:
- Default-deny ingress (and egress) in the namespace — a policy selecting all
pods (
podSelector: {}) that allows nothing. - 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.
The CNI has to enforce it — check first
Section titled “The CNI has to enforce it — check first”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.
DNS is the classic self-inflicted outage
Section titled “DNS is the classic self-inflicted outage”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.
Related
Section titled “Related”- Restrict privileged containers — reduces the compromise that lateral movement follows.
- Disable service account automount — the other thing a compromised pod would use.