Network Policies
Introduction
Network Policies are Kubernetes resources that control the flow of network traffic between pods, namespaces, and external endpoints. By default, Kubernetes allows all pod-to-pod communication within a cluster -- a significant security risk. Network Policies allow you to implement the principle of least privilege at the network layer.
CKS Exam Relevance
Network Policies are one of the most frequently tested topics in the CKS exam. You should be able to write a NetworkPolicy from scratch, understand selector logic, and debug connectivity issues caused by policies. Expect at least one question on this topic.
How NetworkPolicy Objects Work
A NetworkPolicy is a namespaced resource that selects pods using labels and defines rules for allowed ingress (incoming) and egress (outgoing) traffic.
Key Concepts
| Concept | Description |
|---|---|
| Pod Selector | Selects which pods the policy applies to (uses label selectors) |
| Policy Types | Ingress, Egress, or both |
| Ingress Rules | Define what incoming traffic is allowed |
| Egress Rules | Define what outgoing traffic is allowed |
| Default Behavior | No policies = all traffic allowed. Any policy selecting a pod = default deny for that direction |
Critical Understanding
Once any NetworkPolicy selects a pod for a given direction (ingress or egress), all traffic in that direction is denied by default unless explicitly allowed by a rule. This is the "default deny" behavior that kicks in when a policy is applied.
Default Deny Policies
The first step in securing any namespace is to apply default deny policies. These deny all traffic unless explicitly allowed by other policies.
Default Deny All Ingress
Prevents any incoming traffic to all pods in the namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {} # Empty selector = selects ALL pods in namespace
policyTypes:
- Ingress # No ingress rules = deny all ingressDefault Deny All Egress
Prevents any outgoing traffic from all pods in the namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress # No egress rules = deny all egressDNS Will Break
Denying all egress will block DNS resolution (port 53). Your pods will not be able to resolve service names. Always create a companion policy that allows DNS traffic when using default deny egress.
Allow DNS with Default Deny Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Default Deny All Ingress and Egress
The most restrictive starting point -- deny everything in both directions.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressPod Selector Rules
Pod selectors use label matching to identify which pods traffic is allowed from/to.
Allow Traffic from Specific Pods
Allow ingress only from pods with label role: frontend to pods with label role: backend.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: production
spec:
podSelector:
matchLabels:
role: backend # This policy applies to backend pods
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend # Allow traffic FROM frontend pods
ports:
- protocol: TCP
port: 8080Namespace Selector Rules
Namespace selectors allow traffic from pods in specific namespaces. This is essential for cross-namespace communication.
Allow Traffic from a Specific Namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring
namespace: production
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: monitoring # Allow from namespaces labeled purpose=monitoring
ports:
- protocol: TCP
port: 9090Labeling Namespaces
Namespaces must have labels for namespaceSelector to work. Label your namespaces:
kubectl label namespace monitoring purpose=monitoring
kubectl label namespace production purpose=productionKubernetes 1.22+ automatically adds the label kubernetes.io/metadata.name=<namespace-name> to all namespaces.
IP Block Rules
IP blocks allow or restrict traffic based on CIDR ranges. Useful for controlling traffic to/from external services.
Allow Egress to Specific External IPs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-api
namespace: production
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.0.1.0/24 # Exclude this subnet
ports:
- protocol: TCP
port: 443Combining Selectors: AND vs OR
This is one of the most confusing and most tested aspects of Network Policies.
OR Logic (Separate List Items)
Each item in the from or to array is a separate rule. Traffic matching any rule is allowed.
ingress:
- from:
- podSelector: # Rule 1: pods with role=frontend
matchLabels:
role: frontend
- namespaceSelector: # Rule 2: any pod in namespace with team=devops
matchLabels:
team: devopsThis means: allow traffic from frontend pods in the same namespace OR from any pod in namespaces labeled team=devops.
AND Logic (Same List Item)
When selectors are combined in the same list item, they form an AND condition.
ingress:
- from:
- podSelector: # AND condition: must match BOTH
matchLabels:
role: frontend
namespaceSelector: # selectors to be allowed
matchLabels:
team: devopsThis means: allow traffic only from pods labeled role=frontend that are also in namespaces labeled team=devops.
Exam Trap
The difference between AND and OR in NetworkPolicy is a single YAML dash (-). This is a very common source of errors. Always double-check your indentation and list structure.
# OR -- note the dash before EACH selector
- from:
- podSelector: ...
- namespaceSelector: ...
# AND -- note ONLY ONE dash, both selectors under it
- from:
- podSelector: ...
namespaceSelector: ...Combined Ingress and Egress Policy
A complete policy that controls both directions.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
- Egress
ingress:
# Allow ingress from frontend pods on port 8080
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 8080
# Allow ingress from monitoring namespace on port 9090
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ports:
- protocol: TCP
port: 9090
egress:
# Allow egress to database pods on port 5432
- to:
- podSelector:
matchLabels:
role: database
ports:
- protocol: TCP
port: 5432
# Allow DNS resolution
- to: []
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Namespace Isolation Pattern
A common production pattern: isolate a namespace completely and then selectively open access.
Step 1: Default Deny Everything
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressStep 2: Allow DNS for All Pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Step 3: Frontend Can Receive External and Reach Backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: production
spec:
podSelector:
matchLabels:
role: frontend
policyTypes:
- Ingress
- Egress
ingress:
- ports:
- protocol: TCP
port: 443
egress:
- to:
- podSelector:
matchLabels:
role: backend
ports:
- protocol: TCP
port: 8080Step 4: Backend Can Receive from Frontend and Reach Database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: production
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
role: database
ports:
- protocol: TCP
port: 5432Step 5: Database Can Only Receive from Backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-policy
namespace: production
spec:
podSelector:
matchLabels:
role: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: backend
ports:
- protocol: TCP
port: 5432Verifying Network Policies
# List all network policies in a namespace
kubectl get networkpolicies -n production
# Describe a network policy for details
kubectl describe networkpolicy backend-policy -n production
# Test connectivity from one pod to another
kubectl exec -n production frontend-pod -- curl -s --max-time 3 http://backend-svc:8080
# Test that blocked traffic is actually blocked
kubectl exec -n production frontend-pod -- curl -s --max-time 3 http://database-svc:5432
# Should timeout or failCommon Pitfalls and Gotchas
Pitfall 1: CNI Plugin Must Support Network Policies
Not all CNI plugins support Network Policies. The default kubenet does not. You need a compatible CNI like Calico, Cilium, Weave Net, or Antrea. In Kind clusters, you may need to install Calico separately.
Pitfall 2: Policies Are Additive
Multiple NetworkPolicies selecting the same pod are unioned (additive). You cannot create a policy that removes access granted by another policy. The effective set of allowed connections is the union of all policies.
Pitfall 3: Empty podSelector Selects All Pods
An empty podSelector: {} in the spec section selects all pods in the namespace. This is how default deny policies work. Do not confuse this with an empty selector in from/to rules.
Pitfall 4: Forgetting Egress DNS
When applying default deny egress, pods lose DNS resolution. Always add a companion policy allowing UDP/TCP port 53 egress. Without DNS, service discovery fails completely.
Pitfall 5: Namespace Labels Required
namespaceSelector matches on namespace labels, not names. You must ensure target namespaces are labeled. Use kubernetes.io/metadata.name (auto-applied in K8s 1.22+) for matching by name.
Pitfall 6: Policies Are Namespace-Scoped
A NetworkPolicy in namespace A only affects pods in namespace A. You cannot create a policy in one namespace to restrict pods in another namespace.
Pitfall 7: No Explicit Deny Rules
There is no way to write an explicit "deny" rule. Policies only define "allow" rules. Denial is implicit -- traffic not matching any allow rule is denied once any policy selects the pod.
Quick Reference
# Create a default deny ingress policy quickly
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: TARGET_NAMESPACE
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
# Verify pod labels for debugging
kubectl get pods -n production --show-labels
# Check which policies affect a specific pod
kubectl get networkpolicies -n production -o wideExam Speed Tip
Bookmark the NetworkPolicy documentation in your browser during the exam. The examples page has ready-to-use templates you can copy and modify.