Frontend mTLS for AI Services¶
Illustrative mutation fragments
The management mutations on this page are non-standalone fragments. Before adapting them, follow this site's Example Quality Contract and complete Quickstart lifecycle. Do not treat HTTP success or management readback alone as runtime proof.
The current active mTLS path can verify client certificates on the frontend of
a FullProxy service. Configure it through the mtls_frontend object nested in
serviceArguments; there is no standalone /config/mtls endpoint.
Current implementation boundary
The OpenAPI model also declares backend-mTLS and inline certificate-data
fields. The current data-plane wiring applies only the frontend file-path
fields documented below. Do not depend on mtls_backend,
client_ca_cert_data, client_cert_data, or client_key_data for
enforcement. API acceptance or read-back does not prove those fields are
active.
No runnable AI-specific CICD scenario currently qualifies this path. Validate the exact immutable build with a real TLS client and backend before production.
Request flow¶
flowchart LR
C["Client certificate"] -->|"TLS handshake"| G["Gateway FullProxy<br/>frontend verification"]
G -->|"security 1: HTTP"| H["HTTP backend"]
G -->|"security 2: separate TLS connection"| T["TLS backend"]
style G fill:#e8f5e9,stroke:#43a047
style H fill:#fff3e0,stroke:#f57c00
style T fill:#fff3e0,stroke:#f57c00
Frontend mTLS requires mode: 4 and security: 1 or security: 2. Security
mode 1 terminates frontend TLS and uses HTTP to the backend; mode 2
re-originates a separate backend TLS connection. The frontend certificate
check does not prove backend-server identity under mode 2, because the
declared backend verification configuration is not currently wired.
Active frontend fields¶
| Field | Default | Current behavior |
|---|---|---|
client_cert_mode |
disabled |
optional verifies a presented certificate; required rejects a client without a valid certificate |
client_ca_path |
unset | Absolute path to the PEM CA bundle used for client-certificate verification |
require_client_cn |
false |
Enables the additional common-name pattern check |
client_cn_pattern |
unset | Pattern checked when require_client_cn is true |
client_crl_path |
unset | Path to an operator-supplied PEM CRL used for leaf-certificate revocation checks |
Mount certificate files read-only, grant access only to the Gateway process, and rotate them through the organization's secret-management procedure. Do not send PEM or private-key material in REST payloads.
Configure a staging service¶
Set GATEWAY_API to the protected management origin and obtain
GATEWAY_TOKEN through the approved identity workflow:
install -m 600 /dev/null ./control-plane.headers
printf 'Authorization: Bearer %s\n' "$GATEWAY_TOKEN" > ./control-plane.headers
curl --fail-with-body --silent --show-error \
--request POST \
--header 'Content-Type: application/json' \
--header @control-plane.headers \
--data '{
"serviceArguments": {
"externalIP": "192.0.2.10",
"port": 443,
"protocol": "tcp",
"mode": 4,
"security": 1,
"mtls_frontend": {
"client_cert_mode": "required",
"client_ca_path": "/run/secrets/loxilb/client-ca.crt",
"require_client_cn": true,
"client_cn_pattern": "client.example.test",
"client_crl_path": "/run/secrets/loxilb/client.crl"
}
},
"endpoints": [
{"endpointIP": "198.51.100.20", "targetPort": 8080, "weight": 1}
]
}' \
"$GATEWAY_API/netlox/v1/config/loadbalancer"
The addresses are RFC 5737 documentation ranges; replace them with staging
addresses. Use security: 1 here so the recipe tests only the currently wired
frontend control and does not imply backend certificate verification.
Verify enforcement¶
Read-back confirms the control-plane object, not the TLS behavior:
curl --fail-with-body --silent --show-error \
--header @control-plane.headers \
"$GATEWAY_API/netlox/v1/config/loadbalancer/all" | jq .
Then perform both handshake tests against the staging VIP:
# Expected to succeed with a valid, non-revoked certificate and matching CN.
curl --fail-with-body --silent --show-error \
--cert /run/secrets/client/client.crt \
--key /run/secrets/client/client.key \
--cacert /run/secrets/client/vip-ca.crt \
https://192.0.2.10/v1/models
# Expected to fail during TLS negotiation because no client certificate is sent.
curl --fail-with-body --silent --show-error \
--cacert /run/secrets/client/vip-ca.crt \
https://192.0.2.10/v1/models
Also test an untrusted issuer, expired certificate, mismatched CN, and revoked leaf certificate. Capture only sanitized outcomes; never record private keys or unredacted certificates in public evidence.
Troubleshooting¶
| Symptom | Check |
|---|---|
| Frontend settings appear in read-back but are not enforced | Confirm the build includes mTLS, mode is 4, security is 1 or 2, and the mounted paths exist inside the container |
| Every client is rejected | Verify the CA chain, client intermediates, file permissions, validity period, and clock |
| CN check rejects a valid certificate | Confirm require_client_cn and the exact certificate CN/pattern; SAN matching is not a substitute for this specific check |
| Revoked client is admitted | Confirm client_crl_path is readable and contains a current PEM CRL covering the leaf certificate |
| Backend certificate is not verified | Expected current limitation; mtls_backend is declared but not wired into the active data path |
Release qualification boundary¶
Production qualification requires frontend positive/negative handshake tests, certificate rotation, CRL refresh, restart, resource-load, and rollback tests on the exact image. Backend mTLS and inline certificate data remain documentation/API-contract surfaces until implementation and end-to-end tests prove enforcement.