The Least-Permissive Policy
Two deployments in two namespaces need to talk, but a default-deny policy is in the way. The exam does not just want them connected. It wants the least permissive policy that does the job. Let's do it the way the CKA expects.
🎥 Watch the video: https://www.youtube.com/watch?v=tgfQlTP2NqI
This is a CKA Services & Networking walkthrough. Every command below is real output from a live cluster, and you can reproduce the whole thing yourself (scripts at the end).
The scenario
A frontend deployment in the frontend namespace must reach a backend deployment in the backend namespace. The backend already has a default-deny ingress policy that you must not touch. Three candidate policies sit in a netpol directory. Your task is to apply only the one that is correct and least permissive.
- frontend namespace must reach the backend namespace
- Backend has a default-deny ingress policy (do not modify it)
- Three candidate policies wait in netpol/
- Apply only the correct, least-permissive one
How NetworkPolicy works
A default-deny policy selects every pod in a namespace and allows no ingress, so all incoming traffic is dropped. You restore traffic by adding an allow policy alongside it. Policies are additive. The least permissive allow names both the source namespace and the source pod label, and a port, so nothing else gets in.
Inspect the lockdown
Start by seeing what is already there. The backend namespace has one policy, default-deny. Describe it: an empty pod selector, so it matches every pod, and an ingress type with no rules, which means deny everything.
$ kubectl -n backend get networkpolicy
NAME POD-SELECTOR AGE
default-deny <none> 78m
$ kubectl -n backend describe networkpolicy default-deny
Name: default-deny
Namespace: backend
Created on: 2026-06-22 09:08:14 -0400 EDT
Labels: <none>
Annotations: <none>
Spec:
PodSelector: <none> (Allowing the specific traffic to all pods in this namespace)
Allowing ingress traffic:
<none> (Selected pods are isolated for ingress connectivity)
Not affecting egress traffic
Policy Types: Ingress
Reproduce symptom
Confirm the symptom from inside the cluster. Exec into the frontend pod and try to fetch the backend service. The request times out. The default-deny is doing exactly its job.
$ kubectl -n frontend exec deploy/frontend -- wget -T 5 -qO- http://backend.backend.svc.cluster.local
wget: download timed out
command terminated with exit code 1
Review the candidates
Now read the three candidates. The first allows the whole frontend namespace, which works but is too open. The third has an empty ingress list, so it allows nothing. The second allows only pods labelled frontend, in the frontend namespace, on port 80. The namespace selector and pod selector share one from entry, so both must match. That is the least permissive policy that still connects them.
$ ls netpol/
netpol1-allow-namespace.yaml
netpol2-allow-frontend.yaml
netpol3-empty-ingress.yaml
$ cat netpol/netpol1-allow-namespace.yaml
...
# the frontend namespace (namespaceSelector only, no podSelector, no port). It would
# work, but it opens the backend to any workload that lands in that namespace.
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
$ cat netpol/netpol3-empty-ingress.yaml
...
metadata:
name: deny-frontend
namespace: backend
# Candidate 3 — WRONG. It selects the backend pods but its ingress list is empty, so
# it allows nothing. Applying this does not restore communication; it is just a second
# deny on top of the existing default-deny.
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress: []
$ cat netpol/netpol2-allow-frontend.yaml
...
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
Apply the fix
Apply the second policy, and only that one. The default-deny stays exactly where it was. The backend namespace now has two policies working together.
$ kubectl apply -f netpol/netpol2-allow-frontend.yaml
networkpolicy.networking.k8s.io/allow-frontend created
$ kubectl -n backend get networkpolicy
NAME POD-SELECTOR AGE
allow-frontend app=backend 0s
default-deny <none> 78m
Verify
Prove it. The same probe from the frontend pod now returns the nginx welcome page, and the describe shows the allow rule scoped to the frontend pod label on port 80. Communication is restored, with the tightest possible rule.
$ kubectl -n frontend exec deploy/frontend -- wget -T 5 -qO- http://backend.backend.svc.cluster.local | grep title
<title>Welcome to nginx!</title>
$ kubectl -n backend describe networkpolicy allow-frontend
Name: allow-frontend
Namespace: backend
Created on: 2026-06-22 10:26:19 -0400 EDT
Labels: <none>
Annotations: <none>
Spec:
PodSelector: app=backend
Allowing ingress traffic:
To Port: 80/TCP
From:
NamespaceSelector: kubernetes.io/metadata.name=frontend
PodSelector: app=frontend
Not affecting egress traffic
Policy Types: Ingress
Exam tips
A few traps to remember. Policies are additive, so you add an allow next to the deny, you never edit the deny. A namespace selector and a pod selector in the same from entry are ANDed; split them into two entries and you accidentally OR them, which is more permissive. Least permissive means name the pod label and the port, not just the namespace. And always verify from a real pod, because a policy that parses can still be wrong.
- Policies are additive: add an allow, never edit the default-deny
- namespaceSelector + podSelector in one from entry = AND (both match)
- Least permissive: pin the pod label and the port, not just the namespace
- Verify from inside a real pod, not just by reading the YAML
Recap
- Default-deny blocks all ingress; allow policies are additive
- Pick the policy scoped to pod label + port (least permissive)
- Leave the existing deny untouched
- Subscribe + dev.to writeup
Reproduce this yourself
The entire scenario is scripted on a throwaway kind cluster (with Calico so policies are actually enforced):
git clone https://github.com/The-Cyber-Sidekick/TCS_CKA_2026_Exam_Scenarios && cd learning/scenarios/scenario3-network-policy
./setup.sh # creates the cluster, installs Calico, and arms it (backend locked down)
# solve it by hand, or:
./solution.sh # apply the answer key (netpol2) and verify connectivity
If this helped, subscribe to The Cyber SideKick on YouTube for more CKA drills, and grab the newsletter at https://thecybersidekick.beehiiv.com.
Top comments (0)