Kubernetes Network Policies

Declarative firewall rules for Pod-to-Pod traffic. Kubernetes ships with an “allow-all” networking model — every Pod can reach every other Pod by default. NetworkPolicies flip this to an explicit whitelist model, enabling zero-trust east-west segmentation inside the cluster. Source: CKA Day 26

The Problem: Flat Open Networking

By default, a Kubernetes cluster has no internal firewall. The CNI plugin (e.g., Flannel, Calico, Cilium) creates overlay routes that let any Pod talk to any other Pod on any port, across all Namespaces. This means:

  • A compromised frontend Pod can directly scan or connect to a database Pod
  • A misconfigured workload can accidentally leak data to unrelated services
  • There is no network-level defense-in-depth beyond application authentication

Ingress = traffic flowing into a Pod from an external source (user, another Pod, the internet).
Egress = traffic flowing out of a Pod toward an external destination.

NetworkPolicies are the Kubernetes-native answer: they let you declare which connections are legitimate and silently deny everything else.

What Is a NetworkPolicy?

A NetworkPolicy is a namespace-scoped API object (networking.k8s.io/v1) that:

  1. Selects a set of Pods using a label query (podSelector)
  2. Defines allowed traffic directions (Ingress, Egress, or both)
  3. Whitelists specific sources/destinations and ports

Key principle: NetworkPolicies are whitelist-only. They describe what is permitted, not what is forbidden. Once any NetworkPolicy selects a Pod, that Pod flips from “allow all” to “deny all except whitelisted”.

CNI Plugin Support Matrix

NetworkPolicies are not enforced by the API server — they are enforced by the CNI plugin running on each node. If your CNI does not implement the policy controller, NetworkPolicy objects will exist but have zero effect.

CNI PluginPolicy SupportProduction Notes
Flannel❌ NoSimple overlay; no built-in policy engine
kindnet❌ NoDefault in Kind clusters; fine for local tests without policies
Weave Net⚠️ DeprecatedLast significant update ~2 years ago; unreliable on Kubernetes 1.30+
Calico✅ YesIndustry standard; default on GKE; supports L3/L4 policies and advanced features
Cilium✅ YeseBPF-based; supports L3/L4/L7 policies, DNS-aware rules, and observability
AWS VPC CNI✅ Yes (with Network Policy Agent)Required add-on for EKS
Azure CNI✅ YesSupported on AKS with Azure Network Policy or Calico

CKA exam implication: You must know that Flannel and kindnet do NOT support NetworkPolicies, and that Calico is the go-to choice for policy-enabled clusters.

YAML Anatomy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-backend
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: mysql          # Target: Pods with label app=mysql
  policyTypes:
  - Ingress               # Direction(s) this policy controls
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: backend   # Source: only Pods labeled role=backend
    ports:
    - protocol: TCP
      port: 3306          # Destination port on the target Pod

Field Reference

