CKS — Network Policy Testing

9 minute read

Why Testing Matters

A NetworkPolicy that looks correct in YAML may not work as intended. Common reasons:

  • Labels don’t match
  • Wrong namespace
  • CNI doesn’t support NetworkPolicies
  • AND vs OR confusion in selectors
  • Missing DNS allow after default deny egress

You must test every NetworkPolicy you create. On the CKS exam, verifying your policy works is part of answering the question correctly.


Testing with Temporary Pods

The primary testing method is launching temporary pods with kubectl run --rm -it:

# Launch a temporary busybox pod for testing
kubectl run test-pod --rm -it --image=busybox --restart=Never -n <namespace> -- /bin/sh

# Launch a temporary curl pod (has curl pre-installed)
kubectl run test-curl --rm -it --image=curlimages/curl --restart=Never -n <namespace> -- /bin/sh

Flags Explained

Flag Purpose
--rm Delete the pod when it exits
-it Interactive terminal
--image=busybox Lightweight image with basic tools
--restart=Never Create a Pod (not a Deployment)
-n <namespace> Create in a specific namespace

The pod is automatically cleaned up when you exit the shell.


Common Testing Commands

Test DNS Resolution

# Using nslookup (busybox)
nslookup kubernetes.default

# Using wget to test DNS
wget -qO- --timeout=2 http://kubernetes.default.svc.cluster.local

# Using dig (if available)
dig kubernetes.default.svc.cluster.local

Expected when DNS works:

Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kubernetes.default
Address 1: 10.96.0.1 kubernetes.default.svc.cluster.local

Expected when DNS is blocked:

;; connection timed out; no servers could be reached

Test TCP Connectivity

# Using wget (busybox)
wget -qO- --timeout=2 http://<service-name>.<namespace>.svc.cluster.local:<port>

# Using wget with IP
wget -qO- --timeout=2 http://10.0.5.10:8080

# Using nc (netcat) — tests if port is reachable
nc -zv -w 2 <target-ip> <port>

# Using nc with service name
nc -zv -w 2 <service-name>.<namespace>.svc.cluster.local <port>

Test HTTP Connectivity (curl pod)

# From a curl-based test pod
curl -s --connect-timeout 2 http://<service>.<namespace>:8080

# Test HTTPS
curl -sk --connect-timeout 2 https://<service>.<namespace>:443

# Test with verbose output (shows connection details)
curl -v --connect-timeout 2 http://<service>.<namespace>:8080

Test External Connectivity

# Test if pod can reach the internet
wget -qO- --timeout=2 http://ifconfig.me

# Test cloud metadata (should be blocked)
wget -qO- --timeout=2 http://169.254.169.254/latest/meta-data/

Verifying Ingress Rules

Scenario: Only frontend pods should reach backend pods on port 8080.

Test from an allowed source (frontend namespace):

kubectl run test-frontend --rm -it --image=busybox --restart=Never -n frontend -- \
  wget -qO- --timeout=2 http://backend-svc.backend.svc.cluster.local:8080

Expected: response body or HTTP data.

Test from a denied source (different namespace):

kubectl run test-attacker --rm -it --image=busybox --restart=Never -n default -- \
  wget -qO- --timeout=2 http://backend-svc.backend.svc.cluster.local:8080

Expected: timeout or connection refused.

Test from a denied source (same namespace, wrong label):

kubectl run test-wrong-label --rm -it --image=busybox --restart=Never \
  --labels="app=malicious" -n frontend -- \
  wget -qO- --timeout=2 http://backend-svc.backend.svc.cluster.local:8080

If the policy selects by pod label, this pod with a different label should be denied.


Verifying Egress Rules

Scenario: Default deny egress + allow DNS only.

Test DNS (should work):

kubectl run test-dns --rm -it --image=busybox --restart=Never -n production -- \
  nslookup kubernetes.default

Expected: DNS response.

Test external access (should fail):

kubectl run test-external --rm -it --image=busybox --restart=Never -n production -- \
  wget -qO- --timeout=2 http://ifconfig.me

Expected: timeout.

Test internal service access (should fail if only DNS is allowed):

kubectl run test-internal --rm -it --image=busybox --restart=Never -n production -- \
  wget -qO- --timeout=2 http://my-service.production:8080

Expected: timeout (DNS resolves but connection is blocked).


Testing DNS Resolution After Egress Policies

DNS is the most common thing to break with egress policies. Always test DNS first:

