Chapter 28 of 47 · Services and discovery

Network Policies: a firewall for Pods

By default every Pod can talk to every other Pod in the whole cluster. If one Pod is hacked, the attacker can reach your database directly. A NetworkPolicy lets you say, using labels, exactly who may talk to whom. This is called micro-segmentation: many tiny walls instead of one big one.

Four rules to understand

  1. No policy = allow everything. A Pod that no policy selects accepts all traffic.
  2. Selected = default deny. Once any policy selects a Pod for ingress (incoming), only traffic that some policy allows gets in. Same for egress (outgoing).
  3. Policies only add. There are no "deny" rules. Several policies on one Pod simply combine their allows. Order does not matter.
  4. Your network plugin must support them. Calico and Cilium do. Some simple CNIs (plain Flannel) accept the YAML and silently enforce nothing.

Blocked traffic is dropped, not refused, so the caller sees a timeout (chapter 24).

Anatomy of a policy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-posts
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: posts
      ports:
        - protocol: TCP
          port: 5432

podSelector (top level)

Which Pods this policy protects: here every Pod labelled app=db in this namespace. podSelector: {} (empty) means all Pods in the namespace.

Read it as a sentence: "db Pods accept incoming traffic only from posts Pods, only on TCP 5432."

Try it: who can reach whom

Turn policies on one at a time and watch the grid. Click a cell for the reason. Start with default-deny-ingress: everything in default goes dark. Then add allows back.

Recipes you will reuse

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}          # every Pod in the namespace
  policyTypes: [Ingress, Egress]
  # no rules = nothing allowed

The usual starting point: lock everything, then open exactly what is needed. Apply it per namespace.

The AND/OR trap

One dash makes a big difference:

AND: one item

from:
  - namespaceSelector:
      matchLabels: { team: a }
    podSelector:
      matchLabels: { app: api }

Pods labelled app=api that are in team-a namespaces.

OR: two items

from:
  - namespaceSelector:
      matchLabels: { team: a }
  - podSelector:
      matchLabels: { app: api }

Any Pod in team-a namespaces, or app=api Pods in this namespace. Much wider!

Egress and the DNS trap

Egress policies control where a Pod may send. The trap: as soon as you restrict egress, DNS lookups (to CoreDNS in kube-system) are blocked too, and every call fails with ENOTFOUND, even to allowed Pods. Always allow DNS:

spec:
  podSelector:
    matchLabels: { app: posts }
  policyTypes: [Egress]
  egress:
    - to:                                   # 1. DNS
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
          podSelector:
            matchLabels: { k8s-app: kube-dns }
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }
    - to:                                   # 2. the event bus
        - podSelector:
            matchLabels: { app: event-bus }
      ports:
        - { protocol: TCP, port: 4005 }

Locking down the blog app

PodShould accept traffic from
client, posts, comments, queryThe ingress controller (browser traffic) and the event bus
event-busposts, comments, query, moderation
moderationevent-bus only
# moderation: only the event bus
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: moderation-from-bus
spec:
  podSelector: { matchLabels: { app: moderation } }
  policyTypes: [Ingress]
  ingress:
    - from: [ { podSelector: { matchLabels: { app: event-bus } } } ]
      ports: [ { port: 4003 } ]
---
# event bus: only the four services
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: bus-from-services
spec:
  podSelector: { matchLabels: { app: event-bus } }
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchExpressions:
              - { key: app, operator: In, values: [posts, comments, query, moderation] }
      ports: [ { port: 4005 } ]

Policies select by label, so good, consistent labels (chapter 9) are what make this possible.

Testing a policy

# pretend to be a posts Pod: give the test Pod the same label
$ kubectl run test --rm -it --image=busybox:1.36 --labels=app=posts -- \
    wget -qO- --timeout=3 http://moderation-srv:4003
wget: download timed out          ← blocked, as intended

$ kubectl run test --rm -it --image=busybox:1.36 --labels=app=event-bus -- \
    wget -qO- --timeout=3 http://moderation-srv:4003
OK                                ← allowed

$ kubectl get networkpolicy
$ kubectl describe networkpolicy moderation-from-bus

Recap

Network Policies: common questions

Are Kubernetes NetworkPolicies deny or allow rules?

Allow rules only. A Pod not selected by any policy accepts everything; once selected, only what some policy allows gets through. Policies add up and never conflict.