Default-deny and per-consumer allowlists.
💡 In plain terms: By default every pod can talk to every other pod — one big open-plan office. A NetworkPolicy adds doors and a guest list so only approved conversations happen. Just remember to leave the phone line (DNS) open.
The cluster network is flat and open by default — any pod can reach any other pod. NetworkPolicies are how you segment it, and adopting a default-deny posture with explicit allowlists is the single most effective control against lateral movement inside a cluster.
Default-deny and allowlists
A NetworkPolicy selects pods (by label) and specifies the ingress and egress traffic they are allowed — and crucially, the moment any policy selects a pod, that pod switches from allow-all to deny-all except what the policies permit. So the foundational move is a default-deny policy: one that selects all pods in a namespace and allows nothing, flipping the namespace from open to closed. Then you add targeted allow policies for the flows that must exist — the frontend may reach the API on port 8080, the API may reach the database on 5432, everything reaches DNS. This is the same least-privilege model as RBAC applied to the network: start closed, open only what is needed, and identify peers by label (podSelector/namespaceSelector) so rules follow workloads rather than fragile IPs.
# 1. Default-deny ALL ingress + egress in the namespace (flip it closed):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: payments }
spec: { podSelector: {}, policyTypes: [Ingress, Egress] }
---
# 2. Allow only what is needed (and DON'T forget DNS egress):
kind: NetworkPolicy
metadata: { name: api-from-frontend, namespace: payments }
spec:
podSelector: { matchLabels: { app: api } }
ingress: [{ from: [{ podSelector: { matchLabels: { app: frontend } } }],
ports: [{ port: 8080 }] }]
The two things that trip people up
Two caveats decide whether NetworkPolicies actually work. First, they are enforced by the CNI, not by Kubernetes itself — on a plugin that does not support them (basic Flannel) your policies are accepted by the API but silently unenforced, so you must confirm your CNI (Calico, Cilium) enforces them and test that a default-deny actually blocks traffic. Second, a default-deny egress policy blocks DNS unless you explicitly allow it — pods query CoreDNS in kube-system on port 53, and if your policy forgets that, name resolution fails for every affected pod and their dependencies appear down, a very common self-inflicted breakage. So the recipe is: verify the CNI enforces policy, apply default-deny, then add allows including DNS egress to kube-system, and test. Done right, NetworkPolicies box a compromised pod into the tiny set of flows you granted — the difference between a foothold that can scan and reach the whole cluster and one that can talk only to the two services it legitimately needs. This is the network complement to RBAC and security contexts, and it is the highest-leverage control against lateral movement.
⚠️ A default-deny that forgets DNS breaks every pod it covers: A default-deny egress policy blocks pods from reaching CoreDNS unless you explicitly allow DNS (port 53 to kube-system), so name resolution fails and dependencies appear down across the namespace. Always add a DNS egress allow when applying default-deny — and remember policies only work if your CNI enforces them, so verify a default-deny actually blocks traffic before relying on it.
This lesson is part of the free *Kubernetes administration** course at SecOpsLog — hands-on, command-first DevSecOps tutorials. Read the original with the full interactive version →*
Top comments (0)