open atlas
↑ Back to track
Security Foundations SECF · 04 · 02

mTLS and service identity

Ordinary TLS proves the server to the client. mTLS makes both peers present certificates, turning a cryptographic identity into the unit of authorization between services — and making short-lived certs and automated rotation the hard part.

SECF Middle ◷ 15 min
Level
FoundationsJuniorMiddleSenior

A payments service trusts the orders service because requests arrive on the cluster’s private network with a shared API key in the header. One compromised pod later, an attacker is already inside that network, holds the key from a leaked env var, and replays it from a workload that has no business calling payments. Every check the payments service ran — source IP in the right CIDR, valid bearer token — passed. The network told it “this came from a trusted place,” and that was the whole authorization story. The fix isn’t a better firewall rule. It’s making each service prove who it is with a key only it holds, on every connection, in both directions.

By the end of this lesson you’ll know how mTLS turns a cryptographic certificate into a service’s identity, why short-lived certs and automated rotation are the real engineering work, and where mTLS stops and policy must take over.

From “the server is real” to “both sides are real”

Ordinary TLS is one-directional authentication. During the handshake the server sends its certificate, the client validates it against a trusted CA, and from then on the client knows it is talking to the real api.example.com. The server, however, learns nothing verifiable about the client — anyone who can reach the port gets a TLS session. That asymmetry is fine for a browser hitting a public site, because a human logs in afterward. It is a hole for service-to-service traffic, where there is no human and the “login” has historically been a bearer token or a network ACL — both of which an attacker who is already inside the perimeter can steal or satisfy.

mTLS (mutual TLS) closes the asymmetry. The TLS 1.3 handshake (RFC 8446) supports a CertificateRequest: after the server authenticates, it asks the client to present a certificate too, and the client proves possession of the matching private key with a CertificateVerify signature over the handshake transcript. Now both ends have cryptographically proven identity before a single byte of application data flows. Critically, the private key never crosses the wire — the peer proves it holds the key by signing, so observing or replaying the handshake gives an attacker nothing.

This is the load-bearing shift for a zero-trust network (NIST SP 800-207): trust is no longer a property of where a packet came from, but of what identity presented a valid credential. The private network stops being a trust boundary. A pod that gets popped still cannot impersonate the orders service, because it does not hold the orders service’s private key.

What the certificate actually identifies

A certificate authenticates an identity, but for services the identity is not a hostname a browser would type — it is a workload. The dominant convention here is SPIFFE: the workload’s identity is a URI encoded in the certificate’s Subject Alternative Name (SAN), a SPIFFE ID like spiffe://prod.example/ns/payments/sa/orders. That string says: in the prod.example trust domain, the orders service account in the payments namespace. When the orders service connects to payments, payments reads the verified SAN, sees a SPIFFE ID it recognizes, and then decides whether that identity is allowed to do what it’s asking.

That last clause is the part engineers skip. mTLS gives you authentication — a strong, unforgeable answer to “who is this?” It does not give you authorization — “is this identity allowed to do this?” A valid certificate from inside your trust domain proves the caller is a legitimate workload; it says nothing about whether that workload should be allowed to issue refunds. The complete design is: mTLS establishes identity at the connection layer, and a policy layer (SPIFFE-aware authorization, an Istio AuthorizationPolicy, an OPA check) makes the per-request allow/deny decision against that identity. Conflating the two — “the cert validated, so let it through” — is how teams build a flat, fully-trusted mesh where any compromised workload can call anything.

Why this works

Why not just keep using bearer tokens between services? A bearer token is, by definition, a credential anyone holding it can replay — it carries no proof of who is presenting it. Leak it in a log, an env var, a misrouted request, and it’s a skeleton key until it expires (often hours). An mTLS client certificate is bound to a private key that never leaves the workload; possession is proven by signing the live handshake, so there’s nothing replayable to steal off the wire. Tokens answer “is this string valid?”; mTLS answers “did the holder of this specific key participate in this specific handshake?” — a much narrower, much harder thing to forge.

The hard part: rotation at scale

The cryptography of mTLS is solved; the operations are where it lives or dies. If every service needs a certificate and a private key, you now run a certificate authority and a distribution pipeline for thousands of short-lived credentials. The senior instinct is to make certs short-lived — hours, not years. A cert that lives one hour shrinks the window in which a stolen key is useful and makes revocation almost a non-problem: you don’t fight the notoriously unreliable CRL/OCSP revocation machinery, you just let the cert expire and don’t reissue to a workload you’ve cut off. Expiry becomes your revocation.

