Overview
Kubernetes Network Policies enable microsegmentation by controlling traffic flow between pods. By default, pods can communicate with any other pod - Network Policies allow you to restrict this and implement a Zero Trust network model within your cluster.
Who Should Use This Guide:
- Platform engineers securing Kubernetes workloads
- Security teams implementing Zero Trust in Kubernetes
- DevOps engineers hardening production clusters
- Architects designing secure multi-tenant environments
Network Policy Capabilities:
| Feature | Description |
|---|---|
| Pod Selector | Select pods by labels to apply policies |
| Namespace Selector | Select pods in specific namespaces |
| Ingress Rules | Control incoming traffic to pods |
| Egress Rules | Control outgoing traffic from pods |
| IP Blocks | Allow/deny traffic from CIDR ranges |
| Ports | Specify allowed ports and protocols |
Network Policy CNI Support:
| CNI Plugin | Network Policy Support | Advanced Features |
|---|---|---|
| Calico | Full | GlobalNetworkPolicy, DNS policies |
| Cilium | Full | L7 policies, DNS-aware, Hubble |
| Weave Net | Full | - |
| Flannel | None (requires Calico) | - |
| Azure CNI | Full (with Azure NPM) | - |
| AWS VPC CNI | Partial (with Calico) | - |
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Kubernetes Network Policy Architecture │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Namespace: production Namespace: database │
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐│
│ │ ┌─────────┐ │ │ ┌─────────┐ ││
│ │ │ Web App │ │ │ │ MySQL │ ││
│ │ │ Pod │──────────────┼─────┼────────▶│ Pod │ ││
│ │ └─────────┘ Allow │ │ Allow └─────────┘ ││
│ │ │ 3306 │ │ from ││
│ │ │ │ │ production ││
│ │ │ │ │ ││
│ │ ▼ │ └─────────────────────────────┘│
│ │ ┌─────────┐ │ │
│ │ │ API │ │ Namespace: monitoring │
│ │ │ Pod │ │ ┌─────────────────────────────┐│
│ │ └─────────┘ │ │ ┌─────────┐ ││
│ │ │ │ │ │Prometheus│ ││
│ │ │ Allow 9090 │ │ │ Pod │◀────────┼│
│ │ └───────────────────┼─────┼─────────└─────────┘ Scrape ││
│ │ │ │ from all ││
│ └─────────────────────────────┘ └─────────────────────────────┘│
│ │
│ Default: DENY all traffic (with default deny policies) │
│ Explicit: ALLOW only defined paths │
│ │
└─────────────────────────────────────────────────────────────────────┘Prerequisites Check
Verify Network Policy Support
# Check if CNI supports network policies
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl get pods -n kube-system -l k8s-app=cilium
# For AKS, check Azure NPM
kubectl get pods -n kube-system -l k8s-app=azure-npm
# Create test policy to verify support
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
# Verify policy was created
kubectl get networkpolicy test-network-policy
# Cleanup test
kubectl delete networkpolicy test-network-policyInstall Calico (If Needed)
# For clusters without network policy support
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
# Verify installation
kubectl get pods -n calico-system
kubectl wait --for=condition=ready pod -l k8s-app=calico-node -n calico-system --timeout=300sStep 1: Implement Default Deny Policies
Start with a default deny posture, then explicitly allow required traffic.
Default Deny All Ingress (Per Namespace)
# default-deny-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # Applies to all pods in namespace
policyTypes:
- Ingress
# No ingress rules = deny all ingresskubectl apply -f default-deny-ingress.yamlDefault Deny All Egress (Per Namespace)
# default-deny-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
# No egress rules = deny all egressDefault Deny Both (Recommended Starting Point)
# default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressApply to All Namespaces Script
#!/bin/bash
# apply-default-deny.sh
# Namespaces to protect (exclude system namespaces)
NAMESPACES=$(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | grep -v -E '^(kube-system|kube-public|kube-node-lease|calico-system)$')
for ns in $NAMESPACES; do
echo "Applying default deny to namespace: $ns"
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: $ns
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF
doneStep 2: Allow DNS Traffic
After denying all egress, pods can't resolve DNS. Allow DNS queries.
# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {} # All pods in namespace
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Alternative: Allow DNS by IP (CoreDNS ClusterIP)
# Get CoreDNS ClusterIP
kubectl get svc kube-dns -n kube-system -o jsonpath='{.spec.clusterIP}'# allow-dns-by-ip.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.96.0.10/32 # Replace with your CoreDNS IP
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Step 3: Create Application-Specific Policies
Web Application Ingress Policy
Allow traffic from ingress controller to web pods.
# web-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-web
namespace: production
spec:
podSelector:
matchLabels:
app: web-frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
ports:
- protocol: TCP
port: 8080Allow Web to API Communication
# web-to-api.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api-backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web-frontend
ports:
- protocol: TCP
port: 3000Allow API to Database Communication
# api-to-database.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-database
namespace: database
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: production
podSelector:
matchLabels:
app: api-backend
ports:
- protocol: TCP
port: 3306Egress policy for API pods:
# api-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-egress
namespace: production
spec:
podSelector:
matchLabels:
app: api-backend
policyTypes:
- Egress
egress:
# Allow DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
# Allow database access
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: database
podSelector:
matchLabels:
app: mysql
ports:
- protocol: TCP
port: 3306
# Allow external API calls (optional)
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # Block internal
- 172.16.0.0/12 # Block internal
- 192.168.0.0/16 # Block internal
ports:
- protocol: TCP
port: 443Step 4: Namespace Isolation
Isolate namespaces from each other by default.
Deny Cross-Namespace Traffic
# namespace-isolation.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-from-other-namespaces
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {} # Only same namespaceAllow Specific Cross-Namespace Communication
# allow-monitoring-scrape.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-prometheus-scrape
namespace: production
spec:
podSelector:
matchLabels:
prometheus.io/scrape: "true"
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090Step 5: External Traffic Control
Allow External Ingress via LoadBalancer
# allow-external-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-web
namespace: production
spec:
podSelector:
matchLabels:
app: web-frontend
expose: external
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443Restrict Egress to Specific External IPs
# restrict-external-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-specific-external
namespace: production
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Egress
egress:
# DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
# Payment processor IPs only
- to:
- ipBlock:
cidr: 203.0.113.0/24 # Payment provider range
ports:
- protocol: TCP
port: 443Step 6: Complete Application Example
Full network policy set for a three-tier application.
# complete-app-policies.yaml
---
# Default deny for production namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow DNS for all pods
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
---
# Frontend: Allow from ingress, allow to API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: production
spec:
podSelector:
matchLabels:
tier: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80
egress:
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 8080
---
# Backend: Allow from frontend, allow to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
tier: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
tier: database
ports:
- protocol: TCP
port: 5432
---
# Database: Allow from backend only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
namespace: production
spec:
podSelector:
matchLabels:
tier: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 5432Step 7: Testing and Validation
Deploy Test Pods
# test-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-client
namespace: production
labels:
role: test
spec:
containers:
- name: curl
image: curlimages/curl:latest
command: ["sleep", "infinity"]
---
apiVersion: v1
kind: Pod
metadata:
name: test-server
namespace: production
labels:
role: test
app: web-frontend
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80Test Connectivity
# Apply test pods
kubectl apply -f test-pods.yaml
# Wait for pods to be ready
kubectl wait --for=condition=ready pod/test-client -n production --timeout=60s
kubectl wait --for=condition=ready pod/test-server -n production --timeout=60s
# Test allowed traffic (should work if policy allows)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 test-server
# Test blocked traffic (should timeout)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 http://10.96.0.1
# Test DNS resolution
kubectl exec -n production test-client -- nslookup kubernetes.default
# Test external connectivity (if allowed)
kubectl exec -n production test-client -- curl -s --connect-timeout 5 https://google.comNetwork Policy Testing Script
#!/bin/bash
# test-network-policies.sh
NAMESPACE="production"
TEST_POD="test-client"
echo "=== Network Policy Testing ==="
# Test 1: DNS Resolution
echo -n "DNS Resolution: "
if kubectl exec -n $NAMESPACE $TEST_POD -- nslookup kubernetes.default > /dev/null 2>&1; then
echo "PASS"
else
echo "FAIL"
fi
# Test 2: Same namespace communication
echo -n "Same Namespace (test-server): "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 test-server > /dev/null 2>&1; then
echo "PASS (allowed)"
else
echo "BLOCKED"
fi
# Test 3: Cross namespace (kube-system)
echo -n "Cross Namespace (kube-system): "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 http://metrics-server.kube-system > /dev/null 2>&1; then
echo "ALLOWED (check policy)"
else
echo "BLOCKED (expected)"
fi
# Test 4: External internet
echo -n "External Internet: "
if kubectl exec -n $NAMESPACE $TEST_POD -- curl -s --connect-timeout 3 https://google.com > /dev/null 2>&1; then
echo "ALLOWED"
else
echo "BLOCKED"
fi
echo "=== Testing Complete ==="Step 8: Debugging Network Policies
View Applied Policies
# List all network policies in namespace
kubectl get networkpolicies -n production
# Describe specific policy
kubectl describe networkpolicy allow-web-to-api -n production
# Get policy YAML
kubectl get networkpolicy allow-web-to-api -n production -o yamlCheck Pod Labels
# Verify pod labels match policy selectors
kubectl get pods -n production --show-labels
# Check if pod matches a selector
kubectl get pods -n production -l app=api-backendCalico-Specific Debugging
# View Calico policies
kubectl get networkpolicies.crd.projectcalico.org -A
# Check Calico node status
kubectl exec -n calico-system -it $(kubectl get pod -n calico-system -l k8s-app=calico-node -o name | head -1) -- calico-node -bird-live
# View Felix logs
kubectl logs -n calico-system -l k8s-app=calico-node -c calico-node | grep -i policyCilium-Specific Debugging
# Check Cilium status
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium status
# Monitor policy drops
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium monitor --type drop
# View endpoint policies
kubectl exec -n kube-system -it $(kubectl get pod -n kube-system -l k8s-app=cilium -o name | head -1) -- cilium endpoint listPacket Capture
# Capture traffic on a pod (requires debug privileges)
kubectl debug -n production pod/api-pod -it --image=nicolaka/netshoot -- tcpdump -i any -n
# Capture specific traffic
kubectl debug -n production pod/api-pod -it --image=nicolaka/netshoot -- tcpdump -i any port 3306 -nCommon Patterns
Allow Internal Load Balancer
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-internal-lb
namespace: production
spec:
podSelector:
matchLabels:
app: internal-api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8 # Internal network range
ports:
- protocol: TCP
port: 8080Allow Health Checks
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-health-checks
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
# Allow kubelet health checks
- from:
- ipBlock:
cidr: 0.0.0.0/0 # Node network
ports:
- protocol: TCP
port: 8080 # Health check port
endPort: 8081Allow Pod-to-Pod Same Label
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-app
namespace: production
spec:
podSelector:
matchLabels:
app: microservice-a
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: microservice-aTroubleshooting
Common Issues:
| Symptom | Possible Cause | Solution |
|---|---|---|
| All traffic blocked | Default deny without allow rules | Add required allow policies |
| DNS not working | Forgot DNS egress policy | Add allow-dns policy |
| Cross-namespace blocked | Namespace labels missing | Add kubernetes.io/metadata.name label |
| Ingress not working | Wrong ingress namespace selector | Verify ingress controller namespace |
| Pods can't reach external | Egress to 0.0.0.0/0 missing | Add external egress rule |
Verification Commands:
# Check pod can reach service
kubectl exec -n production test-pod -- nc -zv service-name 80
# Check DNS resolution
kubectl exec -n production test-pod -- nslookup kubernetes.default
# Check external connectivity
kubectl exec -n production test-pod -- curl -I https://google.com
# List all policies affecting a pod
kubectl get networkpolicy -n production -o wideVerification Checklist
Policy Implementation:
- Default deny policies applied to sensitive namespaces
- DNS egress allowed for all pods
- Application-specific ingress rules created
- Application-specific egress rules created
- Cross-namespace policies for allowed communication
Testing:
- Allowed traffic flows correctly
- Blocked traffic is denied
- DNS resolution works
- External access controlled as expected
Operations:
- Policy documentation updated
- GitOps workflow for policy changes
- Monitoring for policy violations
- Runbook for debugging connectivity issues
Next Steps
After implementing Network Policies:
- Implement Cilium L7 Policies - HTTP/gRPC-aware filtering
- Enable Network Policy Logging - Audit denied connections
- Integrate with Service Mesh - Combine with Istio/Linkerd
- Automate Policy Generation - Use tools like Cilium Editor
References
- Kubernetes Network Policies Documentation
- Calico Network Policy Guide
- Cilium Network Policies
- Network Policy Editor (Cilium)
- Network Policy Recipes
Last Updated: February 2026