Without network policies, every pod in Kubernetes can communicate with every other pod across all namespaces. A compromised pod thus has unrestricted access to databases, internal APIs, and other services. Default deny with explicit allow is the foundation of zero-trust in Kubernetes — every communication must be explicitly permitted. Lateral movement after a single pod compromise is significantly harder.
Default Deny¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
This policy blocks all incoming and outgoing traffic for all pods in the namespace. It is the starting point — from here you add explicit exceptions. Without policyTypes, an empty podSelector would have no effect. Set default deny in every namespace and then add specific rules to allow necessary communication.
Allowing Specific Communication¶
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
spec:
podSelector:
matchLabels:
app: api
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
This policy states: a pod with label app: api accepts ingress traffic only from pods with label app: frontend on port 8080 and may send egress traffic only to pods app: postgres on port 5432. All other communication remains blocked by the default deny policy.
Practical Tips¶
Start in audit mode — deploy policies with a logging CNI plugin (Cilium) and observe what communication occurs. Then gradually add restrictions. Do not forget egress rules for DNS (port 53 to kube-dns) — without them, pods will not be able to resolve domain names. Use namespace selectors for cross-namespace communication.
Key Takeaway¶
Default deny all, then allow explicitly. Network policies require a CNI plugin with policy support (Calico, Cilium, Weave Net). Vanilla Kubernetes with kubenet does not support network policies.