This example demonstrates keyless mTLS for gRPC using the Double-Ended Sidecar Proxy pattern with NGINX and Azure Managed HSM.
┌──────────────────────────────────────────────────────────────────┐
│ HOST │
│ │
│ ┌─────────────┐ UDS ┌────────────────────────────────┐ │
│ │ gRPC Client │ ────────► │ NGINX Client Sidecar │ │
│ │ (plaintext) │ │ - Initiates mTLS │ │
│ └─────────────┘ │ - Client cert via HSM │ │
│ └────────────┬───────────────────┘ │
│ │ │
│ │ mTLS (port 50051) │
│ │ │
│ ┌────────────▼───────────────────┐ │
│ ┌─────────────┐ UDS │ NGINX Server Sidecar │ │
│ │ gRPC Server │ ◄──────── │ - Terminates mTLS │ │
│ │ (plaintext) │ │ - Server cert via HSM │ │
│ └─────────────┘ │ - Verifies client cert │ │
│ └────────────────────────────────┘ │
│ │ │
└──────────────────────────────────────────┼────────────────────────┘
│
▼
┌────────────────────────┐
│ Azure Managed HSM │
│ (private key ops) │
└────────────────────────┘
Key Feature: The same RSA key in Azure Managed HSM is used for both client and server certificates (different certificate identities).
- Azure Managed HSM with an RSA key
- Azure CLI authenticated (
az login) - NGINX with stream module and SSL support (1.27+ for OSSL_STORE)
- Rust toolchain
- OpenSSL >= 3.0.7 — Ubuntu 22.04's bundled 3.0.2 has an
OSSL_STOREcallback bug that breaks HSM key loading; use Ubuntu 24.04+ or build OpenSSL from source. See openssl#18221. - Provider built:
../target/release/libakv_provider.so
# 1. Copy and configure environment
cp .env.example .env
# Edit .env with your HSM settings
# 2. Generate certificates (uses HSM for signing)
./generate-certs.sh
# 3. Start the demo (NGINX sidecars + gRPC server)
./start-demo.sh
# 4. Run the client (in another terminal)
./run-client.sh
# 5. Stop everything
./stop-demo.shgrpc-example/
├── src/
│ ├── server.rs # gRPC server (supports TCP or UDS)
│ └── client.rs # gRPC client (supports TCP or UDS)
├── proto/
│ └── greeter.proto # gRPC service definition
├── nginx/
│ ├── nginx-server.conf # Server sidecar template
│ └── nginx-client.conf # Client sidecar template
├── certs/ # Generated certificates
│ ├── ca.crt # CA certificate
│ ├── server.crt # Server certificate
│ └── client.crt # Client certificate
├── .env.example # Environment template
├── generate-certs.sh # Certificate generation script
├── start-demo.sh # Start the demo
├── stop-demo.sh # Stop the demo
├── run-client.sh # Run client via sidecar
└── grpc-mtls-sidecar.md # Design document
# Terminal 1: Start server on TCP
cargo run --release --bin grpc-server
# Listens on [::1]:50051
# Terminal 2: Run client
cargo run --release --bin grpc-client# Start everything
./start-demo.sh
# Run client through sidecar
./run-client.sh| Variable | Description | Default |
|---|---|---|
GRPC_UDS_PATH |
Unix socket path | (uses TCP if not set) |
GRPC_ADDR |
TCP address (if no UDS) | [::1]:50051 |
HSM_NAME |
HSM vault name | ManagedHSMOpenSSLEngine |
HSM_KEY_NAME |
HSM key name | myrsakey |
- gRPC Server listens on a Unix Domain Socket (
run/grpc-server.sock) - NGINX Server Sidecar terminates mTLS on port 50051 and forwards plaintext to the server UDS
- NGINX Client Sidecar listens on a UDS (
run/grpc-client.sock) and initiates mTLS to port 50051 - gRPC Client connects to the client sidecar UDS (plaintext)
All TLS private key operations are performed by the Azure Managed HSM via the OpenSSL provider.
# NGINX logs
tail -f logs/nginx-server-error.log
tail -f logs/nginx-client-error.log
# Provider logs
tail -f logs/akv-provider.log- "Provider not found": Build the provider first:
cd .. && cargo build --release - "Access token expired": Re-run
az loginand restart demo - "Socket already in use": Run
./stop-demo.shto clean up
See grpc-mtls-sidecar.md for the full design rationale.