Network Policies

6 minute read

By default, every Pod in Kubernetes can communicate with every other Pod — no restrictions. This is convenient for development but a security risk in production. Network Policies act as firewall rules that control traffic flow between Pods.

Why Network Policies?

Consider a typical application:

┌──────────┐     ┌──────────┐     ┌──────────┐
│ Frontend │────►│ Backend  │────►│ Database │
└──────────┘     └──────────┘     └──────────┘

Without Network Policies, the Frontend can directly access the Database — bypassing the Backend. Network Policies let you enforce that only the Backend can talk to the Database.

Prerequisites

Network Policies require a CNI plugin that supports them:

CNI Plugin Network Policy Support
Calico ✅ Full support
Cilium ✅ Full support + extensions
Weave Net ✅ Supported
Flannel ❌ No support
kubenet ❌ No support

Note: In Minikube, enable Calico: minikube start --cni=calico or use the Calico addon: minikube addons enable calico.

Default Behavior

Without any Network Policies:

  • All ingress (incoming) traffic to a Pod is allowed
  • All egress (outgoing) traffic from a Pod is allowed

Once you apply a Network Policy that selects a Pod, that Pod’s default changes:

  • All traffic NOT explicitly allowed by a policy is denied
  • This is a “default deny” model per-direction

Network Policy YAML

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: backend             # This policy applies to Pods with label app=backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend        # Allow traffic FROM frontend Pods
    ports:
    - protocol: TCP
      port: 8080               # Only on port 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database        # Allow traffic TO database Pods
    ports:
    - protocol: TCP
      port: 5432               # Only on port 5432
  - to:                        # Allow DNS resolution
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Key Fields

Field Description
podSelector Which Pods this policy applies to
policyTypes Ingress, Egress, or both
ingress.from Who can send traffic to these Pods
egress.to Where these Pods can send traffic
ports Which ports are allowed

Common Patterns

1. Default Deny All Ingress

Block all incoming traffic to Pods in a namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {}             # Applies to ALL Pods in the namespace
  policyTypes:
  - Ingress
  # No ingress rules = deny all ingress

2. Default Deny All Egress

Block all outgoing traffic from Pods in a namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  # No egress rules = deny all egress

3. Default Deny All (Both Directions)

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

4. Allow Traffic from Specific Namespace

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
spec:
  podSelector: {}
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          purpose: monitoring    # Allow from namespaces labeled purpose=monitoring
  policyTypes:
  - Ingress

First, label the namespace:

kubectl label namespace monitoring purpose=monitoring

5. Allow Traffic from Specific IP Ranges

ingress:
- from:
  - ipBlock:
      cidr: 10.0.0.0/8
      except:
      - 10.0.1.0/24            # Exclude this subnet

6. Combine Multiple Selectors

ingress:
- from:
  # Rule 1: OR between array elements
  - podSelector:
      matchLabels:
        app: frontend
  - namespaceSelector:
      matchLabels:
        env: staging

  # These are two SEPARATE rules (OR logic)
  # Either Pods with app=frontend OR any Pod from staging namespace

Important: AND vs OR logic:

# OR: Two separate items in the 'from' array
- from:
  - podSelector: {matchLabels: {app: frontend}}     # Rule 1
  - namespaceSelector: {matchLabels: {env: staging}} # Rule 2
  # Allows: frontend pods (any namespace) OR any pod from staging namespace

# AND: Both selectors in the SAME item
- from:
  - podSelector: {matchLabels: {app: frontend}}
    namespaceSelector: {matchLabels: {env: staging}}
  # Allows: frontend pods ONLY from staging namespace

Hands-On: Securing a 3-Tier Application

Let’s create a frontend → backend → database setup with Network Policies:

# application.yaml
# Database
apiVersion: apps/v1
kind: Deployment
metadata:
  name: database
  labels:
    app: database
spec:
  replicas: 1
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
        tier: data
    spec:
      containers:
      - name: db
        image: hashicorp/http-echo
        args: ["-text=Database Response", "-listen=:5432"]
        ports:
        - containerPort: 5432
---
apiVersion: v1
kind: Service
metadata:
  name: database
spec:
  selector:
    app: database
  ports:
  - port: 5432
---
# Backend
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
        tier: api
    spec:
      containers:
      - name: api
        image: hashicorp/http-echo
        args: ["-text=Backend Response", "-listen=:8080"]
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: backend
spec:
  selector:
    app: backend
  ports:
  - port: 8080
---
# Frontend
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
        tier: web
    spec:
      containers:
      - name: web
        image: hashicorp/http-echo
        args: ["-text=Frontend Response", "-listen=:80"]
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  ports:
  - port: 80

Now apply Network Policies:

# network-policies.yaml

# 1. Default deny all traffic in the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
# 2. Frontend: allow ingress from anywhere, egress to backend + DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - {}                         # Allow all ingress
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - port: 8080
  - to:
    - namespaceSelector: {}
    ports:
    - port: 53
      protocol: UDP
---
# 3. Backend: allow ingress from frontend, egress to database + DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - port: 5432
  - to:
    - namespaceSelector: {}
    ports:
    - port: 53
      protocol: UDP
---
# 4. Database: allow ingress from backend only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
spec:
  podSelector:
    matchLabels:
      app: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - port: 5432
kubectl apply -f application.yaml
kubectl apply -f network-policies.yaml

# Test: Frontend → Backend (should work)
kubectl exec deployment/frontend -- wget -qO- http://backend:8080

# Test: Frontend → Database (should FAIL/timeout)
kubectl exec deployment/frontend -- wget -qO- --timeout=3 http://database:5432

# Test: Backend → Database (should work)
kubectl exec deployment/backend -- wget -qO- http://database:5432

Debugging Network Policies

# List Network Policies
kubectl get networkpolicies
# or: kubectl get netpol

# Describe a policy
kubectl describe netpol backend-policy

# Test connectivity
kubectl exec -it <pod-name> -- wget -qO- --timeout=3 http://<service>:<port>

# Check if a CNI supports network policies
kubectl get pods -n kube-system | grep -i calico

Best Practices

  1. Start with default-deny in every namespace, then allow specific traffic
  2. Always allow DNS egress (port 53 UDP/TCP) — Pods need DNS to resolve Service names
  3. Use labels consistently — Network Policies rely on label selectors
  4. Test policies thoroughly — Misconfigured policies can break your application
  5. Document your network architecture — Track which Pods can communicate

Summary

  • Network Policies are Kubernetes’ built-in firewall
  • They require a CNI that supports them (Calico, Cilium)
  • Default behavior: all traffic allowed (no policies)
  • Once a policy selects a Pod, unmatched traffic is denied
  • Use default-deny + allow-list pattern for security
  • Always allow DNS egress (port 53) for Service discovery

What’s Next?

Network Policies control which Pods can communicate. But who can access the Kubernetes API itself? In the next chapter, we’ll learn about RBAC (Role-Based Access Control) — controlling who can do what in your cluster.

Next: RBAC →

Updated: