Kubernetes Network Policies zijn een van die features waarvan iedereen weet dat ze het moeten gebruiken maar weinigen echt begrijpen. De YAML ziet er intimiderend uit, het gedrag is niet-intuïtief, en het mentale model kost tijd om te ontwikkelen.

Ik heb uren besteed aan het debuggen van policies die “zouden moeten werken” maar het niet deden. Laat me je die pijn besparen met een visuele benadering van Network Policies.

De Default: Alles Praat met Alles

Standaard staat Kubernetes alle pod-naar-pod communicatie toe. Elke pod kan elke andere pod bereiken over elke namespace. Dit is handig om te starten maar verschrikkelijk voor security.

flowchart TD
    subgraph cluster["Cluster"]
        Pod1["Pod"] <--> Pod2["Pod"]
        Pod2 <--> Pod3["Pod"]
        Pod3 <--> Pod4["Pod"]
        Pod1 <--> Pod3
        Pod1 <--> Pod4
        Pod2 <--> Pod4
    end
    note["Iedereen kan praten"]

Als een aanvaller één pod compromitteert, kan deze elke andere pod in het cluster bereiken. Dit schendt zero trust principes — we moeten breach aannemen en blast radius beperken.

Network Policies: De Firewall voor Pods

Een NetworkPolicy is een specificatie van hoe pods mogen communiceren. Zie het als een firewall regel die van toepassing is op groepen pods.

Kernconcepten:

  • Ingress: Inkomend verkeer naar een pod
  • Egress: Uitgaand verkeer van een pod
  • Selectors: Hoe pods te identificeren (via labels)
  • Policy Types: Of je ingress, egress, of beide controleert

De Belangrijkste Regel: Default Deny

De eerste policy die je moet toepassen op elke namespace is default deny. Dit blokkeert al het verkeer, dan sta je expliciet toe wat nodig is.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}  # Leeg = alle pods
  policyTypes:
    - Ingress
    - Egress
flowchart TD
    subgraph production["production namespace"]
        Pod1["Pod"] x--x Pod2["Pod"]
        Pod2 x--x Pod3["Pod"]
        Pod3 x--x Pod4["Pod"]
    end
    note["Al het verkeer standaard geblokkeerd"]

Nu kan niets communiceren. We bouwen op vanaf nul.

Ingress Toestaan: Wie Mag NAAR Deze Pod Praten?

Stel we hebben een frontend die verkeer moet ontvangen:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-ingress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
        - podSelector:
            matchLabels:
              app: ingress-controller
      ports:
        - protocol: TCP
          port: 8080
flowchart LR
    subgraph ingress-nginx["ingress-nginx ns"]
        IC["ingress controller"]
    end
    subgraph production["production ns"]
        FE["frontend<br/>app:frontend"]
        Other["andere pods"]
    end
    IC -->|"✓"| FE
    Other x--x|"✗"| FE

De frontend kan alleen verkeer ontvangen van de ingress controller op poort 8080.

Egress Toestaan: Waar Mag Deze Pod NAAR Praten?

De frontend moet praten met de backend API:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: backend
      ports:
        - protocol: TCP
          port: 3000
    - to:  # DNS toestaan
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
flowchart TD
    subgraph production["production namespace"]
        FE["frontend"] -->|"✓"| BE["backend"]
        BE x--x|"✗"| DB["database"]
        FE -->|"✓ alleen DNS"| DNS["kube-dns"]
    end

De frontend kan alleen de backend bereiken (poort 3000) en DNS. Het kan niet direct bij de database.

Het Complete Three-Tier Voorbeeld

Hier is een realistische setup: ingress → frontend → backend → database.

# Frontend policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress
      ports:
        - port: 80
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - port: 8080
    - to:  # DNS
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP
---
# Backend policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: database
      ports:
        - port: 5432
    - to:  # DNS
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - port: 53
          protocol: UDP
---
# Database policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - port: 5432
  egress: []  # Database hoeft geen verbindingen te initiëren
flowchart LR
    Internet --> Ingress
    Ingress --> Frontend
    Frontend --> Backend
    Backend --> Database
    Frontend -.->|"Alleen DNS"| DNS["kube-dns"]
    Backend -.->|"Alleen DNS"| DNS

Elke tier kan alleen praten met de aangrenzende tier. De frontend kan de database niet bereiken. De database kan het internet niet bereiken.

Veelvoorkomende Valkuilen

Valkuil 1: Vergeet DNS Niet

Je pods hebben DNS nodig om service namen te resolven. Sta altijd egress naar kube-dns toe:

egress:
  - to:
      - namespaceSelector: {}
        podSelector:
          matchLabels:
            k8s-app: kube-dns
    ports:
      - port: 53
        protocol: UDP

Zonder dit kunnen je pods backend.production.svc.cluster.local niet resolven.

Valkuil 2: Policies Zijn Additief

Meerdere policies op dezelfde pod worden gecombineerd met OR logica. Als policy A verkeer van X toestaat, en policy B verkeer van Y toestaat, ontvangt de pod verkeer van zowel X als Y.

Je kunt geen “deny” policy maken die een “allow” policy overschrijft. De enige manier om te denyen is niet toestaan.

Valkuil 3: Lege Selector Betekent “Alles”

podSelector: {}  # Alle pods in deze namespace
namespaceSelector: {}  # Alle namespaces

Dit vangt veel mensen. Een lege selector betekent niet “niets” — het betekent “alles.”

Valkuil 4: Policies Beïnvloeden Alleen Geselecteerde Pods

Een NetworkPolicy beïnvloedt alleen pods die matchen met zijn podSelector. Pods zonder policies die ze selecteren hebben geen restricties.

Dit is waarom default-deny belangrijk is: het zorgt dat elke pod minstens één policy heeft.

Network Policies Testen

Test altijd je policies. Hier is een snelle manier:

# Maak een test pod
kubectl run test-pod --image=busybox --rm -it -- sh

# Probeer een andere service te bereiken
wget -qO- --timeout=2 http://backend.production:8080/health

# Probeer de database direct te bereiken (zou moeten falen)
nc -zv database.production 5432

Of gebruik een tool zoals netshoot:

kubectl run netshoot --image=nicolaka/netshoot -it --rm -- bash

CNI Vereisten

Niet alle CNIs ondersteunen Network Policies. Je hebt een CNI nodig die de NetworkPolicy API implementeert:

  • Calico: Volledige ondersteuning, plus uitbreidingen met meer features
  • Cilium: Volledige ondersteuning, plus L7 policies
  • Weave: Volledige ondersteuning
  • Flannel: Geen ondersteuning (heeft Calico erbovenop nodig)

Check je CNI voordat je op Network Policies vertrouwt.

Voorbij Basis Policies

Voor meer geavanceerde use cases, overweeg:

  • Cilium Network Policies: L7 (HTTP) filtering, DNS-based policies
  • Calico Network Policies: Host bescherming, global policies
  • Service Mesh: Istio/Linkerd voor mutual TLS en L7 policies

De Kubernetes NetworkPolicy API is opzettelijk simpel. Complexere vereisten hebben vaak CNI-specifieke uitbreidingen nodig.

Mijn Aanbeveling

Start simpel:

  1. Pas default-deny toe op elke namespace
  2. Voeg policies toe die alleen noodzakelijke communicatie toestaan
  3. Test grondig voordat je naar productie gaat
  4. Gebruik tools zoals Cilium’s policy editor om te visualiseren

Network Policies zijn een van de beste security verbeteringen die je kunt maken in Kubernetes. Ze zijn gratis (geen extra infrastructuur), ze zijn declaratief (infrastructure as code), en ze beperken blast radius dramatisch.

Het mentale model kost oefening, maar zodra het klikt vraag je je af hoe je ooit een cluster draaide zonder.


Network Policies implementeren het principe van least privilege op netwerkniveau. Elke connectie die niet hoeft te bestaan zou niet moeten bestaan.