Introduction
In Kubernetes environments, the default network behavior allows any pod to communicate with any other pod across all namespaces. This flat network model is convenient for development but creates significant security risk in production — a compromised pod can freely scan and attack every other service in the cluster. Network Policies provide microsegmentation at the pod level, controlling east-west traffic with the same precision that traditional firewalls control north-south traffic.
Understanding Kubernetes Network Model
By default, Kubernetes networking follows three rules:
- Every pod gets its own IP address
- Every pod can communicate with every other pod without NAT
- There are no network-level access controls between pods
Network Policies override rule #3 by defining ingress and egress rules that are enforced by the CNI (Container Network Interface) plugin. Not all CNIs support Network Policies — Calico, Cilium, and Weave all do, while Flannel does not.
Default Deny: The Foundation
The first step in any network policy strategy is establishing a default deny posture. Without this, Network Policies are additive — they only allow additional traffic beyond the default “allow all”:
# default-deny-all.yaml
# Apply to every namespace that needs protection
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Applies to all pods in the namespace
policyTypes:
- Ingress
- Egress
With this policy applied, no pod in the namespace can send or receive any traffic until explicitly allowed. This is the network equivalent of a firewall with a default DROP rule.
Allow DNS: Essential for Every Namespace
After applying default deny, pods can’t resolve DNS — which breaks almost everything. Allow DNS egress to kube-dns:
# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Service-to-Service Policies
Now define explicit policies for each service’s communication requirements. Consider a typical three-tier application:
Frontend: Allow Ingress from Load Balancer, Egress to API
# frontend-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 3000
API: Allow Ingress from Frontend, Egress to Database
# api-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
Database: Allow Ingress from API Only
# database-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
namespace: production
spec:
podSelector:
matchLabels:
app: database
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
egress: [] # Database should not initiate outbound connections
Cross-Namespace Policies
For services that need to communicate across namespaces (e.g., a shared monitoring stack), use namespace selectors:
# allow-monitoring.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prometheus-scrape
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090
Egress Control: Limiting External Access
Control which pods can reach external services. This is critical for preventing data exfiltration from compromised pods:
# allow-external-api.yaml
# Only the API pod can reach external payment processor
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-external-egress
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 203.0.113.0/24 # Payment processor IP range
ports:
- protocol: TCP
port: 443
Testing and Validation
Verify your policies work as expected before relying on them in production:
# Deploy a test pod for connectivity checks
kubectl run test-pod --image=busybox --restart=Never -- sleep 3600
# Test allowed connection (should succeed)
kubectl exec test-pod -- wget -qO- --timeout=5 http://api.production.svc:3000/health
# Test blocked connection (should timeout)
kubectl exec test-pod -- wget -qO- --timeout=5 http://database.production.svc:5432
# Check network policy enforcement
kubectl describe networkpolicy -n production
Monitoring Policy Enforcement
With Cilium as your CNI, you get flow visibility and policy enforcement metrics:
# View dropped flows (policy violations)
hubble observe --verdict DROPPED --namespace production
# Count policy drops per source/destination
hubble observe --verdict DROPPED -o json | \
jq '.source.labels, .destination.labels' | sort | uniq -c | sort -rn
Conclusion
Kubernetes Network Policies provide essential microsegmentation for container workloads. The pattern is consistent: start with default deny, allow DNS, then explicitly permit only the communication paths each service requires. This zero-trust approach to east-west traffic means that even if an attacker compromises one pod, their lateral movement is restricted to only the services that pod legitimately communicates with. Combined with monitoring for policy violations, Network Policies transform your cluster from a flat network into a segmented, defensible architecture.