FieldScopeDescription
podSelectorTargetLabel query that decides which Pods this policy protects. Empty {} = all Pods in the namespace.
policyTypesBehaviorList containing Ingress and/or Egress. Required to activate that direction.
ingressWhitelistList of from blocks (sources) and ports (destination ports) allowed into target Pods.
egressWhitelistList of to blocks (destinations) and `ports** allowed out of target Pods.
from / toMatchersCan contain podSelector, namespaceSelector, ipBlock, or a combination.

Selector Combinations in from / to

# Allow from any Pod in the 'monitoring' Namespace
- from:
  - namespaceSelector:
      matchLabels:
        env: monitoring
 
# Allow from a specific Pod in ANY Namespace (less common)
- from:
  - podSelector:
      matchLabels:
        app: frontend
 
# Allow from a specific Pod in a specific Namespace (AND logic)
- from:
  - podSelector:
      matchLabels:
        app: frontend
    namespaceSelector:
      matchLabels:
        env: staging
 
# Allow from an external IP range (e.g., on-prem bastion)
- from:
  - ipBlock:
      cidr: 10.0.0.0/8
      except:
      - 10.1.0.0/16

AND vs OR: When podSelector and namespaceSelector appear in the same array element, they are ANDed (Pod must match labels AND be in a Namespace matching labels). When they appear in separate array elements, they are ORed.

Default Deny Pattern

Production best practice is to start with default-deny policies per Namespace, then open explicit paths:

Deny All Ingress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}       # All Pods in this namespace
  policyTypes:
  - Ingress
  # No ingress rules = deny everything incoming

Deny All Egress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

Combined Deny All

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

After default-deny, add granular allow policies:

  • frontendbackend on TCP 80
  • backenddatabase on TCP 3306
  • All Pods → CoreDNS on UDP 53 (required for DNS resolution)
  • Monitoring Pods → all workloads on TCP 9090 (metrics scraping)

Practical Example: Three-Tier Application

┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│  frontend   │───▶  │   backend   │───▶  │   database  │
│  (nginx:80) │      │  (nginx:80) │      │ (mysql:3306)│
└─────────────┘      └─────────────┘      └─────────────┘

Goal: Only backend may reach database on port 3306. frontend (and any other Pod) must be blocked.

Step 1 — Default Deny Ingress in Namespace

Apply the default-deny-ingress manifest above.

Step 2 — Allow Backend → Database

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-backend
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: backend
    ports:
    - protocol: TCP
      port: 3306

Result:

  • frontend Pod: telnet db 3306 → ❌ hangs / times out
  • backend Pod: telnet db 3306 → ✅ connects immediately
  • Any new unlabeled Pod: telnet db 3306 → ❌ denied

Calico on Kind: Lab Setup

The default Kind CNI (kindnet) does not enforce NetworkPolicies. To practice with Calico:

kind-calico.yaml

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
  podSubnet: "192.168.0.0/16"
nodes:
- role: control-plane
- role: worker
- role: worker

Commands

# 1. Create cluster (nodes will show NotReady)
kind create cluster --name cka --config kind-calico.yaml
 
# 2. Install Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
 
# 3. Wait for CNI pods
kubectl get pods -n kube-system -l k8s-app=calico-node -w
 
# 4. Verify nodes are Ready
kubectl get nodes

Calico deploys as a DaemonSet (calico-node), placing one enforcement agent per node. It uses Linux iptables or eBPF (depending on configuration) to insert allow/deny rules based on every NetworkPolicy object.

Production clusters: The same Calico installation applies to kubeadm-provisioned VMs. After kubeadm init, install Calico before joining worker nodes. Ensure --pod-network-cidr matches Calico’s default pool (192.168.0.0/16). See Kubeadm Cluster Setup for the full installation workflow. Source: CKA Day 27

Imperative Commands

# List all NetworkPolicies in a namespace
kubectl get networkpolicy
kubectl get netpol
 
# Describe a specific policy (shows effective selectors and ports)
kubectl describe netpol db-allow-backend
 
# Test connectivity from inside a Pod
kubectl exec -it frontend-pod -- bash
telnet db 3306          # or curl backend:80
 
# Verify DNS still works after default-deny (egress to CoreDNS must be allowed)
nslookup kubernetes.default

Common Pitfalls

PitfallExplanation
CNI doesn’t support policiesPolicies exist but are ignored. Always verify your CNI before relying on NetworkPolicies for security.
Forgetting CoreDNS egressDefault-deny egress breaks DNS resolution. Explicitly allow UDP 53 to the kube-system Namespace.
Label mismatchpodSelector uses labels, not Pod names or Service names. A typo in a label means zero Pods are selected.
Empty selector = all PodspodSelector: {} selects every Pod in the namespace — powerful but dangerous if unintended.
Policies are namespace-scopedA NetworkPolicy in namespace-a cannot directly select Pods in namespace-b without a namespaceSelector.
Ingress vs Egress confusioningress rules describe who can reach into the target Pod. egress rules describe what the target Pod can reach out to.

CKA Exam Patterns

  • Create a NetworkPolicy from scratch that restricts a sensitive workload (e.g., database) to only its legitimate consumer tier
  • Know the networking.k8s.io/v1 API group and the four top-level fields: podSelector, policyTypes, ingress, egress
  • Remember that policies are whitelist-only: once a Pod is selected, unlisted traffic is denied
  • Be comfortable with telnet or curl from inside Pods to verify policy enforcement
  • Know that Flannel and kindnet are common trap answers for “which CNI does NOT support NetworkPolicies?”

Practical Practice

Exam-style hands-on tasks for NetworkPolicies. Complete each task before reviewing the solution. Time yourself — CKA tasks average 5–7 minutes.

Task 1: Default-Deny Policy You are asked to create a NetworkPolicy default-deny in namespace staging that blocks all ingress and egress traffic by default. Requirements: Must affect all Pods in staging. No exceptions. Verification: kubectl get networkpolicy -n staging Solution:

kubectl create namespace staging
cat <<EOF | kubectl apply -f -

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: staging spec: podSelector: {} policyTypes:

  • Ingress
  • Egress EOF

Task 2: Allow Ingress from Frontend A Pod api with label app=api in namespace production must accept TCP traffic on port 8080 only from Pods with label tier=frontend in the same namespace. Requirements: Use a NetworkPolicy named allow-frontend. Deny everything else. Verification: kubectl get networkpolicy -n production Solution:

cat <<EOF | kubectl apply -f -

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend namespace: production spec: podSelector: matchLabels: app: api policyTypes:

  • Ingress ingress:
  • from:
    • podSelector: matchLabels: tier: frontend ports:
    • protocol: TCP port: 8080 EOF

Task 3: Cross-Namespace Egress to CoreDNS A default-deny NetworkPolicy in namespace secure is blocking DNS resolution. Add an egress rule that allows UDP 53 to the kube-system namespace so Pods can resolve cluster DNS. Requirements: Only allow egress to kube-system on UDP port 53. All other egress remains blocked. Verification: kubectl run -n secure -it --rm debug --image=busybox:1.28 --restart=Never -- nslookup kubernetes.default Solution:

cat <<EOF | kubectl apply -f -

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns namespace: secure spec: podSelector: {} policyTypes:

  • Egress egress:
  • to:
    • namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports:
    • protocol: UDP port: 53 EOF

Task 4: Debug a Connectivity Failure A Service web-svc in namespace default cannot reach Pods behind Service api-svc in namespace backend. You suspect a NetworkPolicy is blocking traffic. Requirements: Identify the blocking policy and modify it to allow ingress from default namespace on port 80. Verification: kubectl exec deploy/web -- curl -s http://api-svc.backend.svc.cluster.local Solution:

# 1. Check existing NetworkPolicies in backend namespace
kubectl get networkpolicy -n backend
# 2. Inspect the policy rules
kubectl describe networkpolicy <policy-name> -n backend
# 3. Edit the policy to add an ingress rule from the default namespace
kubectl edit networkpolicy <policy-name> -n backend
# Add under ingress:
# - from:
#   - namespaceSelector:
#       matchLabels:
#         kubernetes.io/metadata.name: default
#   ports:
#   - protocol: TCP
#     port: 80

See Also


Tags: kubernetes networkpolicy cni calico security zero-trust cka networking egress ingress devsecops