Kubernetes Network Policies: Microsegmentation and East-West Traffic Control

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:

  1. Every pod gets its own IP address
  2. Every pod can communicate with every other pod without NAT
  3. 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.

Scroll to Top