Introduction
Figure 1: Cilium Zero Trust Architecture across Kubernetes worker nodes with eBPF-based policy enforcement and WireGuard encryption
In today’s cloud-native landscape, the traditional perimeter-based security model is no longer sufficient. With microservices communicating across dynamic, ephemeral environments, the zero trust model has become the gold standard for Kubernetes security. The core principle is simple: never trust, always verify. No workload should be implicitly trusted, regardless of its network location.
Cilium is an open-source CNI (Container Network Interface) plugin that leverages eBPF (Extended Berkeley Packet Filter) to provide highly scalable networking, security, and observability for Kubernetes. Unlike traditional iptables-based approaches, Cilium applies security policies at the kernel level with minimal overhead, making it ideal for enforcing zero trust principles at scale.
In this tutorial, you will learn how to:
- Install Cilium on a Kubernetes cluster
- Implement zero trust network policies using Cilium’s identity-based security model
- Enforce Layer 7 (HTTP/gRPC) policies for fine-grained API-level control
- Encrypt inter-node traffic with Cilium’s WireGuard integration
- Monitor and audit network flows with Hubble
- Apply practical zero trust patterns to a sample microservices application
📖 Prerequisites
This tutorial assumes you have a working Kubernetes cluster (v1.24+) and kubectl configured. A local cluster created with kind or minikube works fine for testing. You’ll also need the cilium CLI tool (v1.15+).
Understanding Zero Trust in Kubernetes
Zero trust architecture (ZTA) is built on three foundational principles:
- Verify explicitly — Always authenticate and authorize based on all available data points (identity, context, workload labels).
- Use least-privileged access — Limit access with just-in-time and just-enough-access (JIT/JEA).
- Assume breach — Segment access, encrypt all traffic, and continuously monitor for anomalies.
In a Kubernetes context, this translates to:
- Default deny: All ingress and egress traffic is blocked unless explicitly allowed.
- Identity-based policies: Policies are based on workload identity (labels/service accounts), not IP addresses.
- Layer 7 awareness: Policies can inspect HTTP methods, paths, and even gRPC calls.
- Encryption in transit: All pod-to-pod traffic is encrypted, including east-west traffic within the cluster.
Cilium excels at all of these because eBPF allows it to see and control traffic at multiple layers of the network stack without modifying application code.
Step 1: Installing Cilium on Your Cluster
Let’s start by installing Cilium. The recommended way is using the cilium-cli tool, which handles Helm chart installation and validates the setup.
1.1 Install the Cilium CLI
# Download the latest Cilium CLI
curl -L --remote-name-all https://github.com/cilium/cilium-cli/releases/latest/download/cilium-linux-amd64.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-amd64.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz{,.sha256sum}
Verify the installation:
cilium version
# Expected output: cilium-cli: v0.16.x (or newer)
1.2 Install Cilium on Your Cluster
Install Cilium with encryption enabled and Hubble (the observability layer):
cilium install \
--set encryption.type=wireguard \
--set hubble.relay.enabled=true \
--set hubble.ui.enabled=true
# Wait for all pods to be ready
cilium status --wait
This command enables WireGuard-based encryption for all pod-to-pod traffic, implements zero trust at the transport layer, and enables Hubble for network observability.
Verify that all Cilium pods are running:
kubectl -n kube-system get pods -l k8s-app=cilium
# NAME READY STATUS RESTARTS AGE
# cilium-xxxxx 1/1 Running 0 2m
1.3 Deploy the Hubble UI
cilium hubble enable --ui
# Port-forward to access the UI (in a separate terminal)
cilium hubble ui
# Opens at http://localhost:12000
💡 Tip
Hubble gives you a visual service map showing all pod-to-pod connections, policy decisions (allowed vs. denied), and DNS queries. This is invaluable for auditing your zero trust posture.
Step 2: Applying a Default Deny Policy
Before we allow any traffic, we must establish a default deny posture. CiliumNetworkPolicy (CNP) is the custom resource definition (CRD) that Cilium uses.
2.1 Default Deny Ingress on All Pods
cat <
This policy matches all pods in the default namespace (note the empty matchLabels) and applies an empty ingress rule — which means no ingress traffic is allowed.
2.2 Default Deny Egress on All Pods
cat <
After applying these two policies, all inbound and outbound traffic is blocked for every pod in the default namespace. This is the starting point for zero trust.
⚠️ Important
Do not apply this on a production cluster without first defining allow rules for essential services like kube-dns. In this tutorial, we'll add explicit allow rules step by step.
Step 3: Deploying a Sample Application
Let's deploy a three-tier microservices application to demonstrate zero trust policies in action:
- frontend: A Node.js web app (port 8080)
- api: A Go REST API (port 5000)
- db: Redis cache (port 6379)
cat <
Verify the pods are running:
kubectl -n demo get pods
# NAME READY STATUS RESTARTS AGE
# frontend 1/1 Running 0 30s
# api 1/1 Running 0 30s
# db 1/1 Running 0 30s
Step 4: Implementing Zero Trust Policies with Cilium
Now let's define explicit, least-privilege policies that follow the zero trust model.
Figure 2: Policy enforcement flow from default deny through least-privilege allow rules with Hubble monitoring
4.1 Allow DNS Egress (Essential for All Pods)
Pods need DNS resolution to discover services. We allow egress to port 53 on kube-dns using identity-based selection:
cat <
4.2 Frontend → API Policy (Identity-Based)
Allow only the frontend to reach the api on port 5000:
cat <
4.3 API → Database Policy
Allow only the api to reach db on port 6379:
cat <
4.4 Allow Ingress to Frontend
Allow ingress traffic to the frontend from within the namespace (or from an ingress controller):
cat <
Let's verify that the policies are enforced:
# Test frontend can reach api
kubectl -n demo exec frontend -- wget -qO- http://api-svc:5000/get | head -5
# ✅ Should return JSON response
# Test frontend CANNOT reach db directly
kubectl -n demo exec frontend -- wget -qO- http://db-svc:6379 2>&1
# ❌ Connection refused / timeout — policy is enforced!
# Test api CAN reach db
kubectl -n demo exec api -- wget -qO- --timeout=2 http://db-svc:6379 2>&1
# ✅ Service response (Redis hates plain HTTP but connection is established)
Step 5: Layer 7 (HTTP-Aware) Policies
Cilium's true power is Layer 7 policy enforcement. You can restrict traffic based on HTTP methods, paths, and headers without any sidecar proxies.
5.1 Enable Layer 7 Policy Enforcement
First, enable L7 proxy in Cilium:
cilium config set --restart proxy-http-enabled true
5.2 Apply an HTTP-Aware Policy
Allow the frontend to only GET /get and POST /post endpoints on the API:
cat <
Now test the Layer 7 enforcement:
# ✅ Allowed: GET /get
kubectl -n demo exec frontend -- wget -qO- http://api-svc:5000/get
# ✅ Allowed: POST /post
kubectl -n demo exec frontend -- wget -qO- --post-data='{"key":"value"}' http://api-svc:5000/post
# ❌ Denied: GET / (root path)
kubectl -n demo exec frontend -- wget -qO- http://api-svc:5000/ 2>&1
# "Access denied" — Layer 7 policy enforced!
# ❌ Denied: DELETE /get (wrong method)
kubectl -n demo exec frontend -- wget -qO- --method=DELETE http://api-svc:5000/get 2>&1
# "Access denied"
📝 Note
Layer 7 policies in Cilium are enforced by a proxy (Envoy) that is automatically injected at the endpoint level. This means no sidecar containers to manage — eBPF handles the traffic interception seamlessly.
Step 6: Encrypting All Pod Traffic with WireGuard
If you installed Cilium with --set encryption.type=wireguard, encryption is already enabled. Let's verify:
# Check WireGuard status on any Cilium node pod
kubectl -n kube-system exec ds/cilium -- cilium encrypt status
# Expected: Wireguard is enabled
Cilium's WireGuard integration ensures that all pod-to-pod traffic across nodes is encrypted using WireGuard tunnels. This covers east-west traffic that traditional perimeter security often misses.
If you didn't enable it during install, you can patch the Cilium config:
cilium config set encryption-type wireguard
cilium config set encrypt-node true
With this enabled, even if an attacker gains access to the underlying network fabric, they cannot read pod-to-pod traffic — a fundamental zero trust requirement.
Step 7: Monitoring with Hubble
Hubble provides deep observability into network flows and policy decisions. Let's use it to audit our zero trust enforcement.
7.1 Using Hubble CLI
# Enable the Hubble client
cilium hubble enable
# Port forward to the Hubble relay
kubectl -n kube-system port-forward svc/hubble-relay 4245:80 &
# Watch live flows
hubble observe --namespace demo --not ip=127.0.0.1
# See flows dropped by policy
hubble observe --namespace demo --verdict DROPPED
# Observe HTTP layer data
hubble observe --namespace demo --protocol http
7.2 Using Hubble UI
Access the Hubble UI (port-forwarded earlier) at http://localhost:12000. You'll see:
- A service map showing all connections between workloads
- Green lines for allowed flows, red lines for denied ones
- L7 details showing HTTP methods, paths, and response codes
- DNS visibility — every DNS query made by pods
Step 8: Advanced Zero Trust Patterns
8.1 Cluster-Wide Default Deny with CiliumClusterwideNetworkPolicy
For production, use CiliumClusterwideNetworkPolicy (CCNP) to enforce default deny across all namespaces:
cat <
8.2 Combining with Kubernetes Service Accounts
For stronger identity, bind policies to Kubernetes service accounts instead of pod labels:
cat <
8.3 FQDN-Based Egress Policies
Allow pods to reach specific external APIs by domain name:
cat <
Verifying Your Zero Trust Posture
Use this checklist to validate your implementation:
| Check | Command | Expected Result |
|---|---|---|
| Default deny enforced | kubectl -n demo exec frontend -- wget http://db-svc:6379 |
Connection refused / timeout |
| Identity-based policies | Allowed flows work by label, not IP | Identical behavior after pod restart |
| L7 policies active | hubble observe --verdict DROPPED |
HTTP-level denials visible |
| Encryption enabled | cilium encrypt status |
WireGuard: Enabled |
| No unknown flows | hubble observe --namespace demo |
Only expected flows visible |
Conclusion
Implementing a zero trust network model in Kubernetes is no longer a luxury — it is a necessity. Cilium, powered by eBPF, provides a production-grade, high-performance foundation for zero trust that goes far beyond what standard Kubernetes NetworkPolicy can offer.
In this tutorial, you learned:
- How to install Cilium with WireGuard encryption and Hubble observability
- How to apply default deny policies as the foundation of zero trust
- How to create identity-based, least-privilege allow policies
- How to enforce Layer 7 (HTTP-aware) policies without sidecars
- How to monitor and audit all network flows with Hubble
- Advanced patterns including cluster-wide policies and FQDN-based egress
By adopting these patterns, your Kubernetes workloads are protected against lateral movement, data exfiltration, and unauthorized access — even if an attacker breaches the cluster perimeter.
🏫 Next Steps
Explore Cilium's official documentation for advanced topics like Cilium Service Mesh, mutual TLS (mTLS) automatic injection, and cluster mesh for multi-cluster zero trust.
Clean Up
To remove the demo resources:
kubectl delete namespace demo
kubectl delete ciliumnetworkpolicies --all
kubectl delete ciliumclusterwidenetworkpolicies --all