Inherit nginx x509 header config, add operator runbook

- LookupConfig: ssl-client-cert / ssl-cert-chain-prefix /
  certificate-chain-length now fall back to the built-in nginx
  provider's settings (Config scope, then KC_SPI_X509CERT_LOOKUP_NGINX_*
  env vars), so switching the provider to "hybrid" needs no duplicated
  Helm values. Explicit hybrid-scope settings still override.
- Add LookupConfigTest covering inheritance, override, and defaults
  (14 tests total, all passing).
- Add RUNBOOK.md: phase-by-phase deployment procedure sized to 30-min
  test windows, with verification checklists, forged-header negative
  test, rollback per phase, and failure triage table.
- README/Dockerfile.example updated to match; stop tracking target/
  build output (.gitignore added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 14:06:38 -07:00
parent cc9b0a37f2
commit 73701b4a34
23 changed files with 318 additions and 113 deletions

View File

@@ -27,6 +27,9 @@ Per-request decision logic:
No changes are needed to your realm's X509 browser authentication flow — the
authenticator receives the cert the same way regardless of source.
> **Deploying this?** Follow the step-by-step operator guide in [RUNBOOK.md](RUNBOOK.md).
> This README is the reference (config options, security rationale, troubleshooting).
---
## 1. Build
@@ -71,9 +74,11 @@ Do NOT use `required`.
# Switch the lookup provider from nginx to hybrid
KC_SPI_X509CERT_LOOKUP_PROVIDER=hybrid
# Header carrying the user cert — COPY THE VALUE from your existing
# KC_SPI_X509CERT_LOOKUP_NGINX_SSL_CLIENT_CERT (default "ssl-client-cert")
KC_SPI_X509CERT_LOOKUP_HYBRID_SSL_CLIENT_CERT=ssl-client-cert
# Header name: NOTHING TO ADD. The hybrid provider inherits ssl-client-cert,
# ssl-cert-chain-prefix and certificate-chain-length from your existing
# KC_SPI_X509CERT_LOOKUP_NGINX_* settings in the Helm values — leave them in place.
# (Set KC_SPI_X509CERT_LOOKUP_HYBRID_SSL_CLIENT_CERT only to override; the startup
# log line "inherited from the nginx provider config" confirms what was picked up.)
# Day-one: permissive mode (headers accepted from anyone, like today, but everything logged)
KC_SPI_X509CERT_LOOKUP_HYBRID_TRUST_MODE=log
@@ -94,9 +99,9 @@ All options (prefix `spi-x509cert-lookup-hybrid-`):
| Option | Default | Meaning |
|---|---|---|
| `ssl-client-cert` | `ssl-client-cert` | Header with the user cert (PEM, URL-encoded PEM, or base64 DER — auto-detected) |
| `ssl-cert-chain-prefix` | `ssl-cert-chain` | Optional chain headers `<prefix>-0..n` |
| `certificate-chain-length` | `1` | Max chain headers to read |
| `ssl-client-cert` | inherits nginx setting, else `ssl-client-cert` | Header with the user cert (PEM, URL-encoded PEM, or base64 DER — auto-detected) |
| `ssl-cert-chain-prefix` | inherits nginx setting, else `ssl-cert-chain` | Optional chain headers `<prefix>-0..n` |
| `certificate-chain-length` | inherits nginx setting, else `1` | Max chain headers to read |
| `trust-mode` | `log` | `log` = honor all headers, warn on untrusted; `enforce` = ignore headers from untrusted peers |
| `trusted-proxy-cert-sha256` | — | Comma-separated SHA-256 fingerprints of the F5's client cert(s). **Strongest check.** List old+new during rotations. |
| `trusted-proxy-subject-dn` | — | `\|`-separated exact subject DNs (RFC2253). Safe because TLS already validated the chain. Survives rotation. |
@@ -115,15 +120,16 @@ browsers use to filter the cert-picker dialog.
## 4. Rollout / test plan (one 30-min cycle each, in order)
**Cycle 0 — no deploy, info gathering.** From the current deployment grab:
`KC_SPI_X509CERT_LOOKUP_NGINX_SSL_CLIENT_CERT` (header name), truststore config, base image
tag, and whether `start --optimized` is used. Ask the F5 team to start on the one-pager
(section 6) in parallel.
**Cycle 0 — no deploy, info gathering.** From the current deployment grab: truststore
config, base image tag, and whether `start --optimized` is used. (The header name is
inherited automatically from the existing `KC_SPI_X509CERT_LOOKUP_NGINX_*` values — no
need to look it up.) Ask the F5 team to start on the one-pager (section 6) in parallel.
**Cycle 1 — deploy in log mode (zero behavior change expected).**
Jar + `KC_HTTPS_CLIENT_AUTH=request` + provider=hybrid + `trust-mode=log`.
Jar + `KC_HTTPS_CLIENT_AUTH=request` + provider=hybrid + `trust-mode=log`; keep all
existing `KC_SPI_X509CERT_LOOKUP_NGINX_*` values in the Helm values file untouched.
Verify: existing F5 CAC login still works. Then grep logs for `x509-hybrid`:
- startup line shows the parsed config;
- startup lines show the parsed config, including which nginx settings were inherited;
- each F5 login logs `remoteAddr=` (→ your CIDR value) and `peer=` (→ `no-tls-client-cert`
until the F5 presents one);
- `header cert decoded using strategy '...'` (DEBUG) confirms the F5's encoding.