Overview

Traces what happens when a NetworkPolicy object is created — from the API server write through CNI plugin programming to the enforcement of packet-level filtering on nodes.

NetworkPolicy Enforcement Architecture

NetworkPolicy is a Kubernetes API object — it expresses INTENT.
Enforcement is the CNI plugin's responsibility — not kube-proxy.

                API Server
                    │ NetworkPolicy Created
                    ▼
             CNI Plugin controller
      (Cilium operator / Calico controller / etc.)
                    │ watches NetworkPolicy objects
                    ▼
             Translates to CNI datapath rules
      ┌─────────────────────────────────────────────┐
      │ Cilium: BPF programs loaded per endpoint    │
      │ Calico: iptables / eBPF rules per node      │
      │ Flannel: no NetworkPolicy support (!)       │
      └─────────────────────────────────────────────┘
                    │
                    ▼
             Packet filtering at veth pair
             (evaluated per packet at kernel level)

Cilium NetworkPolicy Enforcement Flow

kubectl          API Server      etcd      Cilium Operator   Cilium Agent (node)   Linux Kernel
   │                 │             │             │                  │                   │
   │─POST NP───────►│             │             │                  │                   │
   │ (NetworkPolicy) │             │             │                  │                   │
   │                 │──WRITE─────►│             │                  │                   │
   │◄─ 201 Created──│             │             │                  │                   │
   │                 │             │             │                  │                   │
   │                 │──WATCH NP ─────────────►  │                  │                   │
   │                 │  (NetworkPolicy Added)     │                  │                   │
   │                 │                           │                  │                   │
   │                 │                    [Cilium Operator          │                   │
   │                 │                     translates NP to         │                   │
   │                 │                     CiliumNetworkPolicy      │                   │
   │                 │                     (CRD)]                   │                   │
   │                 │◄── CREATE CNP ─────────── │                  │                   │
   │                 │──WRITE CNP ──────────────►│(etcd)            │                   │
   │                 │                           │                  │                   │
   │                 │──WATCH CNP ──────────────────────────────────►                   │
   │                 │  (CiliumNetworkPolicy Added)                 │                   │
   │                 │                                              │                   │
   │                 │                                     [Cilium agent on each node]  │
   │                 │                                     fetches endpoint identities  │
   │                 │                                     compiles BPF program:        │
   │                 │                                     ┌─────────────────────────┐  │
   │                 │                                     │ allow ingress from:     │  │
   │                 │                                     │   identity=frontend,    │  │
   │                 │                                     │   port=8080             │  │
   │                 │                                     │ deny all other ingress  │  │
   │                 │                                     └─────────────────────────┘  │
   │                 │                                              │                   │
   │                 │                                              │──bpf_load────────►│
   │                 │                                              │  (tc filter       │
   │                 │                                              │   attached to     │
   │                 │                                              │   veth pair)      │
   │                 │                                              │◄── loaded ───────│
   │                 │                                              │                   │
   Policy enforced — all ingress/egress packets evaluated by BPF at kernel level

How Cilium Identifies Pods (Security Identities)

Traditional NetworkPolicy uses IP addresses → breaks with pod restarts.

Cilium uses security identities based on labels:
  Pod labels → identity number (e.g., identity=12345 for app=frontend,env=prod)
  Identity is embedded in packet metadata (via BPF map lookup)

Flow:
  1. Pod created with labels {app:frontend, env:prod}
  2. Cilium agent assigns identity=12345 to this label set
  3. BPF program on every node stores: identity=12345 → policy allows
  4. When frontend pod sends packet to payments-api pod:
     - BPF intercepts at veth of sender (egress)
     - Looks up sender's identity (12345 = frontend)
     - Checks policy map: is 12345 allowed to reach payments-api on port 8080?
     - Allow or DROP

iptables-based CNI (Calico without eBPF)

Calico translates NetworkPolicy to iptables chains:

iptables -L | grep cali
  KUBE-FORWARD  →  cali-INPUT  →  cali-from-hep-...  →  cali-to-hep-...
  cali-tw-payments-api-xxx  (traffic to payments-api pod)
    → cali-pi-default.payments-isolation  (policy for namespace)
      → match: src=10.0.0.5 (frontend pod IP)  → ACCEPT
      → default: DROP

Problem: rules must be updated every time a pod IP changes
(pod restart → new IP → all related iptables rules must be rebuilt)

NetworkPolicy — Default Deny Pattern

# Step 1: Default deny all ingress in namespace
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

---
# Step 2: Allow only what payments-api needs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payments-api-allow
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payments-api
  policyTypes: [Ingress, Egress]

  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: nginx-ingress
    ports:
    - protocol: TCP
      port: 8080

  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  # DNS egress (required for all pods)
  - ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Verifying Policy Enforcement

# Test connectivity after policy applies
kubectl run test --image=nicolaka/netshoot --rm -it -- \
  nc -zv payments-api.production.svc.cluster.local 8080
# Should succeed from frontend pod, fail from unrelated pod

# Cilium: check BPF policy for a specific pod
CILIUM_POD=$(kubectl get pod -n kube-system -l k8s-app=cilium \
  --field-selector spec.nodeName=$(kubectl get pod payments-api-xxx \
    -n production -o jsonpath='{.spec.nodeName}') \
  -o jsonpath='{.items[0].metadata.name}')

kubectl exec -n kube-system $CILIUM_POD -- \
  cilium endpoint list | grep payments-api

kubectl exec -n kube-system $CILIUM_POD -- \
  cilium policy get

# Hubble — observe dropped packets
hubble observe --namespace production --verdict DROPPED

# Trace policy decision for a specific flow
cilium policy trace \
  --src-k8s-pod production/frontend-xxx \
  --dst-k8s-pod production/payments-api-xxx \
  --dport 8080

NetworkPolicy Propagation Timing

kubectl apply NetworkPolicy
  → API server write: ~10ms
  → Cilium operator translation: ~100ms
  → Cilium agent BPF reload per node: ~50-200ms per node
  → BPF program loaded: policy enforced immediately for new packets

Total propagation to all nodes: typically 500ms–3s for large clusters

Note: in-flight TCP connections are NOT immediately dropped when policy
is tightened (BPF evaluates per new connection, not per packet for
established TCP). Connections established before policy apply continue
until they close. For immediate enforcement of existing connections,
use Cilium's policy enforcement: always mode.