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?”

See Also


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