# Quick DNS test — should complete in <1 second if DNS works
kubectl run dns-test --rm -it --image=busybox --restart=Never -n <namespace> -- \
  nslookup kubernetes.default

# If DNS times out, your allow-DNS policy is wrong
# Check CoreDNS pod labels
kubectl get pods -n kube-system -l k8s-app=kube-dns --show-labels

# Check the namespace label used in your policy
kubectl get namespace kube-system --show-labels

Debugging: Policy Not Working?

When a NetworkPolicy doesn’t behave as expected, work through this checklist:

1. Does Your CNI Support NetworkPolicies?

# Check which CNI is installed
kubectl get pods -n kube-system | grep -E "calico|cilium|weave|flannel"

# Or check the CNI config
ls /etc/cni/net.d/
CNI NetworkPolicy Support
Calico Full (ingress + egress)
Cilium Full (ingress + egress + L7)
Weave Net Ingress only
Flannel None — policies are silently ignored

If you are using Flannel, NetworkPolicies will apply without error but will have NO effect.

2. Do Labels Match?

# Check pod labels
kubectl get pods -n <namespace> --show-labels

# Check namespace labels
kubectl get namespaces --show-labels

# Check what your policy selects
kubectl describe networkpolicy <name> -n <namespace>

The describe output shows the resolved selectors. Verify that the labels on your pods/namespaces match exactly.

3. Is the Policy in the Right Namespace?

# List policies in a namespace
kubectl get networkpolicy -n <namespace>

A NetworkPolicy in namespace A does not affect pods in namespace B.

4. Did You Specify policyTypes?

kubectl describe networkpolicy <name> -n <namespace> | grep "Policy Types"

If policyTypes is empty or missing, check your YAML.

5. AND vs OR — Check Your YAML Indentation

This is the most common mistake:

# AND — must match both
- from:
    - namespaceSelector:
        matchLabels:
          tier: frontend
      podSelector:          # same level as namespaceSelector = AND
        matchLabels:
          app: web

# OR — either matches
- from:
    - namespaceSelector:
        matchLabels:
          tier: frontend
    - podSelector:          # new list item = OR
        matchLabels:
          app: web

6. Is Default Deny in Place?

Without default deny, added allow policies have no restrictive effect. Traffic already flows freely.

# Check for default deny
kubectl get networkpolicy -n <namespace> | grep deny

NetworkPolicy Troubleshooting Flowchart

Traffic is BLOCKED when it should be ALLOWED:
│
├─ Is there a default deny policy?
│  └─ Yes → Is there an allow policy for this traffic?
│     ├─ No → Create one
│     └─ Yes → Check label matching:
│        ├─ Pod labels match podSelector? (kubectl get pods --show-labels)
│        ├─ Namespace labels match namespaceSelector? (kubectl get ns --show-labels)
│        ├─ Ports correct? (protocol AND port number)
│        └─ AND vs OR correct? (check YAML indentation)
│
Traffic is ALLOWED when it should be BLOCKED:
│
├─ Is there a default deny policy in the TARGET namespace?
│  ├─ No → Create one (NetworkPolicies only add allows on top of deny)
│  └─ Yes → Is there an overly permissive allow policy?
│     ├─ Check for namespaceSelector: {} (matches ALL namespaces)
│     ├─ Check for podSelector: {} (matches ALL pods)
│     └─ Check for missing ports (no ports = all ports allowed)
│
DNS is broken:
│
├─ Is there a default deny egress policy?
│  └─ Yes → Is there an allow-DNS-egress policy?
│     ├─ No → Create one (allow port 53 UDP+TCP to CoreDNS)
│     └─ Yes → Check:
│        ├─ CoreDNS pod labels (k8s-app: kube-dns)
│        ├─ Both UDP and TCP port 53 allowed?
│        └─ namespaceSelector matches kube-system?

kubectl Commands for Debugging

List All NetworkPolicies

# In a specific namespace
kubectl get networkpolicy -n production

# Across all namespaces
kubectl get networkpolicy --all-namespaces

Describe a NetworkPolicy

kubectl describe networkpolicy <name> -n <namespace>

The output shows:

  • Which pods the policy selects
  • Ingress rules (from, ports)
  • Egress rules (to, ports)
  • Policy types

Check Pod Labels

kubectl get pods -n <namespace> --show-labels

# Filter by specific label
kubectl get pods -n <namespace> -l app=web --show-labels

Check Namespace Labels

kubectl get namespaces --show-labels

# Check a specific namespace
kubectl get namespace <name> --show-labels

Check Which Policies Affect a Pod

