An edge proxy authenticates callers with mutual TLS, and the application behind it needs to know which client certificate was presented. How should that identity reach the application, and what is the classic way this arrangement is defeated?
answer
- the handshake ends at the proxy
- identity becomes an assertion, not evidence
- strip inbound, always, unconditionally
- who else can open that port
- proxy client cert makes the path cryptographic
basics
~20 sThe proxy verifies the client certificate and then asserts the resulting identity to the application in a request header, because the certificate itself cannot survive termination. That assertion is defeated whenever the header is not stripped from inbound requests or the application is reachable without going through the proxy.
solid answer
~50 sMutual TLS authenticates a connection, and a terminating proxy consumes that connection — so the certificate cannot be forwarded, only *reported*. The proxy validates the chain, then injects the identity it verified into a request header: the subject or SAN, a fingerprint, or the escaped certificate itself. Envoy's `x-forwarded-client-cert` is the widely copied format. The whole scheme rests on two invariants. First, every ingress hop must overwrite or delete that header on inbound requests, unconditionally, so a client can never supply its own. Second, the application must be unreachable except through the proxy — otherwise an attacker who can open a socket to it directly sends any identity they like. Enforce reachability with network policy and, better, by re-encrypting from the proxy with its own client certificate so the app requires mTLS from the proxy specifically. If the application must verify the end client's certificate itself, do not terminate in front of it at all — pass the connection through.
code
bash · 2 linescurl -sS -H 'x-forwarded-client-cert: By=spiffe://cluster/ns/edge;Hash=0f2a;Subject="CN=payments-admin,O=Acme"' \
http://orders.internal:8080/admin/refundsgo deeper
Understand that mutual TLS authenticates the connection, so when a proxy terminates it the backend gets the proxy's word about the caller rather than the certificate itself.
Explain how the identity travels — a request header carrying the verified subject, SAN or fingerprint — and why that header must be overwritten on every inbound request rather than merely added when a certificate was seen.
Demonstrate the whole trust path: strip at the boundary, prove the peer with a proxy client certificate on a re-encrypted hop, alert on client-certificate expiry, and recognise when a requirement forces passthrough instead.
Set the standard others build against: where client identity is verified, in what form it is asserted downstream, whether a signed short-lived assertion replaces a bare header, and how the platform proves the invariant still holds a year after someone edits a firewall rule.
## Termination consumes the authentication In mutual TLS the client proves possession of a private key during the handshake. That proof is bound to *that connection* and to the handshake transcript; it cannot be copied, replayed, or handed onward. So when a proxy terminates, the client's authenticated connection ends there. Whatever the application learns about the caller is now an *assertion made by the proxy*, not evidence the application can check. That is not a flaw — centralising certificate validation at the edge is usually the point. But it changes the security question from "is this certificate valid?" to "do I trust the entity telling me about it, and can anyone else pretend to be that entity?" ## Carrying the identity The proxy validates the chain, revocation status and whatever name constraints you require, then puts the result in a request header on the forwarded request. Common contents are the certificate's subject distinguished name, a SAN entry such as a URI or DNS name, a fingerprint, or the whole certificate escaped for the application to parse. The de-facto format is Envoy's `x-forwarded-client-cert`, a list of key-value pairs including `By`, `Hash`, `Subject`, `URI` and `DNS`, which several other proxies and meshes emit or consume. An application reading that header should be conservative: pin to the specific field it cares about, require an exact match against an allowlist of expected identities rather than a substring test on a distinguished name, and treat a missing header as "unauthenticated", never as "internal, therefore fine". ## The two ways it breaks **The header is not stripped.** If the proxy only *adds* the header when a certificate was presented, and passes it through otherwise, any client can send the header itself and the application will believe it. The rule is unconditional: at the trust boundary, delete or overwrite the identity header on every inbound request before evaluating anything. "Append if present" is not good enough — the application usually reads the first or last element, and an attacker who knows which one wins controls it. **The application is directly reachable.** Even a perfectly stripping proxy is irrelevant if the application's port answers to anything else on the network. Anyone who reaches it — a compromised neighbouring workload, a developer's jump host, a mis-scoped firewall rule, a service that follows a redirect to an internal address — sends a forged header straight to the app: ``` curl -H 'x-forwarded-client-cert: Subject="CN=payments-admin"' \ http://orders.internal:8080/admin/refund ``` No certificate, no proxy, full impersonation. This is why the header approach is only as strong as the network path, and network paths are exactly the thing that quietly changes over two years of platform evolution. ## Making the path enforceable Order the defences from weakest to strongest: 1. **Network policy or firewalling** so only the proxy's addresses may open the app's port. Real, but it is an assertion about topology, and it fails open when someone adds a rule. 2. **Re-encrypt from the proxy with the proxy's own client certificate**, and have the application *require* mutual TLS on its listener. Now the app authenticates its immediate peer cryptographically: the identity header is trustworthy because only a peer holding the proxy's key could have sent it. This is the answer that survives a network change, and it is why re-encryption and mTLS between hops belong in the same conversation. 3. **A signed assertion rather than a bare header** — the proxy mints a short-lived token naming the verified client, which the application validates against the proxy's public key. This survives an extra hop and does not depend on the header being stripped, at the cost of running a signing key and clock discipline. ## When to not terminate at all Sometimes the application genuinely must see the client certificate — a regulatory requirement that the authenticating endpoint is the application itself, or a protocol that binds credentials to the client's TLS key. No amount of header forwarding satisfies that, because the binding cannot be delegated. Then the honest answer is passthrough: the proxy forwards bytes, the application completes the mutual handshake, and you give up edge routing and inspection for that route. Being able to state that trade — and to say which requirement forces it — is what separates a good answer here from a list of headers. ## Operating it Two practical notes. Client certificates expire, and unlike server certificates nobody visits the site and notices; track client-certificate expiry per caller and alert well ahead, because the failure mode is a partner's integration dying at 3am. And decide up front what revocation means for you — a proxy that never checks revocation is issuing lifetime credentials, so short-lived client certificates are usually a better lever than a revocation infrastructure you will not maintain.
- Why is stripping the identity header on inbound requests not sufficient on its own?Stripping protects the path through the proxy. It does nothing about a request that never passes through it. If the application's port answers to anything else on the network, that caller sets the header itself and is believed. You need both: strip at the boundary, and make the application refuse peers that are not the proxy.
- How does re-encrypting from the proxy with its own client certificate strengthen the arrangement?It turns "only the proxy can reach this app" from a network assertion into a cryptographic one. The application requires mutual TLS on its listener and accepts only the proxy's identity, so any forged identity header arriving from elsewhere never gets past the handshake. It also encrypts the hop, which you likely wanted anyway.
- A requirement says the application itself must authenticate the client certificate. What does that rule out?Every terminating topology. A proxy that completes the handshake has consumed the proof and can only report what it saw, which is a delegation, not verification by the application. Satisfying the requirement means passing the connection through untouched — and accepting that the edge can no longer route on path, insert headers, cache or retry for that route.
- What operational surprise do client certificates bring that server certificates do not?Silent expiry. No user sees a browser warning for a client certificate; a partner's calls simply start failing with handshake errors, usually outside business hours. Inventory client certificates per caller with expiry alerting, and prefer short lifetimes with automated renewal over long-lived credentials plus a revocation system nobody exercises.
saying these in an interview costs you the question
- Believes the client certificate is forwarded to the backend
- Adds the identity header only when present, without stripping
- Trusts the header because the network is internal
- Substring-matches a distinguished name for authorization
- Thinks mTLS at the edge means the app is doing mTLS