Build a gRPC-style RPC framework (14 scenes)
Scene 11 · mTLS and propagating identity
TLS encrypts and proves the server; mTLS proves both peers with a workload identity. The original caller's identity must propagate across hops, just like a deadline.
Previously
We can now move bytes fast, fairly, and resiliently — but we've never asked who is on the other end of the wire; and because no hop shares memory with the one before it, each hop must carry both a proof of who it is AND the original caller's identity forward, exactly the way it carried the deadline.
Scene 11
mTLS and propagating identity
Diagram
One call drawn as two SEPARATE things — a padlock (the channel is encrypted) and a badge (a per-call token in the metadata) — followed by a 3-hop A→B→C chain. Under plain TLS the server can only see the caller's IP; with mutual certificates both peers display a cryptographically verified workload identity; with identity propagation on, the original caller's identity is forwarded down the chain so the leaf can decide based on who STARTED the call, not just its immediate caller.
padlock = the channel is encrypted
badge = a per-call token (separate thing)
← workload identity: a verified name, both sides
First, separate two ideas people constantly conflate. Encrypting the wire — what HTTPS does — scrambles the bytes so an eavesdropper can't read them, and it proves the SERVER is who it claims (your browser checks the server's certificate). That's the *padlock*. A *per-call token* is a different thing entirely: a credential placed in the call's metadata that says 'this specific call is allowed.' That's the *badge*. The diagram draws them as two distinct objects on purpose. Now the gap: under plain TLS the badge and padlock prove the server to the client, but the server has no proof of who the CLIENT is — it sees only an IP address. When we make *both* sides present a certificate, each peer gets a cryptographically verified name we'll call its **workload identity** — a stable proof like `prod/frontend` of which service this is, not just where it's connecting from. Watch the slider move from TLS to that mutual-cert mode.
Implementation
Channel.establish
the TLS handshake — and whether the client also proves itself
1def establish(server_addr):2 conn = tls_handshake(server_addr)3 # server's cert always verified by the client4 conn.server_id = verify_cert(conn.server_cert)5 if mutual_tls:6 # client ALSO presents a cert: both sides named7 present_cert(conn, self.cert) # e.g. SPIFFE/SVID8 conn.client_id = self.workload_identity9 else:10 conn.client_id = None # caller is just a peer IP11 return conn
Client.call
what rides in the call's metadata: a token, and the origin
1def call(conn, method, req, ctx):2 md = {}3 md['authorization'] = mint_token() # per-call badge4 if propagate_identity:5 # carry who STARTED the call, like grpc-timeout does6 md['origin-principal'] = ctx.origin or self.id7 # HTTP/2 HEADERS frame carries the metadata8 return conn.invoke(method, req, metadata=md)
Server.authorize
the leaf decides on who called — origin or just the last hop
1def authorize(conn, md, action):2 caller = conn.client_id # verified by mTLS3 origin = md.get('origin-principal')4 if origin is not None:5 # authorize on who STARTED the call6 return policy.allow(origin, action)7 # no origin forwarded: fall back to immediate caller8 return policy.allow(caller, action)