There is no built-in command for this. You need to:

  1. Get the pod’s labels
  2. Find policies whose podSelector matches those labels
  3. Check policyTypes to see what is governed
# Get pod labels
kubectl get pod <name> -n <namespace> -o jsonpath='{.metadata.labels}'

# List all policies and check each one
kubectl get networkpolicy -n <namespace> -o yaml

Verify Connectivity with a One-Liner

# TCP connectivity test (exits 0 if reachable, non-zero if blocked)
kubectl run test --rm -it --image=busybox --restart=Never -n <namespace> -- \
  sh -c "nc -zv -w 2 <target> <port> && echo ALLOWED || echo BLOCKED"

Testing Checklist for CKS Exam

When you create a NetworkPolicy on the exam:

  1. Apply the policy: kubectl apply -f policy.yaml
  2. Verify it exists: kubectl get networkpolicy -n <namespace>
  3. Describe it: kubectl describe networkpolicy <name> -n <namespace>
  4. Test allowed traffic: run a temporary pod from the allowed source/namespace
  5. Test denied traffic: run a temporary pod from a denied source/namespace
  6. Test DNS: if egress policy, verify DNS still works

Time is limited on the exam. Have your test commands ready:

# Quick allowed test
kubectl run t1 --rm -it --image=busybox --restart=Never -n <source-ns> -- \
  wget -qO- --timeout=2 http://<svc>.<target-ns>:8080

# Quick denied test
kubectl run t2 --rm -it --image=busybox --restart=Never -n <other-ns> -- \
  wget -qO- --timeout=2 http://<svc>.<target-ns>:8080

Practice Questions

Question 1: You applied a default deny ingress policy to the `secure` namespace. Write the commands to verify that ingress is actually blocked.
# Step 1: Create a target pod in the secure namespace
kubectl run target --image=nginx --port=80 -n secure

# Wait for it to be ready
kubectl wait --for=condition=Ready pod/target -n secure --timeout=30s

# Step 2: Try to reach it from the default namespace (should fail)
kubectl run test-ingress --rm -it --image=busybox --restart=Never -n default -- \
  wget -qO- --timeout=3 http://$(kubectl get pod target -n secure -o jsonpath='{.status.podIP}'):80

# Expected: timeout — ingress is blocked

# Step 3: Clean up
kubectl delete pod target -n secure

If wget times out after 3 seconds, the default deny ingress policy is working.

Question 2: After applying default deny egress, how do you quickly verify that DNS is broken?
kubectl run dns-check --rm -it --image=busybox --restart=Never -n <namespace> -- \
  nslookup kubernetes.default

# If DNS is broken, you'll see:
# ;; connection timed out; no servers could be reached

# If DNS works, you'll see:
# Name:      kubernetes.default
# Address 1: 10.96.0.1

A DNS timeout confirms that egress to port 53 is blocked. Apply the allow-DNS-egress policy and re-test.

Question 3: You created a policy to allow ingress from pods labeled `app: web` but traffic is still blocked. What are the first three things to check?
  1. Check that the source pod actually has the label:
    kubectl get pods -n <source-ns> --show-labels | grep web
    
  2. Check that the policy is in the correct namespace (the namespace of the target pod):
    kubectl get networkpolicy -n <target-ns>
    
  3. Check that policyTypes includes Ingress:
    kubectl describe networkpolicy <name> -n <target-ns> | grep "Policy Types"
    

Additional checks: verify namespaceSelector labels, check AND vs OR, confirm CNI supports policies.

Question 4: How do you test that pods cannot reach the cloud metadata endpoint (169.254.169.254)?
# Launch a test pod and try to reach the metadata endpoint
kubectl run test-metadata --rm -it --image=busybox --restart=Never -n <namespace> -- \
  wget -qO- --timeout=3 http://169.254.169.254/latest/meta-data/

# If blocked: timeout after 3 seconds
# If NOT blocked: you'll see metadata response (ami-id, instance-type, etc.)

If you see metadata output, the blocking policy is not working. Check your egress rules.

Question 5: Your NetworkPolicy uses `namespaceSelector` with `matchLabels: {env: production}` but traffic from the `production` namespace is still denied. What is likely wrong?

The production namespace probably does not have the label env: production. Verify:

kubectl get namespace production --show-labels

If the label is missing, add it:

kubectl label namespace production env=production

NetworkPolicies match namespaces by labels, not by name. The namespace must have the exact label specified in the namespaceSelector. The built-in label kubernetes.io/metadata.name: production is available by default, but custom labels must be added manually.

Updated: