20: Network Policies

20: Network Policies

Objective

Learn how Kubernetes NetworkPolicies control traffic flow between Pods, understand the default allow-all behavior, and practice implementing a defense-in-depth approach using default-deny policies with selective allow rules.


Theory

Default Behavior: Allow All

By default, Kubernetes allows all traffic between all Pods in the cluster — any Pod can communicate with any other Pod, regardless of namespace. This is convenient for development but dangerous in production.

NetworkPolicies allow you to restrict this behavior by defining rules for which Pods can send and receive traffic.

Cilium as the Network Policy Engine in AKS

NetworkPolicies are only enforced if the cluster has a network policy engine that implements them. In AKS, Cilium is the only supported engine when using Azure CNI with Cilium dataplane.

Cilium enforces network policies using eBPF programs attached directly in the Linux kernel:

  • No iptables overhead — rules are evaluated at the kernel level, providing significantly better performance
  • Per-Pod enforcement — each Pod gets its own eBPF program for ingress and egress filtering
  • Identity-based — Cilium assigns identities to Pods and evaluates policies based on these identities, not just IP addresses

Policy Types

Type Controls Direction
Ingress Incoming traffic to the selected Pods Who can send traffic to these Pods
Egress Outgoing traffic from the selected Pods Where these Pods can send traffic to

Selectors

NetworkPolicies use selectors to define which Pods are affected and which traffic is allowed:

Selector Purpose Example
podSelector Select Pods by label within the same namespace app: server
namespaceSelector Select Pods in other namespaces by namespace label env: production
ipBlock Allow/deny traffic from specific IP ranges cidr: 10.0.0.0/8

From Allow-All to Whitelist

graph LR
    subgraph step1["Step 1: Default (Allow All)"]
        direction TB
        A1["Pod A"] -->|"allowed"| B1["Pod B"]
        C1["Pod C"] -->|"allowed"| B1
        D1["External"] -->|"allowed"| B1
    end

    subgraph step2["Step 2: Default Deny Ingress"]
        direction TB
        A2["Pod A"] -->|"BLOCKED"| B2["Pod B"]
        C2["Pod C"] -->|"BLOCKED"| B2
        D2["External"] -->|"BLOCKED"| B2
    end

    subgraph step3["Step 3: Allow Specific"]
        direction TB
        A3["Pod A<br/>app: client"] -->|"allowed"| B3["Pod B<br/>app: server"]
        C3["Pod C"] -->|"BLOCKED"| B3
        D3["External"] -->|"BLOCKED"| B3
    end

    step1 -->|"apply<br/>default-deny"| step2
    step2 -->|"apply<br/>allow policy"| step3

    style step1 fill:#ffcdd2,stroke:#c62828,stroke-width:1px
    style step2 fill:#fff9c4,stroke:#f9a825,stroke-width:1px
    style step3 fill:#c8e6c9,stroke:#2e7d32,stroke-width:1px

Practical Tasks

Task 1: Deploy Client and Server Applications

First, deploy a simple server (nginx) and a client (busybox) in your namespace.

Create a file called netpol-apps.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: server-XX
  namespace: student-XX
  labels:
    app: server-XX
spec:
  replicas: 1
  selector:
    matchLabels:
      app: server-XX
  template:
    metadata:
      labels:
        app: server-XX
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: server-XX
  namespace: student-XX
spec:
  selector:
    app: server-XX
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: v1
kind: Pod
metadata:
  name: client-XX
  namespace: student-XX
  labels:
    app: client-XX
spec:
  containers:
    - name: busybox
      image: busybox:1.36
      command: ["sleep", "3600"]

Deploy and verify:

kubectl apply -f netpol-apps.yaml
kubectl get pods -n student-XX

Wait for all Pods to be Running, then test connectivity:

kubectl exec client-XX -n student-XX -- wget -qO- --timeout=5 server-XX

You should see the nginx welcome page HTML. This confirms that by default, all traffic is allowed.

Task 2: Apply Default-Deny Ingress Policy

Create a file called default-deny.yaml:

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

The empty podSelector: {} means this policy applies to all Pods in the namespace. With no ingress rules defined, no ingress traffic is allowed.

Apply the policy:

kubectl apply -f default-deny.yaml

Warning: Watch out for DNS! If you also apply a default-deny egress policy, it will block DNS (UDP port 53). If your pods can’t resolve service names after applying such a policy, you’ll need to add an egress rule allowing traffic to kube-dns in the kube-system namespace. The ingress-only default-deny above does not affect DNS resolution.

Now test connectivity again:

kubectl exec client-XX -n student-XX -- wget -qO- --timeout=5 server-XX

This time the connection should time out or be refused. The default-deny policy is blocking all ingress traffic to every Pod in the namespace.

Task 3: Create an Allow Policy

Now create a policy that specifically allows traffic from the client to the server on port 80.

Create a file called allow-client-to-server.yaml:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-client-to-server
  namespace: student-XX
spec:
  podSelector:
    matchLabels:
      app: server-XX
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: client-XX
    ports:
    - protocol: TCP
      port: 80

This policy:

  • Applies to Pods with label app: server-XX (the server)
  • Allows ingress traffic from Pods with label app: client-XX (the client)
  • Only on TCP port 80

Apply the policy:

kubectl apply -f allow-client-to-server.yaml

Task 4: Verify Allowed and Blocked Traffic

Test from the allowed client:

kubectl exec client-XX -n student-XX -- wget -qO- --timeout=5 server-XX

This should succeed again — the allow policy permits traffic from the client Pod.

Now test from a different Pod that does not match the allow policy:

kubectl run unauthorized-XX --rm -it --image=busybox:1.36 -n student-XX -- wget -qO- --timeout=5 server-XX

This should time out or be refused — the unauthorized Pod does not have label app: client-XX, so it does not match the allow policy. Only the specifically authorized client can reach the server.

Verify the policies in your namespace:

kubectl get networkpolicies -n student-XX
kubectl describe networkpolicy allow-client-to-server -n student-XX

Clean Up

kubectl delete -f allow-client-to-server.yaml
kubectl delete -f default-deny.yaml
kubectl delete -f netpol-apps.yaml

Common Problems

Problem Possible Cause Solution
NetworkPolicy has no effect Cluster does not have a network policy engine Verify Cilium is running: kubectl get pods -n kube-system -l k8s-app=cilium
Traffic still allowed after default-deny Policy applied to wrong namespace Check the namespace in the NetworkPolicy metadata
Allowed traffic is being blocked Labels do not match between policy selectors and Pod labels Verify labels with kubectl get pods --show-labels -n student-XX
DNS resolution fails after default-deny Egress to DNS (kube-dns) is blocked Add an egress policy allowing UDP port 53 to kube-dns
Timeout instead of connection refused Expected behavior with NetworkPolicy drops Cilium silently drops packets; timeout is the normal indicator of blocked traffic

Best Practices

  1. Always start with default-deny — Apply a default-deny policy to every namespace, then explicitly allow only the traffic that is required. This is the whitelist approach.
  2. Label your Pods consistently — NetworkPolicies rely on labels. Use a consistent labeling scheme (e.g., app, tier, env) across all workloads.
  3. Do not forget DNS — If you apply egress default-deny, remember to allow egress to kube-dns (UDP port 53), otherwise Pods cannot resolve Service names.
  4. Test policies before production — Use temporary test Pods to verify that policies allow expected traffic and block unexpected traffic.
  5. Document your policies — Maintain a clear record of which traffic flows are allowed and why, as the number of policies can grow quickly.

Summary

In this exercise you learned:

  • Kubernetes allows all Pod-to-Pod traffic by default
  • NetworkPolicies provide a mechanism to restrict traffic based on labels, namespaces, and IP blocks
  • Cilium enforces NetworkPolicies using eBPF in AKS clusters with the Cilium dataplane
  • A default-deny policy with an empty podSelector blocks all ingress traffic in the namespace
  • Allow policies can selectively permit traffic from specific Pods on specific ports
  • The whitelist approach (default-deny + explicit allows) is a security best practice

results matching ""

    No results matching ""