Skip to content

Kubernetes_network_policies

Network Policies are Kubernetes resources that control the traffic between pods and network endpoints. By default, all pods can communicate with each other (flat network), but Network Policies allow you to implement zero-trust security by explicitly defining which pods can communicate.

Without Network Policies:

  • Any pod can access any other pod
  • Lateral movement is easy for attackers
  • No isolation between namespaces
  • Compliance requirements cannot be met

With Network Policies:

  • Pods only receive traffic they’re authorized to receive
  • Defense in depth strategy
  • Namespace isolation
  • Compliance with security standards (PCI-DSS, SOC2, etc.)
┌─────────────────────────────────────────────────────────────────┐
│ Network Policy Model │
│ │
│ ┌──────────┐ │
│ │ Ingress │ ──▶ Controls incoming traffic TO a pod │
│ │ Rules │ │
│ └──────────┘ │
│ │
│ ┌──────────┐ │
│ │ Egress │ ──▶ Controls outgoing traffic FROM a pod │
│ │ Rules │ │
│ └──────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ podSelector: Which pods this policy applies to │ │
│ │ namespaceSelector: Which namespaces │ │
│ │ ipBlock: CIDR ranges for external traffic │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

Deny all ingress traffic to selected pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector:
matchLabels: {} # Empty = all pods in namespace
policyTypes:
- Ingress

This is the “deny by default” approach - pods won’t receive any traffic until explicitly allowed.

Allow traffic from specific pods:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-from-web
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 8080

This allows pods with label app: api to receive traffic on port 8080 from pods with label app: web.

Allow traffic from specific namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9090

Combine multiple sources with AND/OR logic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-multiple
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
# Allow from web pods OR from monitoring namespace
- from:
- podSelector:
matchLabels:
app: web
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 8080

Control outgoing traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-egress-policy
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow to database namespace
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
spec:
podSelector:
matchLabels:
tier: frontend
policyTypes:
- Ingress
- Egress
ingress:
# Allow from ingress controller / load balancer
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
egress:
# Allow to backend
- to:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 8080
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
spec:
podSelector:
matchLabels:
tier: backend
policyTypes:
- Ingress
- Egress
ingress:
# Allow from frontend only
- from:
- podSelector:
matchLabels:
tier: frontend
ports:
- protocol: TCP
port: 8080
egress:
# Allow to database
- to:
- podSelector:
matchLabels:
tier: database
ports:
- protocol: TCP
port: 5432
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
spec:
podSelector:
matchLabels:
tier: database
policyTypes:
- Ingress
- Egress
ingress:
# Allow from backend only
- from:
- podSelector:
matchLabels:
tier: backend
ports:
- protocol: TCP
port: 5432
egress: []

Control traffic to/from external IP ranges:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-api
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
# Allow to specific external IP ranges (e.g., external API)
- to:
- ipBlock:
cidr: 203.0.113.0/24
except:
- 203.0.113.1/32
ports:
- protocol: TCP
port: 443
# Allow DNS
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53

Specify a range of ports:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-http-ports
spec:
podSelector:
matchLabels:
app: webserver
policyTypes:
- Ingress
ingress:
- ports:
- protocol: TCP
port: 80
endPort: 8080 # Allows ports 80-8080

Note: endPort requires the CNI plugin to support it.

It’s common to apply these two policies to start with zero-trust:

# Deny all ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
---
# Deny all egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress

Then add specific allow policies as needed.

Cilium supports additional features:

apiVersion: cilium.io/v2
kind: NetworkPolicy
metadata:
name: cilium-http-policy
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromEndpoints:
- matchLabels:
app: web
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
path: /api/v1.*

Not all CNI plugins support Network Policies:

CNI PluginIngressEgressNotes
CalicoFull support, rich policy
CiliumFull support, L7 policy
CanalUses Calico policy engine
WeaveFull support
Kube-routerFull support
FlannelNo network policy support
AWS VPC CNIUse Security Groups instead
Terminal window
# Check if network policy is applied
kubectl get networkpolicy
# Describe network policy
kubectl describe networkpolicy <name>
# Check pod network status
kubectl get pods -o wide
# Test connectivity between pods
kubectl exec -it <source-pod> -- wget -O- <target-pod>:<port>
# View NetworkPolicy logs (Cilium)
kubectl logs -n kube-system -l k8s-app=cilium-operator
  1. Start with deny-all: Apply default deny policies first
  2. Use namespace isolation: Separate workloads into namespaces
  3. Implement least privilege: Only allow necessary traffic
  4. Document policies: Comment on why each policy exists
  5. Use labels consistently: Organize pods by tier, environment
  6. Test thoroughly: Verify policies work as expected
  7. Monitor and iterate: Adjust based on observed traffic

Network Policies are essential for:

  • Zero-trust security: Assume breach, deny by default
  • Compliance: Meet regulatory requirements
  • Microsegmentation: Isolate workloads
  • Defense in depth: Layer security controls

Key concepts:

  • podSelector: Selects pods to apply the policy to
  • ingress: Controls incoming traffic
  • egress: Controls outgoing traffic
  • namespaceSelector: Selects pods in specific namespaces
  • ipBlock: Controls traffic to/from CIDR ranges