How to Implement Zero-Trust Workload Identity in Kubernetes with SPIFFE, SPIRE, and Cilium
Destiny Erhabor
Your network policy says: allow traffic from 10.0.1.45.
Yesterday, 10.0.1.45 was your payment service. Today, after a rolling deployment, it's your logging agent. Your payment service is now at 10.0.1.89.
Kubernetes has already updated all the endpoints and service records — but your network policy has no idea. It silently allows traffic through based on an IP address that no longer belongs to the workload you intended to trust.
This is the workload identity problem. IP addresses aren't an identity, they're a location. And in a Kubernetes cluster, location changes constantly. Building security policy on top of IP addresses means your security posture silently degrades every time a pod is scheduled, rescheduled, or scaled.
The answer is cryptographic workload identity: every workload gets a certificate-backed identity that proves who it is, not where it is. Services authenticate each other using those certificates before exchanging any data. If the certificate doesn't match, the connection is refused, regardless of what IP address it came from.
This is what SPIFFE and SPIRE provide. And this is how Cilium enforces it using eBPF, without injecting a sidecar into every pod.
In this article you'll understand how the SPIFFE identity model works, deploy SPIRE to issue cryptographic identities to workloads, and use Cilium's built-in SPIRE integration to enforce mutual TLS between services without touching your application code.
Prerequisites
Familiarity with Kubernetes RBAC and pod security — this handbook covers the foundations
Familiarity with TLS certificates and Kubernetes Secrets — this handbook covers cert-manager and certificate concepts
Helm 3 and the Cilium CLI installed
A kind cluster — you'll create a fresh one with Cilium as the CNI in this article
Patience: this is the most complex demo I've covered in this group of articles. SPIRE has more moving parts than anything else covered so far.
All demo files are in the companion GitHub repository.
Table of Contents
Prerequisites
The Workload Identity Problem
How SPIFFE Works
SPIFFE IDs and Trust Domains
SVIDs: The Cryptographic Identity Document
The Trust Bundle
How SPIRE Works
SPIRE Server and SPIRE Agent
Node Attestation
Workload Attestation
SVID Issuance and Rotation
How Cilium Implements Mutual TLS with SPIFFE
Demo 1 — Install Cilium with SPIRE Integration
Step 1: Install the Cilium CLI
Step 2: Create a kind cluster without a default CNI
Step 3: Install Cilium with SPIRE enabled
Step 4: Verify the installation
Demo 2 — Enforce Mutual TLS with a CiliumNetworkPolicy
Step 1: Deploy a client and server
Step 2: Confirm traffic flows without authentication
Step 3: Apply a CiliumNetworkPolicy requiring mutual authentication
Step 4: Verify authenticated traffic still flows
Step 5: Observe the authentication with Hubble (optional)
Step 6: Verify that a pod without the matching label is blocked
Step 7: Check the workload entries in SPIRE
Conclusion
Cleanup (kind)
The Workload Identity Problem
The opening scenario isn't theoretical. In Kubernetes, pods are ephemeral. The scheduler can place a pod on any node, and a pod's IP address is assigned at scheduling time from the node's IP pool.
When a pod is deleted and recreated through a rolling deployment, a node drain, or an autoscaler event, it gets a new IP address. If you've written a NetworkPolicy that says, "allow traffic from this IP", that policy is now pointing at nothing, or worse, at a different workload.
Kubernetes service names help here for east-west traffic — a Service name resolves consistently regardless of which pods back it. But a NetworkPolicy based on a Service name is still a label selector match, not a cryptographic assertion. Any pod that can spoof the right labels can bypass it.
What you actually want is this: before service A sends a request to service B, service B proves its identity cryptographically. If service B can't prove it is who it claims to be, service A refuses the connection. This is mutual TLS, and the key question is: where do the identities come from?
SPIFFE answers that question.
How SPIFFE Works
SPIFFE — Secure Production Identity Framework for Everyone — is a CNCF standard that defines a model for workload identity. It doesn't implement anything by itself. It specifies the format of identities, the API for requesting them, and the trust model that makes them verifiable across services, clusters, and clouds. SPIRE is the reference implementation of that specification.
SPIFFE IDs and Trust Domains
A SPIFFE identity is a URI with a specific format:
spiffe:///
The trust domain is a string that identifies the administrative boundary — typically your organisation, cluster, or environment. Everything within the same trust domain can verify each other's identities. Identities from different trust domains require explicit...