CKSKubernetes 1.35

Lessons

NetworkPolicy: default deny, then the one path you need

updated 2026-10-22 by seed

The pattern, every time

  1. Deny everything in the namespace.
  2. Re-open exactly one flow, on the port in the task.
  3. 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 namespaceSelector and podSelector = 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

  • policyTypes names 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