mTLS & pod-to-pod encryption

Identity and encryption between services.

Advanced10 min · lesson 16 of 24

Pod-to-pod traffic rides the cluster network in plaintext by default, so anything that can sniff the wire or insert itself in the path can read and tamper with it — and the flat network means a compromised pod is often already in a position to try. Mutual TLS solves two problems in one handshake: it encrypts the connection, and it gives both ends a cryptographically verifiable identity, so payments-api knows the caller really is web and not an impostor that merely reached its port.

How sidecars encrypt pod-to-pod traffic
POD A POD B Mesh CA / SPIFFE issues short-lived SVID certs app unaware sidecar proxy Envoy · mTLS sidecar proxy Envoy · mTLS app unaware mTLS encrypted both present + verify certs SVID SVID plaintext plaintext
plaintext app ↔ sidecar (in-pod) encrypted mTLS tunnel between sidecars cert issuance SPIFFE / mesh CA
The apps stay plaintext-unaware; the sidecars mutually authenticate and encrypt everything on the wire.

The usual way to get mTLS everywhere without touching application code is a service mesh — Istio or Linkerd inject a sidecar proxy beside each pod that transparently wraps every connection. You enable it per namespace and set the mode to STRICT so plaintext is refused rather than merely tolerated; PERMISSIVE (the migration default) accepts both, which is a stepping stone, not a destination.

peerauth.yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata: { name: default, namespace: payments }
spec:
mtls:
mode: STRICT # refuse any non-mTLS traffic in this namespace

Identity, not just a tunnel

The real value of mTLS is the identity it carries. Meshes build on SPIFFE — every workload gets a short-lived certificate encoding an identity (a SPIFFE ID), issued and rotated automatically by the mesh CA (SPIRE, or the mesh’s built-in one). Because each connection is authenticated to a specific workload identity, you can then write authorization in terms of who is calling, not which IP.

authz.yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata: { name: payments-allow-web, namespace: payments }
spec:
selector: { matchLabels: { app: payments-api } }
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/storefront/sa/web"] # SPIFFE identity

Without a mesh

No mesh means mTLS is the application’s job: issue certificates (cert-manager is the common in-cluster CA), mount them into the pods, and have the client verify the server and the server verify the client. It is more wiring and more to rotate, but the guarantee is identical — encrypted, mutually authenticated connections between named workloads.

mTLS authenticates; it does not authorize
STRICT mTLS proves who is calling; it does not decide what they may do — every authenticated workload can still reach every other one until you add rules. Pair it with mesh AuthorizationPolicy and with NetworkPolicy at the L3/L4 layer, so identity and reachability are both constrained.