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-dnsin thekube-systemnamespace. 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
- 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.
- Label your Pods consistently — NetworkPolicies rely on labels. Use a consistent labeling scheme (e.g.,
app,tier,env) across all workloads. - 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.
- Test policies before production — Use temporary test Pods to verify that policies allow expected traffic and block unexpected traffic.
- 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
podSelectorblocks 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