From 389f6b8233c747761a095c3adca3259cc1888ac7 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Mon, 27 Apr 2026 01:55:04 +0000 Subject: [PATCH] Bundle F follow-up: M-023 doc env-var cleanup (G-3 guard fix) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI on the bundle-F merge (run #24972730564) failed the G-3 env-var docs guardrail because docs/legacy-est-scep.md mentioned CERTCTL_EST_PROXY_TRUSTED_SOURCES CERTCTL_EST_TRUST_PROXY_CLIENT_CERT_HEADER which are documented as future-feature env vars but don't exist in config.go. The G-3 guard treats any env-var name in docs that's not either defined in source OR on the documented integration-surface allowlist as drift. The runbook's 'certctl-side configuration' section was over-promising features that haven't shipped yet. Rewritten to be honest: - Current implementation is header-agnostic (X-SSL-Client-Cert is ignored). EST/SCEP authentication still works correctly because both protocols carry their own auth (CSR signature for EST, challengePassword for SCEP) inside the request body. - The reverse proxy is purely a TLS-version bridge. - Future-feature description retained in prose form (without literal env-var names) so an operator who needs proxy-supplied client identity knows to open an issue. The nginx config block's comment was also rewritten to reflect the header-agnostic default. The proxy still SETS the headers (cheap, no-op when ignored); a future commit can flip certctl to read them behind a fail-closed CIDR allowlist + opt-in toggle. Verification: grep -rnE 'CERTCTL_EST_PROXY|CERTCTL_EST_TRUST' README.md docs/ deploy/helm/ — empty (G-3 guard now passes for these names) --- docs/legacy-est-scep.md | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/docs/legacy-est-scep.md b/docs/legacy-est-scep.md index 8bfff2b..f46e97e 100644 --- a/docs/legacy-est-scep.md +++ b/docs/legacy-est-scep.md @@ -80,10 +80,12 @@ server { location ~ ^/\.well-known/(est|pki) { # Forward the client cert (if presented) to certctl over the - # private hop. certctl's EST handler reads X-SSL-Client-Cert - # only when the connection's source IP is in - # CERTCTL_EST_PROXY_TRUSTED_SOURCES — without that allowlist - # the header is ignored to prevent spoofing. + # private hop. The current certctl implementation IGNORES the + # X-SSL-Client-Cert header (header-agnostic by default — see + # the certctl-side configuration section below). EST/SCEP + # authentication still works correctly because both protocols + # carry their own auth (CSR signature for EST, challengePassword + # for SCEP) inside the request body. proxy_set_header X-SSL-Client-Cert $ssl_client_escaped_cert; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; @@ -132,26 +134,34 @@ backend certctl_backend ## certctl-side configuration -Two env vars on the certctl process control the proxy-trust contract: +The current implementation is **header-agnostic**: certctl ignores any +`X-SSL-Client-Cert` / `X-Forwarded-For` headers from the proxy. EST +authentication still happens via in-protocol CSR signature + profile +policy (RFC 7030 §3.2.3); SCEP authentication still happens via the +`challengePassword` attribute embedded in the CSR (RFC 8894 §3.2). Both +mechanisms are inside the request body and survive the reverse-proxy +hop without server-side header trust. -``` -# Comma-separated CIDR ranges that certctl will trust to set -# X-SSL-Client-Cert and X-Forwarded-For headers. Any other source has -# those headers stripped before reaching the EST/SCEP handlers. -# Default: empty (no proxy trust — header-spoofing attempt = 403). -CERTCTL_EST_PROXY_TRUSTED_SOURCES=10.0.0.0/24 +**Why this is the correct default:** trusting a proxy-supplied header +for client identity opens a header-spoofing attack surface that requires +careful design (CIDR allowlist of trusted proxies, fail-closed defaults, +explicit operator opt-in). The Bundle F closure of M-023 ships the +TLS-bridge guidance as documentation only; a future commit can extend +certctl with proxy-header trust if and when an operator demonstrates a +deployment shape that requires it. Until that lands, the runbook above +is operationally complete: legacy EST and SCEP clients continue to +authenticate via their in-protocol mechanisms, and the reverse proxy is +purely a TLS-version bridge. -# When set, the certctl EST handler treats X-SSL-Client-Cert as -# authoritative for client identity (instead of requiring an inbound -# mTLS handshake). MUST be paired with CERTCTL_EST_PROXY_TRUSTED_SOURCES. -CERTCTL_EST_TRUST_PROXY_CLIENT_CERT_HEADER=true -``` - -The two-key contract is intentional: setting `TRUST_PROXY_CLIENT_CERT_HEADER` -without a non-empty `TRUSTED_SOURCES` is rejected at startup with a -fail-loud error. Spoofing the `X-SSL-Client-Cert` header is the obvious -attack against this configuration and the dual-knob design forces an -operator to think about it. +If your deployment requires proxy-supplied client identity (e.g., the +proxy terminates mTLS and you want certctl to record the client-cert +subject in the audit trail beyond what the CSR carries), open an issue +and a future commit will add a header-trust contract behind two +fail-closed env vars: a CIDR allowlist of trusted proxies, plus an +explicit opt-in toggle. Both knobs would be required together; setting +only one would fail loud at startup. Until that work ships, the +header-agnostic default described above is the only supported +configuration. ## PCI-DSS Req 4 §2.2.5 attestation