CKS — Default Deny Policies
Why Default Deny Is Essential for CKS
By default, Kubernetes allows all pod-to-pod communication within a cluster. Any pod can reach any other pod on any port. This is the opposite of zero-trust networking.
The CKS exam expects you to lock down namespaces with default deny policies as the first step in securing cluster networking. Without default deny:
- A compromised pod can scan the entire cluster network
- A pod in
frontendcan directly access the database inbackend - Lateral movement after initial compromise is trivial
Default deny is the foundation. Everything else builds on top of it.
Default Deny Ingress
This policy selects all pods in the namespace (empty podSelector) and declares Ingress as a policy type — but specifies no ingress rules. Result: all inbound traffic to every pod in the namespace is denied.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
What This Does
- Selects: every pod in the
productionnamespace (podSelector: {}matches all pods) - Policy type:
Ingress— this policy governs inbound traffic - Ingress rules: none — no inbound traffic is allowed
- Egress: not affected — outbound traffic is still unrestricted
Apply It
kubectl apply -f default-deny-ingress.yaml
# Verify
kubectl get networkpolicy -n production
Default Deny Egress
Same pattern, but for outbound traffic:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
What This Does
- Selects: every pod in the
productionnamespace - Policy type:
Egress— this policy governs outbound traffic - Egress rules: none — no outbound traffic is allowed
- Ingress: not affected
Warning: Default deny egress breaks DNS resolution. Pods will not be able to resolve service names or any hostnames. You almost always need to pair this with an allow-DNS-egress policy. See below.
Default Deny Both Directions
The most restrictive starting point — deny all traffic in both directions:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
What This Does
- No inbound traffic to any pod in the namespace
- No outbound traffic from any pod in the namespace
- DNS is broken — pods cannot resolve anything
- This is the most secure starting point — only explicitly allowed traffic can pass
The DNS Problem
After applying default deny egress, every pod in the namespace loses DNS resolution:
# From a pod in the namespace with default deny egress
nslookup kubernetes.default
;; connection timed out; no servers could be reached
This breaks virtually everything — services cannot find each other, health checks fail, and applications crash.
The Fix: Allow DNS Egress
This is the most common CKS pattern — default deny all + allow DNS:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
What This Does
- Selects: all pods in
production - Allows egress: to pods labeled
k8s-app: kube-dnsin any namespace, on port 53 (UDP and TCP) - Everything else egress: still denied (by the default deny policy)
Why TCP and UDP? DNS typically uses UDP, but falls back to TCP for large responses (over 512 bytes) and zone transfers. Always allow both.
Alternative: Allow DNS by Namespace Only
If you don’t want to rely on pod labels, allow egress to the entire kube-system namespace on port 53:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
This uses the built-in kubernetes.io/metadata.name label that Kubernetes automatically sets on every namespace.
Applying Default Deny Per Namespace
Every sensitive namespace should have its own default deny policy. You cannot create a single cluster-wide default deny NetworkPolicy — NetworkPolicies are namespaced resources.
# Apply default deny to multiple namespaces
for ns in production staging backend database; do
kubectl apply -f default-deny-all.yaml -n $ns
kubectl apply -f allow-dns-egress.yaml -n $ns
done
What Happens After Default Deny
Once default deny is in place, the only way to allow traffic is with additional NetworkPolicies that explicitly permit it:
- Default deny all → nothing can talk to anything
- Allow DNS egress → pods can resolve names, but cannot connect
- Allow specific ingress → frontend can receive traffic from the ingress controller
- Allow specific egress → backend can reach the database
This is the zero-trust networking model: deny everything, then allow only what is needed.
Common Mistakes
Mistake 1: Forgetting DNS After Default Deny Egress
# This BREAKS everything — no DNS
spec:
podSelector: {}
policyTypes:
- Egress
Always pair default deny egress with an allow-DNS policy.
Mistake 2: Missing policyTypes
# This does NOTHING — no policyTypes means no enforcement
spec:
podSelector: {}
Without policyTypes, the policy has no effect. You must specify Ingress, Egress, or both.
Mistake 3: Thinking Default Deny Is Cluster-Wide
NetworkPolicies are namespaced. A default deny in production does not affect staging. You need to apply default deny to each namespace individually.
Mistake 4: Applying to the Wrong Namespace
# Wrong — applies to default namespace
kubectl apply -f default-deny-all.yaml
# Right — specify the namespace
kubectl apply -f default-deny-all.yaml -n production
If the metadata.namespace is set in the YAML, it overrides the -n flag. Be careful.
The Complete Default Deny Pattern
Here is the complete two-policy pattern you should memorize for the CKS exam:
Policy 1: Default Deny All
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Policy 2: Allow DNS Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Apply both, then add specific allow policies for each workload.
Security Impact
| Without Default Deny | With Default Deny |
|---|---|
| Any pod can reach any other pod | Only explicitly allowed traffic passes |
| Compromised pod can scan the cluster | Lateral movement is blocked |
| Data exfiltration is easy | Egress must be explicitly allowed |
| No audit trail of intended traffic | Policies document intended traffic flows |
Practice Questions
Question 1: Write a NetworkPolicy that denies all ingress traffic to pods in the `secure` namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: secure
spec:
podSelector: {}
policyTypes:
- Ingress
Key points:
podSelector: {}selects all pods in the namespacepolicyTypes: ["Ingress"]means this policy governs inbound traffic- No
ingressrules means all inbound traffic is denied
Question 2: After applying default deny egress, DNS stops working. Write the policy to fix DNS while keeping all other egress denied.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: secure
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
This allows egress only to CoreDNS pods on port 53 (UDP and TCP). All other egress remains denied by the default deny policy.
Question 3: What is the effect of a NetworkPolicy with `podSelector: {}` and no `policyTypes` field?
It has no effect. Without policyTypes, the NetworkPolicy does not enforce any rules. You must explicitly specify Ingress, Egress, or both in the policyTypes field.
Actually, there is a nuance: if you specify ingress or egress rules but omit policyTypes, Kubernetes infers the policy types from the rules present. But if neither rules nor policyTypes are specified, the policy is effectively a no-op.
Question 4: Can a single NetworkPolicy apply default deny across all namespaces in a cluster?
No. NetworkPolicies are namespaced resources. A NetworkPolicy in namespace A only affects pods in namespace A. To apply default deny across the cluster, you must create a default deny NetworkPolicy in each namespace individually.
Some tools like Kyverno or OPA Gatekeeper can automate this by creating default deny policies whenever a new namespace is created.
Question 5: You applied default deny all (ingress + egress) and allow-DNS-egress to the `app` namespace. A pod in `app` can resolve DNS names but cannot connect to a service in the same namespace. Why?
The default deny policy blocks all ingress. Even though the client pod can resolve the service name (DNS egress is allowed), the target pod’s ingress is denied. You need an additional NetworkPolicy that allows ingress to the target pod from the client pod.
Additionally, the client pod needs an egress rule allowing traffic to the target pod. The allow-DNS-egress policy only permits egress to port 53 — it does not allow egress to other pods.
Both an ingress allow on the target and an egress allow on the source are required.