In a default Kubernetes cluster, every pod can talk to every other pod - across all namespaces, without any restriction. Your application pod can try to connect directly to your database, to your monitoring stack, to pods in completely unrelated namespaces. If a pod is compromised, the attacker gets the same network access the pod had.
Network Policies fix this. They let you define exactly which pods can talk to which, and block everything else. This is Part 2 of the security work we started with RBAC hardening. That locked down the Kubernetes API. This locks down the network.
What You Need
- Install kubectl — configured against your cluster
- A CNI that supports Network Policies: Calico, Cilium, or Weave. If on EKS, see below.
# Verify your CNI supports Network Policies
kubectl get pods -n kube-system | grep -E "calico|cilium|weave"
# Calico, Cilium, Weave: supported
# Flannel: NOT supported — you'll need to install Calico alongside it
# On EKS, enable Network Policy with Amazon VPC CNI (EKS 1.25+):
kubectl set env daemonset aws-node -n kube-system ENABLE_NETWORK_POLICY=true
In a default Kubernetes cluster, every pod can talk to every other pod — across all namespaces. If an application pod is compromised, an attacker can attempt connections to your database, your internal APIs, your monitoring systems. Network Policies restrict this.
This is Part 2 of the Kubernetes Security series. Part 1 covered RBAC hardening — restricting what Kubernetes API actions pods can perform. Network Policies restrict which pods can communicate with each other.
Step 1: Apply Default Deny
The first policy I apply to any production namespace blocks everything. Then I add back only what needs to work:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Applies to ALL pods in this namespace
policyTypes:
- Ingress
- Egress
kubectl apply -f default-deny.yaml
# Verify it's applied
kubectl get networkpolicies -n production
# NAME POD-SELECTOR AGE
# default-deny-all 5s
Important: after applying default-deny, your pods lose all connectivity immediately — including DNS. Apply the DNS allowance below right away.
Step 2: Allow DNS (Required for Everything)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {} # All pods need DNS
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Step 3: Allow Your App to Reach Its Database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-to-postgres
namespace: production
spec:
podSelector:
matchLabels:
app: postgres # This policy applies TO postgres pods
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: my-app # Only my-app can connect to postgres
ports:
- protocol: TCP
port: 5432
Step 4: Allow Ingress Controller to Reach Your App
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-app
namespace: production
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080
Step 5: Allow App Egress to External Services
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-external-egress
namespace: production
spec:
podSelector:
matchLabels:
app: my-app
policyTypes:
- Egress
egress:
- ports:
- protocol: TCP
port: 443 # HTTPS to external APIs (Stripe, SendGrid, etc.)
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
Testing Your Policies
# Spawn a debug pod in the namespace
kubectl run test-pod --image=busybox --rm -it --restart=Never -n production -- sh
# Inside test-pod — test what should fail
nc -zv postgres-service 5432
# nc: postgres-service (10.x.x.x:5432): Connection refused ← correct, test-pod is not my-app
# Test DNS still works (should succeed — we allowed it)
nslookup kubernetes.default.svc.cluster.local
# Apply all policies at once
kubectl apply -f network-policies/
# Verify what policies exist
kubectl get networkpolicies -n production
# NAME POD-SELECTOR AGE
# default-deny-all 1m
# allow-dns 30s
# allow-app-to-postgres app=postgres 30s
# allow-ingress-to-app app=my-app 30s
# allow-app-external-egress app=my-app 30s
The most common mistake when implementing Network Policies for the first time: applying default-deny without mapping all traffic paths first. Use Cilium Hubble or kubectl sniff to observe actual pod communications for a week before locking things down. That way you know what to allow before you block it.
Combined with the RBAC policies from Part 1, Network Policies give you defence in depth — even if a pod is compromised, it cannot freely communicate with the rest of your infrastructure.

Comments
All comments are reviewed before appearing.
Leave a Comment