-
Notifications
You must be signed in to change notification settings - Fork 15
examples/mtls-identity: demonstrate cert-SAN identity with axum + a helper for parity with standalone Server #49
Description
PR #31 added PeerCerts and PeerAddr to Context::extensions, automatically populated by the standalone Server from the rustls accept loop. Handlers can read peer identity (cert SANs, remote addr) without caring about transport - on the standalone Server.
For axum users, this is currently a do-it-yourself path: axum::serve(listener, app) assumes a plain TcpListener, so a custom rustls accept loop is needed to extract peer_certificates() and inject it into request extensions per-connection.
This issue tracks two related pieces of work.
Part 1: examples/mtls-identity/ example
A new example crate demonstrating mTLS + cert-SAN-based identity extraction in an axum app. Mirrors the existing examples/middleware/ shape but swaps bearer-token auth for mTLS:
- Server uses an axum router behind a rustls TLS terminator that requires client certificates (
WebPkiClientVerifier) - Per-connection layer captures
PeerCertsandPeerAddr, injects them into request extensions - Handler reads identity by parsing the leaf cert's SANs from
ctx.extensions.get::<PeerCerts>()rather than from a bearer token - Test certs generated at runtime via
rcgen(no PEM files in the repo) - Integration test exercises authorized + permission-denied paths
The example would show how to bridge axum's listener model with connectrpc's peer-identity convention, so handlers stay transport-agnostic.
Part 2: Helper API for axum + connectrpc identity parity
Today, the standalone Server::with_tls magically populates PeerCerts/PeerAddr for handlers. Axum users have to write the rustls accept loop and tower-layer wiring themselves. The same handler code that reads ctx.extensions.get::<PeerCerts>() won't work on axum without that custom wiring.
Proposal: ship a thin helper from connectrpc that does the rustls accept loop + extension injection for axum users. Sketch:
// In connectrpc::axum (behind the `axum` + `server-tls` features)
pub async fn serve_tls(
listener: tokio::net::TcpListener,
app: axum::Router,
tls_config: Arc<rustls::ServerConfig>,
) -> Result<(), BoxError> {
// Custom accept loop:
// 1. TCP accept
// 2. tokio_rustls handshake
// 3. Extract peer_certificates() + peer_addr()
// 4. Wrap each connection in a hyper service that
// injects PeerCerts/PeerAddr into req.extensions before
// handing off to the axum service
}With this helper:
// Plaintext - existing axum convention
axum::serve(listener, app).await?;
// TLS with peer-identity passthrough - drop-in replacement
connectrpc::axum::serve_tls(listener, app, tls_config).await?;The same handler code reads ctx.extensions.get::<PeerCerts>() regardless of whether the server was built with the standalone Server or axum + this helper. Hosting choice and identity extraction are now orthogonal.
Alternatives considered
- Tower layer: a layer can't extract peer certs from the rustls connection unless something earlier in the stack already exposed them - the layer runs at the HTTP-request level, not the TCP/TLS-connection level. So the helper has to be at the listener/accept-loop level, not a layer.
- Document the manual pattern in the example only: doesn't scale - every axum + mTLS user reimplements the same accept loop and per-connection injection layer.
- Push the work to
axum-serverintegration: adds a transitive dependency for axum users; the accept loop is small enough to ship in connectrpc directly.
Suggested order
Land Part 2 (the helper) first so the example in Part 1 can use the public API rather than reimplementing it inline. Both can ship in the same release.
References
- Expose mTLS peer credentials and remote address to handlers #31
Expose mTLS peer credentials and remote address to handlers(the originalPeerCerts/PeerAddrwork) - Add streaming-tour and middleware examples #46
Add streaming-tour and middleware examples(the existing middleware example this would complement) connectrpc/src/server.rs:577for the existing peer-cert extraction logic in the standalone Server