How does Istio set up mTLS between services automatically?
Quick Answer
Istio automates mTLS through istiod, which acts as a certificate authority and issues SPIFFE-based certificates to each Envoy sidecar. The sidecars handle TLS handshakes for all service-to-service traffic automatically, encrypting everything without any code changes.
Detailed Answer
Think of Istio's automatic mTLS like a corporate building where every employee gets a smart badge at the door. When two employees from different departments need to exchange documents, they both tap their badges to verify each other's identity before anything changes hands. Neither employee had to apply for the badge or learn how the verification system works -- building security (Istio) handles badge issuance, verification, and renewal entirely behind the scenes.
Mutual TLS (mTLS) means both the client and server prove their identity to each other using X.509 certificates during the TLS handshake. This is different from regular TLS, where only the server proves who it is. Istio makes this happen transparently through its sidecar architecture. When a service sends a request to another service, the source Envoy proxy starts a TLS connection with the destination Envoy proxy. Both proxies show their certificates, verify each other's identity, and set up an encrypted channel. The application containers talk to their local Envoy sidecars over localhost in plain text, and all traffic between pods is encrypted. Services get mutual authentication and encryption without importing any TLS libraries, managing certificates, or changing a single line of code.
Under the hood, the process starts with the Citadel component inside istiod acting as a Certificate Authority (CA). When a new pod starts up, the Envoy sidecar uses the Kubernetes Service Account token to identify itself to istiod through a Certificate Signing Request (CSR). Istiod checks the identity against the Kubernetes API, generates a short-lived X.509 certificate with a SPIFFE identity baked in (like spiffe://cluster.local/ns/namespace/sa/service-account), and delivers it to the Envoy sidecar through the Secret Discovery Service (SDS). These certificates have a configurable lifespan, typically 24 hours, and get automatically renewed before they expire. The SPIFFE identity in the certificate enables identity-based authorization policies -- you can define which services are allowed to talk to each other based on their service account identities rather than IP addresses, which change constantly in Kubernetes.
In production, Istio offers three PeerAuthentication modes. STRICT mode enforces mTLS for all traffic and rejects any unencrypted connections. PERMISSIVE mode accepts both mTLS and plaintext, which is essential during migration when not all services have sidecars yet. DISABLE mode turns off mTLS entirely. Best practice is to start with PERMISSIVE mode across the mesh, switch to STRICT mode one namespace at a time, and finally enforce STRICT mode globally. You should also set up AuthorizationPolicy resources to build zero-trust networking, where services are denied all traffic by default and explicitly allowed only the communication paths they need. Combining PeerAuthentication with AuthorizationPolicy gives you both transport-level encryption and application-level access control.
A big gotcha is that mTLS only protects traffic between services that both have Envoy sidecars. If a service outside the mesh (like a legacy app or an external API) tries to talk to a mesh service running in STRICT mode, the connection fails because the external service cannot present a valid mesh certificate. This is a common cause of outages during Istio rollouts. Another pitfall is that PERMISSIVE mode can silently accept unencrypted traffic, giving a false sense of security. Always use istioctl authn tls-check to verify the actual mTLS status of connections instead of assuming the config matches reality. Certificate rotation failures, while rare, can cause service outages if SDS connectivity to istiod is disrupted.