Back to Blog

Kubernetes Network Policies for the CKS Exam: Default-Deny, Namespace Isolation & Egress Control

A security-first guide to Kubernetes NetworkPolicy for the CKS exam — why pods are non-isolated by default, how to apply a default-deny-all baseline, restrict ingress and egress with pod and namespace selectors, block the cloud metadata endpoint at 169.254.169.254, and prove your rules work. Includes CNI requirements, DNS gotchas, and exam-speed manifests.

By Sailor Team , August 12, 2026

On the CKS exam, network policy is where a lot of candidates quietly lose points. The mechanics look simple — a NetworkPolicy is just a few selectors and rules — but the exam doesn’t test whether you can copy a manifest. It tests whether you understand the security model: that Kubernetes pods accept traffic from anywhere until you say otherwise, that a policy is additive, and that locking down egress is what actually stops a compromised pod from phoning home or stealing cloud credentials.

This guide takes the security-engineer view the CKS rewards. If you want the pure mechanics of ingress and egress selectors first — the CKA-level “how do the fields fit together” walkthrough — read the Kubernetes NetworkPolicy for the CKA exam guide, then come back here for the hardening angle. This article assumes you know the syntax and focuses on what a hardened cluster’s policies look like and why. It sits inside the CKS Cluster Hardening and Minimize Microservice Vulnerabilities domains — the same territory covered in the minimize microservice vulnerabilities guide. For the full exam picture, start with the CKS exam guide for 2026 and sequence your prep with the CKS study plan.

The Default That Fails Open

Here is the single most important sentence for the exam: a pod with no NetworkPolicy selecting it accepts all traffic, from anywhere, on every port. Kubernetes networking fails open. Every pod can reach every other pod, across namespaces, by default. There is no implicit firewall.

That default is the attack surface. If an attacker compromises one pod — an unpatched web front end, a vulnerable dependency — they land in a flat network where they can scan and connect to every database, cache, and internal API in the cluster. Lateral movement is trivial. NetworkPolicy is how you replace “fail open” with “least privilege on the wire.”

A second rule that trips people up: the moment a NetworkPolicy selects a pod for a direction (ingress or egress), that pod switches to deny-by-default for that direction, and only the listed rules are allowed. Policies are purely additive — there is no deny verb, no ordering, no priority. You allow things; everything you didn’t allow is denied once at least one policy applies. Understanding this “select to isolate, then allow back” behaviour is the whole game.

Prerequisite: Your CNI Must Enforce Policy

NetworkPolicy is an API that the API server will happily accept — but it does nothing unless your CNI plugin enforces it. This is a favourite exam trap: you write a perfect default-deny policy, traffic still flows, and you assume the manifest is wrong. It isn’t; the CNI just doesn’t implement policy.

CNI pluginEnforces NetworkPolicy?
CalicoYes
CiliumYes
Weave NetYes
AntreaYes
Flannel (alone)No
kubenetNo

On the CKS exam the cluster uses a policy-capable CNI (typically Calico or Cilium), so you can trust that policies take effect. In the real world, confirm enforcement before you rely on it. A quick sanity check is to apply a default-deny and verify a previously-working connection now times out.

Step One: The Default-Deny-All Baseline

The correct hardening pattern is deny everything, then allow what each workload genuinely needs. You establish that baseline per namespace with a policy that selects every pod (podSelector: {}) and lists no allow rules for both directions:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}            # selects every pod in the namespace
  policyTypes:
    - Ingress
    - Egress
  # no ingress/egress rules => nothing is allowed

podSelector: {} matches all pods in payments. Naming both Ingress and Egress in policyTypes with no rules means every pod is now isolated in both directions. Apply this first to any namespace you’re hardening, then layer specific allow-policies on top. If you only default-deny ingress, a compromised pod can still open outbound connections — which is exactly how data exfiltration and credential theft happen. For the exam and for real security, deny egress too.

A common softer variant denies ingress but leaves egress open:

spec:
  podSelector: {}
  policyTypes:
    - Ingress

Know the difference cold: the version above isolates inbound only. If a question says “ensure pods cannot make outbound connections except to X,” you must include Egress in policyTypes.

Step Two: Allow Only What the Workload Needs (Ingress)

