Skip to content

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

ConceptDescription
Pod SelectorSelects which pods the policy applies to (uses label selectors)
Policy TypesIngress, Egress, or both
Ingress RulesDefine what incoming traffic is allowed
Egress RulesDefine what outgoing traffic is allowed
Default BehaviorNo 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.

yaml
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 ingress

Default Deny All Egress

Prevents any outgoing traffic from all pods in the namespace.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress         # No egress rules = deny all egress

DNS 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

yaml
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: 53

Default Deny All Ingress and Egress

The most restrictive starting point -- deny everything in both directions.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Pod 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.

yaml
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: 8080

Namespace Selector Rules

Namespace selectors allow traffic from pods in specific namespaces. This is essential for cross-namespace communication.

Allow Traffic from a Specific Namespace

yaml
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: 9090

Labeling Namespaces

Namespaces must have labels for namespaceSelector to work. Label your namespaces:

bash
kubectl label namespace monitoring purpose=monitoring
kubectl label namespace production purpose=production

Kubernetes 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

yaml
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: 443

Combining 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.

yaml
ingress:
  - from:
      - podSelector:            # Rule 1: pods with role=frontend
          matchLabels:
            role: frontend
      - namespaceSelector:      # Rule 2: any pod in namespace with team=devops
          matchLabels:
            team: devops

This 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.

yaml
ingress:
  - from:
      - podSelector:            # AND condition: must match BOTH
          matchLabels:
            role: frontend
        namespaceSelector:      # selectors to be allowed
          matchLabels:
            team: devops

This 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.

yaml
# 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.

yaml
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: 53

Namespace Isolation Pattern

A common production pattern: isolate a namespace completely and then selectively open access.

Step 1: Default Deny Everything

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Step 2: Allow DNS for All Pods

yaml
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: 53

Step 3: Frontend Can Receive External and Reach Backend

yaml
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: 8080

Step 4: Backend Can Receive from Frontend and Reach Database

yaml
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: 5432

Step 5: Database Can Only Receive from Backend

yaml
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: 5432

Verifying Network Policies

bash
# 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 fail

Common 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

bash
# 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 wide

Exam 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.

Released under the MIT License.