But short-lived certs only work if rotation is fully automated and overlapping. This is the failure mode that pages people: a renewal job dies, certs expire at 03:00, and suddenly every service rejects every peer at once — a self-inflicted, cluster-wide outage with no attacker involved. The defenses are: rotate well before expiry (renew at ~50–66% of lifetime so a failed renewal has hours of runway), reload the new cert without dropping live connections, and make trust-anchor (CA) rotation a separate, slower, overlap-based process where both old and new roots are trusted simultaneously during the cutover. In practice a service mesh (Istio, Linkerd) or SPIFFE’s SPIRE runs this loop for you — issuing, distributing, and rotating workload certs through a sidecar or agent so application code never touches a key. The tradeoff you’re buying: real per-workload identity in exchange for operating a high-availability CA whose outage is now a production outage.

ConcernNetwork ACL / shared tokenmTLS + workload identity
Unit of trustSource IP / possession of a stringCryptographic identity (SPIFFE ID in SAN)
Stolen credentialReplayable for the token’s whole lifetimeKey never leaves workload; nothing on the wire to replay
Compromised podInside the network → trusted by defaultStill can’t present another service’s identity
RevocationRotate the shared secret everywhereLet short-lived cert expire; stop reissuing
Main operational costFirewall sprawl; secret distributionRun an HA CA + automated overlapping rotation

How a senior reasons about adopting it

mTLS is not free, and the honest framing is a tradeoff, not a silver bullet. You gain identity-based trust that survives a network breach and turns lateral movement from “free inside the perimeter” into “still need each service’s private key.” You pay with a CA that must be highly available (its outage is your outage), an automated rotation loop you cannot let fail silently, and a real CPU/latency cost on every handshake — which is why you terminate mTLS at a sidecar and keep sessions long-lived to amortize it. And you must remember the boundary: mTLS authenticates the workload, never the end user. The human’s identity (their session, their scopes) still rides inside the request at the application layer; mTLS secures the pipe and proves the caller’s service identity, but a service trusted to call you can still carry a forged or over-scoped user claim if you don’t check it.

Pick the best fit

Your services already do mTLS via the mesh: every connection presents a valid workload cert from your trust domain. A new requirement says only the `orders` service may call `POST /refunds` on `payments`. What's the correct way to enforce it?

Quiz

In the mTLS handshake, what stops an attacker who has fully recorded a legitimate client's handshake from replaying it to impersonate that client?

Quiz

A team makes workload certs valid for 2 years 'so we don't have to deal with rotation.' Why is this the wrong call for an mTLS mesh?

Order the steps

Order the steps of an mTLS service-to-service connection, from opening the handshake to making the access decision:

  1. 1 Client opens a TLS 1.3 connection to the server
  2. 2 Server presents its certificate and a CertificateRequest
  3. 3 Client presents its own cert + a CertificateVerify signature proving key possession
  4. 4 Both peers validate the presented certs against the trusted CA
  5. 5 Policy layer authorizes the request against the verified SPIFFE identity
Recall before you leave
  1. 01
    Explain how mTLS changes the unit of trust for service-to-service traffic, and why a compromised pod inside the network still can't impersonate another service.
  2. 02
    Why are short-lived certificates and automated rotation the hard part of mTLS, and what's the failure mode that takes down a whole cluster?
Recap

Ordinary TLS proves only the server to the client, so service-to-service traffic falls back to trusting network position or a replayable bearer token — both of which an inside attacker defeats. mTLS adds a CertificateRequest/CertificateVerify exchange so both peers prove identity by signing the live handshake with a private key that never leaves the workload, shifting the unit of trust from “where the packet came from” to “what identity presented a valid credential” — the heart of a zero-trust network. For services, that identity is a workload, typically a SPIFFE ID carried in the certificate’s SAN. Crucially, mTLS only authenticates; authorization stays a separate policy layer that decides whether the verified identity may take the action. The cryptography is easy — the engineering is operating a highly available CA and an automated, overlapping rotation loop, with certs kept short-lived so expiry replaces fragile revocation and a stolen key dies in hours. So when someone says “we have mTLS, so we’re zero-trust,” your first question is: does anything actually authorize the verified identity per request, or did you just prove who’s calling and then trust them with everything?

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 6 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.