With default-deny in place, add targeted allow policies. Suppose an api pod should receive traffic only from the web pods in the same namespace, on port 8080:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - protocol: TCP
          port: 8080

Because a policy already isolated these pods, this one adds back a single allowed source. Anything not matching app: web on TCP/8080 is still denied.

The namespaceSelector vs podSelector trap

The most-tested subtlety in NetworkPolicy is how selectors combine inside a single from entry:

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            team: frontend
        podSelector:
          matchLabels:
            app: web

Two selectors in one list item are ANDed: traffic must come from a pod labelled app: web that lives in a namespace labelled team: frontend. Contrast that with two separate list items:

ingress:
  - from:
      - namespaceSelector:
          matchLabels:
            team: frontend
      - podSelector:
          matchLabels:
            app: web

These are ORed: allow any pod from team: frontend namespaces, or any pod named app: web in the policy’s own namespace. That single dash changes the security boundary entirely. On the exam, read the indentation carefully — this is where wrong answers hide.

Remember too that a bare podSelector (without a namespaceSelector) only matches pods in the same namespace as the policy. To allow cross-namespace traffic you must include a namespaceSelector.

Step Three: Egress Control — The CKS Differentiator

Ingress rules protect your services. Egress rules protect the world from your compromised pod. This is where CKS content diverges sharply from CKAD/CKA: reducing the blast radius of a breach.

The problem is that if you default-deny egress, you also break DNS, because pods resolve names through CoreDNS on UDP/TCP 53. A default-deny-egress policy with no DNS allowance makes every pod unable to resolve anything.svc.cluster.local, and applications fail in confusing ways. So the canonical hardened egress baseline allows DNS and nothing else:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

namespaceSelector: {} with a podSelector matches the CoreDNS pods in whatever namespace they run (kube-system). Now add exactly the egress destinations a workload legitimately needs — for example, letting api reach a database pod:

  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

Everything else outbound — arbitrary internet hosts, other namespaces, internal admin endpoints — stays blocked. That is what stops a breached container from downloading a second-stage payload or exfiltrating data.

Blocking the Cloud Metadata Endpoint

This deserves its own section because it is a classic CKS scenario and a real-world compromise path. On AWS, GCP, and Azure the node’s instance metadata service lives at the link-local address 169.254.169.254. A pod that can reach it may be able to read the node’s IAM/instance credentials and escalate straight out of the cluster into the cloud account.

You block it with an egress policy that allows everything the pod needs except that address, using an ipBlock with an except:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-metadata-egress
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 169.254.169.254/32
      ports:
        - protocol: TCP
          port: 443

The cidr: 0.0.0.0/0 with except: 169.254.169.254/32 says “you may egress to any IP on 443 except the metadata endpoint.” In a real hardened cluster you’d narrow the allowed CIDR far more tightly and keep the DNS allowance from earlier — but knowing the ipBlock/except pattern for the metadata address is exam-critical. Note that ipBlock matches on IP, so it’s the right tool for infrastructure endpoints that have no pod or namespace labels.

Proving It Works: Test, Don’t Trust

Never submit a policy task without verifying it. The exam gives you a running cluster; use it. The fastest test is an ephemeral pod and a timeout-bounded connection attempt:

# Should SUCCEED: allowed path
kubectl run tester --rm -it --image=busybox:1.36 --labels="app=web" \
  -n payments --restart=Never -- \
  wget -qO- --timeout=3 http://api:8080

# Should FAIL (time out): blocked path
kubectl run intruder --rm -it --image=busybox:1.36 --labels="app=evil" \
  -n payments --restart=Never -- \
  wget -qO- --timeout=3 http://api:8080

# Should FAIL: metadata endpoint blocked
kubectl run tester --rm -it --image=busybox:1.36 --labels="app=api" \
  -n payments --restart=Never -- \
  wget -qO- --timeout=3 http://169.254.169.254/latest/meta-data/

A blocked connection hangs and times out — it does not return “connection refused.” That timeout is your proof the policy denied the traffic. Also inspect what’s actually applied:

kubectl get networkpolicy -n payments
kubectl describe networkpolicy default-deny-all -n payments

describe prints the resolved pod selector and the allowed peers, which is the quickest way to catch a selector that matches the wrong pods.

Common Mistakes That Cost Points

