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
- No policy = allow everything. A Pod that no policy selects accepts all traffic.
- Selected = default deny. Once any policy selects a Pod for ingress (incoming), only traffic that some policy allows gets in. Same for egress (outgoing).
- Policies only add. There are no "deny" rules. Several policies on one Pod simply combine their allows. Order does not matter.
- 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.
policyTypes
Which directions this policy controls: Ingress (incoming), Egress (outgoing), or both. Listing a type turns on default deny for it.
ingress.from
Who may come in. Sources can be chosen by podSelector (Pods in the same namespace), namespaceSelector (whole namespaces), or ipBlock (IP ranges, e.g. outside the cluster).
ports
Which ports the allowed sources may use. Leave it out to allow all ports.
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 allowedThe usual starting point: lock everything, then open exactly what is needed. Apply it per namespace.
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- from:
- podSelector: {} # any Pod in THIS namespacePods in the namespace may talk to each other; other namespaces may not reach in.
spec:
podSelector:
matchLabels: { app: posts }
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoringLet Prometheus in the monitoring namespace scrape posts. Every namespace automatically has the label kubernetes.io/metadata.name.
spec:
podSelector:
matchLabels: { app: client }
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/16 # the company VPN (chapter 24, CIDR)
except: [10.0.5.0/24]For traffic from outside the cluster. Careful: after a load balancer, the source IP may be the node's, not the user's.
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
| Pod | Should accept traffic from |
|---|---|
| client, posts, comments, query | The ingress controller (browser traffic) and the event bus |
| event-bus | posts, comments, query, moderation |
| moderation | event-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
- Default: everything can reach everything. Selected by a policy: default deny for that direction.
podSelectorat the top = who is protected;from/to= who is allowed.- Sources and destinations:
podSelector,namespaceSelector,ipBlock, plusports. - Policies only add allows. One list item = AND, separate items = OR.
- Restricting egress? Allow DNS on port 53, or nothing resolves.
- Requires a CNI that enforces policies (Calico, Cilium).
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.