Network policies
By default, every pod in a cluster can reach every other pod, in any namespace. The flat network is a deliberate simplification — and a poor security posture the moment you run more than one team's workloads.
NetworkPolicy narrows it.
Selecting turns on default-deny
A NetworkPolicy does not add rules to a permissive baseline. The moment any policy selects a pod, that pod switches to default-deny for the directions the policy mentions, and only the listed rules are permitted.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payments-ingress
spec:
podSelector:
matchLabels:
app: payments
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: checkout
ports:
- port: 8080
Payments now accepts traffic from checkout on 8080 and nothing else. Egress is
untouched, because policyTypes does not list it.
Namespace selectors need labels
namespaceSelector matches on namespace labels, not names. Namespaces have no
useful labels by default beyond kubernetes.io/metadata.name, which the control
plane sets automatically:
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
Combining namespaceSelector and podSelector in a single from entry is an AND.
Listing them as two entries is an OR. One character of YAML indentation separates
"Prometheus pods in the monitoring namespace" from "anything in monitoring, plus
Prometheus pods anywhere".
The CNI has to implement it
Like Ingress, the object is only a declaration. Calico, Cilium and Antrea enforce policies. Flannel, on its own, does not.
A sensible starting point
Deny everything inbound in a namespace, then open specific paths:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes: [Ingress]
An empty podSelector selects every pod in the namespace. With no ingress rules,
nothing is allowed in. Layer permissive policies on top per service — policies are
additive, so any rule that allows traffic wins over the baseline denial.