MistakeWhy it failsFix
Default-deny ingress onlyCompromised pod can still exfiltrateAdd Egress to policyTypes
Egress deny with no DNS ruleName resolution breaks; app errorsAllow UDP/TCP 53 to CoreDNS
podSelector without namespaceSelector for cross-nsOnly matches same namespaceAdd namespaceSelector
Selectors in one from item when you meant ORAND is far stricter than intendedSplit into separate list items
Assuming policy works on FlannelCNI doesn’t enforce itUse a policy-capable CNI
Forgetting ipBlock for metadata/IP endpointsLabel selectors can’t match an IPUse ipBlock with except

A Realistic Hardening Sequence

Put together, hardening a namespace on the exam looks like this, in order:

  1. Apply default-deny-all (both directions) to the namespace.
  2. Add allow-dns-egress so name resolution keeps working.
  3. Add tight ingress allow-policies per service (who may connect in).
  4. Add tight egress allow-policies per workload (what it may connect out to).
  5. Add deny-metadata-egress where pods run with any node-level cloud identity.
  6. Test every allowed and every denied path with ephemeral pods.

This mirrors real zero-trust network design and is exactly the muscle memory the exam rewards. If you also need the cluster-level controls that sit around these policies — API server flags, RBAC, CIS remediation — the CKS cluster setup and hardening guide covers that layer.

Practicing Against Live Clusters

Reading manifests is not the same as writing them under a 15-minute-per-task clock against a cluster you didn’t build. The fastest way to make default-deny, DNS carve-outs, and metadata blocks automatic is to break and repair real clusters repeatedly. Sailor.sh’s Certified Kubernetes Security Specialist (CKS) Mock Exam Bundle gives you full-length, browser-based performance exams against live clusters, with tasks in exactly this style — apply a default-deny baseline, allow a single source, block the metadata endpoint, and prove it — so the workflow is second nature by exam day.

Frequently Asked Questions

Do NetworkPolicies apply to traffic that never leaves the node?

Yes. NetworkPolicy is about pod-to-pod and pod-to-endpoint traffic regardless of whether the peers are on the same node. Enforcement is handled by the CNI’s dataplane, which applies rules for local traffic too.

Why does my app still work after I applied a default-deny policy?

Most often the CNI doesn’t enforce NetworkPolicy (e.g. plain Flannel), or the policy’s podSelector doesn’t actually match your pods. Run kubectl describe networkpolicy to see the resolved selector, and confirm your CNI supports policy enforcement.

How do I allow a pod to reach the internet but not the cloud metadata endpoint?

Use an egress rule with ipBlock: { cidr: 0.0.0.0/0, except: [169.254.169.254/32] }. Keep a separate egress rule allowing DNS to CoreDNS, otherwise name resolution breaks.

Are NetworkPolicies namespaced?

Yes. A NetworkPolicy only affects pods in its own namespace, and a bare podSelector peer matches only same-namespace pods. Cross-namespace allow rules require a namespaceSelector. To harden a whole cluster you apply baseline policies per namespace.

Can I express “deny” explicitly in a NetworkPolicy?

No. There is no deny rule. You isolate pods by selecting them, and everything not explicitly allowed is denied. “Default deny” is achieved by selecting pods with no allow rules for a direction.

Does egress filtering break Services and DNS?

It can. Blocking egress without allowing UDP/TCP port 53 to CoreDNS breaks name resolution, and blocking egress to a Service’s backend pods breaks the connection. Always allow DNS first, then the specific backends each workload needs.

Key Takeaways

  • Pods fail open: with no policy, all ingress and egress is allowed.
  • Selecting a pod with a policy flips it to deny-by-default for that direction; policies are additive, never subtractive.
  • Start every hardened namespace with a default-deny-all covering both directions.
  • Egress control is the CKS differentiator — always allow DNS back, then only the destinations a workload needs.
  • Block the cloud metadata endpoint (169.254.169.254) with an ipBlock/except egress rule to stop credential theft.
  • Your CNI must enforce policy, and you must test allowed and denied paths before moving on.

Master these six ideas and NetworkPolicy shifts from a point-losing topic to one of the most reliable sections of your CKS score. Pair the theory with repeated hands-on reps, and default-deny becomes your reflex — on the exam and in production.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now