DEV Community

The Cyber Sidekick
The Cyber Sidekick

Posted on

CKA Exam 2026 Scenario 3 - Pick the least-permissive NetworkPolicy to connect two namespaces (CKA)

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)