Certified Kubernetes Security Specialist (CKS) #12 Pod-to-Pod mTLS: Cilium
In CKS #11 we covered how to isolate containers at the kernel level with gVisor and Kata Containers. If isolation was about shrinking the blast radius of a single Pod, this post is about protecting the communication that travels between Pods. As the final topic of the Minimize Microservice Vulnerabilities domain, we’ll lock in encryption in transit and its flagship implementation, mTLS.
Kubernetes’ default network is flat. Without extra configuration, packets a Pod sends to another Pod flow between nodes as plaintext. Being inside the same cluster doesn’t make it safe. An attacker who can eavesdrop on inter-node traffic, or an intruder who has established a foothold in the cluster network, reads plaintext packets as-is. This post organizes, concept-first, how you cover this plaintext problem with encryption, and how that approach differs from NetworkPolicy.
Why plaintext Pod-to-Pod communication is dangerous #
In a microservice architecture, a single request passes through several Pods. The frontend Pod asks the auth service, the auth service asks the user DB, and it hands off again to the payment service. The data traveling between these contains sensitive values like session tokens, passwords, and payment information.
In a default cluster, this traffic is not encrypted. The risk becomes clear when you picture situations like these.
- Inter-node eavesdropping. Depending on the CNI, Pod traffic flows between nodes as plaintext. An attacker who can capture packets at the physical network or cloud VPC level reads the contents as-is.
- Lateral movement. Once an attacker takes over one Pod, they sniff plaintext traffic headed for other services on the same network, or connect directly to internal services with no identity verification.
- Absence of identity. Plaintext communication doesn’t verify who the peer is. An IP address alone can’t guarantee “is this really that service.” IPs can be spoofed or reassigned.
The NetworkPolicy we covered in CKS #2 controls, among these, who can connect to whom. But NetworkPolicy does not encrypt the contents of an allowed connection. Even permitted communication still travels as plaintext packets. This is where encryption in transit becomes necessary.
NetworkPolicy and mTLS are controls at different layers #
The point most easily confused in CKS is the division of roles between these two. Let’s lay it out in a table first.
| Aspect | NetworkPolicy | mTLS |
|---|---|---|
| Operating layer | L3/L4 (IP/port) | Closer to L7 (connection/session) |
| What it controls | Allowing/blocking connections | Encryption + mutual identity verification |
| What it blocks | Disallowed communication itself | Eavesdropping, tampering, identity spoofing |
| As an analogy | A lock that opens and closes a door | A sealed envelope + ID check |
| Sufficient on its own | No | No |
NetworkPolicy decides whether you can connect, and mTLS encrypts the established communication and verifies both identities. The two are not substitutes but complements. Narrowing communication with default deny (NetworkPolicy) and encrypting the remaining communication while binding identity to it (mTLS) are the two pillars of in-transit security.
What mTLS is #
TLS is one-way identity verification, where the client verifies the server’s certificate. The HTTPS in which a web browser trusts a server falls under this. mTLS (mutual TLS) goes one step further: both sides verify each other’s certificate. The client also presents its own certificate, and the server checks that certificate too.
Inside Kubernetes, mTLS provides two things at once.
- Encryption. Traffic between two Pods is encrypted with TLS, so even if it’s intercepted in the middle, the contents can’t be read.
- Workload identity. A certificate is issued to each workload, so “is this service really the payment service” is verified by certificate rather than by IP. This identity is often expressed through a standard like SPIFFE.
Approach 1: Service Mesh sidecar mTLS #
The most widely known way to implement Pod-to-Pod mTLS is Service Mesh. The flagship implementations are Istio and Linkerd.
The core idea is to attach a proxy container (sidecar) next to each Pod. The application container believes it communicates in plaintext as usual, but in reality the sidecar proxy in the same Pod intercepts the traffic and exchanges it with the peer Pod’s sidecar over mTLS.
[ app A ] --plaintext--> [ sidecar A ] ==mTLS encryption==> [ sidecar B ] --plaintext--> [ app B ]The characteristics of this structure are as follows.
- No application code changes. Developers don’t handle TLS directly. The mesh handles mTLS transparently through sidecar injection.
- Automatic certificate management. The mesh’s control plane issues a certificate to each workload and rotates it on a short cycle. No human needs to create certificates.
- L7 policy and observability. Beyond mTLS, it also provides features like traffic routing, retries, L7 authorization policy, and distributed tracing.
In Istio, the policy that enforces mTLS across the entire mesh looks, conceptually, like the following.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT # reject plaintext, allow mTLS onlySTRICT mode accepts communication between sidecar-equipped workloads only over mTLS. During a transition period, it’s common to start with PERMISSIVE (allowing both plaintext and mTLS) and gradually move to STRICT.
The cost of the sidecar approach #
Service Mesh is powerful, but it comes at a price. Since one more proxy container is attached per Pod, memory/CPU overhead arises, and since traffic passes through the proxy one more time, latency increases. A new operational target — the control plane — also appears. So “do you only need encryption, or do you need L7 features too” becomes the fork in the road for the adoption decision.
Approach 2: Cilium’s transparent encryption #
If encryption is the main goal, there’s a way to encrypt inter-node traffic wholesale at the CNI layer without a mesh sidecar. This is Cilium’s transparent encryption.
Cilium is an eBPF-based CNI, and you can turn on encryption right in the data plane that processes NetworkPolicy. It offers two backends.
| Backend | Characteristics |
|---|---|
| WireGuard | Simple to configure and lightweight. The recommended modern approach |
| IPsec | An older standard. Fits certain regulatory/compatibility requirements |
The crux of transparent encryption is, as the name says, that it’s transparent. Neither the application nor the Pod manifest changes. Cilium automatically encrypts Pod traffic leaving a node and decrypts it at the receiving node. Since there’s no sidecar, there’s also no per-Pod proxy overhead.
[ Pod A on node1 ] --> Cilium (encrypt) ==WireGuard tunnel==> Cilium (decrypt) --> [ Pod B on node2 ]WireGuard transparent encryption is, conceptually, turned on with about one line in the Cilium configuration. It varies by installation tool, but expressed in Helm values it takes a form like the following.
# At Cilium install time (Helm values concept)
encryption:
enabled: true
type: wireguardOnce it’s on, Pod traffic traveling between nodes is encrypted with WireGuard. The key effect is that the inter-node eavesdropping threat disappears.
Cilium transparent encryption vs mTLS #
One distinction matters here. WireGuard/IPsec transparent encryption is node-to-node encryption. It encrypts traffic, but by itself it doesn’t provide the workload-level mutual identity verification that mTLS does. It blocks eavesdropping, but verifying “who is the peer workload of this connection, really” by certificate is a separate matter.
So Cilium has separately pushed forward functionality in the mTLS direction. It introduces SPIFFE-based workload identity, moving toward handling not just encryption but also identity-based policy without a sidecar. To summarize:
- WireGuard/IPsec transparent encryption: focuses on encrypting inter-node traffic. The goal is eavesdropping prevention.
- Cilium mTLS/SPIFFE identity: workload-level identity verification and policy. A sidecar-less mTLS direction.
From a CKS perspective, it’s enough to know that there are two branches of choice — “a sidecar mesh (Istio/Linkerd), or CNI-native encryption (Cilium)” — and to be able to distinguish how each handles encryption and identity.
One line of L7 control in Cilium #
Cilium expresses L7-level control too, with CiliumNetworkPolicy, which extends the standard NetworkPolicy. For example, let’s look at just one fragment of a policy that, conceptually, allows only HTTP GET /metrics for a specific service.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-metrics-only
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/metrics"Where standard NetworkPolicy controlled only down to the port, CiliumNetworkPolicy controls L7 attributes like the HTTP method and path as well. It’s a different axis from encryption/identity, but it’s an example that shows how far Cilium reaches beyond L3/L4 when we talk about in-transit security.
CKS perspective: what you need to know #
The depth CKS demands on this topic is concepts and judgment, not fine-grained mesh configuration. You’re fine if you can make the following clear.
- Why it’s needed. Pod-to-Pod communication is plaintext by default and is exposed to inter-node eavesdropping and lateral movement. Encryption in transit covers this threat.
- What’s different. NetworkPolicy is connection allow/block (L3/L4); mTLS is encryption and mutual identity verification. The two are complementary.
- What approaches exist. The mTLS of a sidecar-based Service Mesh (Istio/Linkerd), CNI-native transparent encryption (Cilium WireGuard/IPsec), and Cilium’s mTLS/SPIFFE direction.
- What the trade-off is. The mesh is powerful but carries sidecar overhead and operational burden; transparent encryption is lightweight but by itself doesn’t provide workload identity verification.
Exam points #
- Default Pod-to-Pod communication is plaintext. Being in the same cluster doesn’t mean it’s encrypted.
- The flagship implementation of encryption in transit is mTLS. mTLS provides encryption and mutual identity verification together.
- Distinguish between one-way TLS and mTLS. In mTLS the client also presents a certificate.
- NetworkPolicy (L3/L4) and mTLS (encryption/identity) are at different layers. NetworkPolicy only allows/blocks communication; it doesn’t encrypt the contents. The two are used together.
- Service Mesh (Istio/Linkerd) handles mTLS transparently with sidecar proxies and no application changes, and issues/renews certificates automatically. The price is overhead and operational burden.
- Cilium encrypts inter-node Pod traffic without a sidecar via WireGuard/IPsec transparent encryption. This is inter-node encryption; workload identity verification is separate, in the SPIFFE-based mTLS direction.
- Remember the difference in Istio’s
PeerAuthentication:STRICTallows only mTLS, whilePERMISSIVEallows both plaintext and mTLS.
Wrap-up #
What this post locked in:
- Pod-to-Pod communication is plaintext by default. It’s exposed to inter-node eavesdropping and lateral movement, so encryption in transit is necessary.
- mTLS provides encryption and mutual workload identity verification together. Distinguish it from one-way TLS.
- NetworkPolicy and mTLS are complements. One controls the connection; the other encrypts the controlled connection and binds identity to it.
- Two approaches. A sidecar-based Service Mesh (Istio/Linkerd) and CNI-native transparent encryption (Cilium WireGuard/IPsec), plus Cilium’s mTLS/SPIFFE direction.
- CKS asks about concepts and trade-offs rather than detailed configuration. You need to be able to explain why you encrypt, what’s different, and what the cost is.
With this we wrap up the Minimize Microservice Vulnerabilities domain (#9–#12). With PSA blocking dangerous Pods, encrypting Secrets, isolating workloads, and encrypting communication, we’ve shrunk the microservice attack surface along four branches.
Next: Minimal images #
The controls surrounding the workload end here. The next domain, Supply Chain Security, moves on to making the container image itself secure.
In #13 Minimal images: distroless, scratch (Supply Chain), we’ll build it ourselves and organize how to shrink the attack surface itself by reducing the packages that go into an image to a minimum, the difference between distroless and scratch base images, why an image with the shell and package manager removed is safer, and the pattern of making the final image light with a multi-stage build.