Mutual TLS (mTLS) can require every REST caller to present a client certificate issued by a configured private CA. OpenMed validates the client chain again at the application boundary, exposes the verified certificate identity to handlers, and can map an exact subject or subject alternative name (SAN) to the principal and scopes used by route authorization.
mTLS is disabled by default. Enable it without changing application code:
OPENMED_SERVICE_MTLS_ENABLED=true \
OPENMED_SERVICE_MTLS_CA_BUNDLE=/etc/openmed/client-ca.pem \
uvicorn openmed.service.app:app --host 127.0.0.1 --port 8080OPENMED_SERVICE_MTLS_CA_BUNDLE must contain at least one trusted CA
certificate. It must not contain a production private key. Online CRL and OCSP
checking are not performed; certificate issuance and revocation infrastructure
remain the deployer's responsibility.
For direct TLS, use an ASGI server that implements the ASGI TLS extension and
places the leaf-first PEM client chain in
scope["extensions"]["tls"]["client_cert_chain"]. Configure the server to
require client certificates against the same CA bundle. For example, the
corresponding Uvicorn transport flags are:
uvicorn openmed.service.app:app \
--host 0.0.0.0 \
--port 8443 \
--ssl-keyfile /etc/openmed/server-key.pem \
--ssl-certfile /etc/openmed/server-cert.pem \
--ssl-cert-reqs 2 \
--ssl-ca-certs /etc/openmed/client-ca.pemThe transport must both enforce the TLS handshake and expose the verified
chain through the ASGI TLS extension. If the server does not expose that
extension, OpenMed cannot recover the peer certificate from an HTTP request and
returns mtls_certificate_required; use a compatible server or the trusted
proxy pattern below.
A sidecar or ingress may terminate mTLS and forward a URL-escaped PEM client chain in a dedicated header. Configure both the header name and the exact proxy addresses or CIDR networks allowed to supply it:
OPENMED_SERVICE_MTLS_ENABLED=true \
OPENMED_SERVICE_MTLS_CA_BUNDLE=/etc/openmed/client-ca.pem \
OPENMED_SERVICE_MTLS_CLIENT_CERT_HEADER=X-OpenMed-Client-Cert \
OPENMED_SERVICE_MTLS_TRUSTED_PROXIES=127.0.0.1/32,10.42.0.0/16 \
uvicorn openmed.service.app:app --host 127.0.0.1 --port 8080 --no-proxy-headersThe proxy must:
- remove the client-supplied
X-OpenMed-Client-Certheader; - require and verify a client certificate at its TLS listener;
- URL-escape the leaf-first PEM chain into the configured header; and
- connect to OpenMed only from an address in the trusted-proxy list.
OpenMed rejects the header from any other peer and independently verifies the
forwarded chain against OPENMED_SERVICE_MTLS_CA_BUNDLE. Do not configure a
public or shared proxy network as trusted. The ASGI scope["client"] value must
remain the transport peer address used for this check. Disable generic proxy
header rewriting as shown above, or configure the server so untrusted
X-Forwarded-For input cannot replace that peer address.
Verified certificate details are available to handlers as
request.state.mtls_identity:
subject: the RFC 4514 distinguished name;sans: typed values such asuri:spiffe://openmed.test/clinic-api,dns:clinic-api.internal, orip:10.42.0.8; andfingerprint_sha256: the lowercase leaf-certificate fingerprint.
Use exact subject or SAN values to assign a stable, non-PHI service principal and route scopes:
OPENMED_SERVICE_MTLS_PRINCIPALS='[
{
"identities": ["uri:spiffe://clinic.example/workload/openmed-client"],
"principal": "clinic-api",
"scopes": ["analyze:write", "pii:read", "pii:write", "models:read"]
}
]'The mapped AuthPrincipal is available as request.state.auth_principal and
request.scope["openmed.auth"]. When no mapping matches, OpenMed uses
mtls:<sha256-fingerprint> with no scopes. This authenticates the certificate
without silently granting application permissions.
mTLS protects the connection and supplies a workload identity. REST
authentication remains independently configurable with
OPENMED_SERVICE_AUTH_ENABLED=true. An explicit JWT or API key takes
precedence for route authorization, while request.state.mtls_identity
continues to identify the certificate-bearing workload. Without an explicit
credential, the mapped mTLS principal is used, so only scopes granted by
OPENMED_SERVICE_MTLS_PRINCIPALS can authorize scoped routes.
To require JWT on normal application routes, leave the mTLS principal without route scopes and configure JWT keys and route scopes as described in REST Service Authentication. The TLS certificate is still required before the JWT is evaluated.
Client certificate PEM, subject, SANs, and fingerprint are never written to
access logs. A successful mTLS request adds only the configured principal (or
the fallback fingerprint-based service identifier) as identity, plus
credential_type=mtls. Use machine and workload identities in certificates;
do not put patient names, record identifiers, or other PHI in certificate
subjects, SANs, or principal mappings.