NetworkPolicy: default deny, then the one path you need
The pattern, every time
- Deny everything in the namespace.
- Re-open exactly one flow, on the port in the task.
- Prove it from inside a Pod.
# 1. deny all ingress AND egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: default-deny, namespace: prod}
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
# 2. allow frontend -> backend:3000, nothing else
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: backend-allow-frontend, namespace: prod}
spec:
podSelector:
matchLabels: {tier: backend}
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels: {tier: frontend}
ports:
- {protocol: TCP, port: 3000}
The trap that fails people: AND vs OR
- Two list items under
from= OR. Either one is allowed. - One item with both
namespaceSelectorandpodSelector= AND. Both must match — this is "frontend pods in namespace X".
# OR: any pod with tier=frontend, anywhere, OR any pod in namespace app=orion
ingress:
- from:
- podSelector: {matchLabels: {tier: frontend}}
- namespaceSelector: {matchLabels: {app: orion}}
# AND: pods with tier=frontend that live in namespace app=orion
ingress:
- from:
- namespaceSelector: {matchLabels: {app: orion}}
podSelector: {matchLabels: {tier: frontend}}
Note the indentation: in the AND case both selectors sit under the same
dash. One extra dash and you have written an OR without noticing.
Egress: DNS must stay open
A default-deny egress policy also kills DNS, and then every single thing in the
namespace fails for a reason unrelated to the task. Always re-open port 53:
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector: {}
ports:
- {protocol: UDP, port: 53}
- {protocol: TCP, port: 53}
Cloud metadata: 169.254.169.254
The task reads "a compromised Pod must not reach the metadata server". The
answer is an ipBlock with an except:
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: ["169.254.169.254/32"]
ipBlock cannot be combined with podSelector in the same to item — that is
an invalid policy, not a subtlety. Keep them in separate list items.
Restrict by CIDR, with one exception
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
except: ["10.0.5.0/24"]
Verify
kubectl get netpol -n prod
kubectl describe netpol backend-allow-frontend -n prod # shows effective rules
# from inside a pod, with a shell (CKA skill):
kubectl exec -n prod deploy/frontend -- wget -qO- --timeout=2 http://backend:3000
kubectl exec -n prod deploy/other -- wget -qO- --timeout=2 http://backend:3000 # must fail
No NetworkPolicy is enforced without a policy-capable CNI. Calico, Cilium,
Weave, Antrea do; flannel alone does not. If your policy "works" but nothing is
blocked, check the CNI before doubting the YAML.
Checklist before you move on
policyTypesnames both directions when the task means both- selectors match the labels that actually exist (
kubectl get pods --show-labels) - the port is the container port, not the Service port
- DNS is still open when you deny egress
- you tested both the allowed and a denied path