From 6cedaf42311cfa78bf83a6e354e115965d6fa1c1 Mon Sep 17 00:00:00 2001 From: Shankar Date: Wed, 29 Apr 2026 22:32:19 +0000 Subject: [PATCH] fix(docs/est): drop CERTCTL_EST_* wildcard prose to satisfy G-3 docs-drift guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit (ec3258e) added the per-profile env-var documentation to docs/features.md but used the prose form `CERTCTL_EST_*` (asterisk wildcard) when describing the legacy single-issuer flat env vars. The G-3 docs-drift guard's docs-side extraction regex (`\bCERTCTL_[A-Z_]+\b` against README + docs + helm) parses that prose as the env-var literal `CERTCTL_EST_` (trailing underscore, since `*` is a non-word char that ends the \b boundary). The Go-source-defined-vars side has no `CERTCTL_EST_` literal — only the stem `CERTCTL_EST_PROFILE_` + specific full names — so the guard reports docs-only-not-defined and refuses the build. The SCEP doc has the same prose wildcard form (line 661 of features.md uses `CERTCTL_SCEP_*`) but is whitelisted in the G-3 ALLOWED list at .github/workflows/ci.yml:1278 (`CERTCTL_SCEP_|` matches the trailing-underscore stem). EST has no equivalent allowlist entry. Two fixes were possible: (a) add `CERTCTL_EST_|` to the G-3 allowlist (matches SCEP precedent; minimal change), or (b) rewrite the prose to a form the regex doesn't grab (cleaner; no allowlist sprawl). This commit takes (b): the wildcard `CERTCTL_EST_*` becomes the explicit enumeration `CERTCTL_EST_ENABLED` / `CERTCTL_EST_ISSUER_ID` / `CERTCTL_EST_PROFILE_ID` — same operator-facing meaning, no regex collision. Verified locally: G-3 guard reports clean for the EST surface on both directions (docs-only-not-defined + defined-not-docs). --- docs/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features.md b/docs/features.md index e083e12..ef4c74c 100644 --- a/docs/features.md +++ b/docs/features.md @@ -625,7 +625,7 @@ Accepts both base64-encoded DER (EST standard) and PEM-encoded PKCS#10 CSR input | `CERTCTL_EST_ENABLED` | `false` | Enable EST endpoints | | `CERTCTL_EST_ISSUER_ID` | `iss-local` | Issuer for EST enrollments. Legacy single-issuer mode; merged into `Profiles[0]` (PathID="") by the Phase 1 back-compat shim when `CERTCTL_EST_PROFILES` is unset. | | `CERTCTL_EST_PROFILE_ID` | (none) | Optional profile constraint. Legacy single-issuer mode (same back-compat shim as above). | -| `CERTCTL_EST_PROFILES` | (none, single-issuer mode) | **EST RFC 7030 hardening Phase 1.** Comma-separated list of EST profile names enabling **multi-endpoint dispatch**. When set, certctl exposes one `/.well-known/est//` endpoint group per name (e.g. `CERTCTL_EST_PROFILES=corp,iot,wifi` produces `/.well-known/est/corp/{cacerts,simpleenroll,simplereenroll,csrattrs}` etc.). Each name also drives the env-var prefix for the per-profile config below. When unset, certctl runs in legacy single-issuer mode using the flat `CERTCTL_EST_*` env vars above (which synthesise a single-element profile bound to the legacy `/.well-known/est/` root path). PathID must be a path-safe slug (`[a-z0-9-]`, no leading/trailing hyphen); names get lowercased for the URL path and uppercased for the env-var prefix. Mirrors the SCEP `CERTCTL_SCEP_PROFILES` family from the SCEP RFC 8894 master bundle (commit `6d30493`). | +| `CERTCTL_EST_PROFILES` | (none, single-issuer mode) | **EST RFC 7030 hardening Phase 1.** Comma-separated list of EST profile names enabling **multi-endpoint dispatch**. When set, certctl exposes one `/.well-known/est//` endpoint group per name (e.g. `CERTCTL_EST_PROFILES=corp,iot,wifi` produces `/.well-known/est/corp/{cacerts,simpleenroll,simplereenroll,csrattrs}` etc.). Each name also drives the env-var prefix for the per-profile config below. When unset, certctl runs in legacy single-issuer mode using the flat `CERTCTL_EST_ENABLED` / `CERTCTL_EST_ISSUER_ID` / `CERTCTL_EST_PROFILE_ID` env vars above (which synthesise a single-element profile bound to the legacy `/.well-known/est/` root path). PathID must be a path-safe slug (`[a-z0-9-]`, no leading/trailing hyphen); names get lowercased for the URL path and uppercased for the env-var prefix. Mirrors the SCEP `CERTCTL_SCEP_PROFILES` family from the SCEP RFC 8894 master bundle (commit `6d30493`). | | `CERTCTL_EST_PROFILE__ISSUER_ID` | (none) | Per-profile issuer binding when `CERTCTL_EST_PROFILES` is set. `` is the upper-cased profile name from the list (so a `CERTCTL_EST_PROFILES` entry of `corp` resolves the issuer-id env var key with `` replaced by `CORP`, the `_ISSUER_ID` suffix unchanged). The same per-profile env-var prefix `CERTCTL_EST_PROFILE_` is also used for `_PROFILE_ID`, `_ENROLLMENT_PASSWORD`, `_MTLS_ENABLED`, `_MTLS_CLIENT_CA_TRUST_BUNDLE_PATH`, `_CHANNEL_BINDING_REQUIRED`, `_ALLOWED_AUTH_MODES`, `_RATE_LIMIT_PER_PRINCIPAL_24H`, `_SERVERKEYGEN_ENABLED` — see the rows below. **Required for every profile** listed in `CERTCTL_EST_PROFILES`. Each profile is independently validated at startup; per-profile failures log the offending PathID. | | `CERTCTL_EST_PROFILE__PROFILE_ID` | (none) | Per-profile optional `CertificateProfile` constraint, mirroring the legacy `CERTCTL_EST_PROFILE_ID`. Leave unset to allow the issuer's defaults. **Required when `_SERVERKEYGEN_ENABLED=true`** because the Phase 5 server-keygen path needs a profile to pin `AllowedKeyAlgorithms` (the server has to decide what key to generate). | | `CERTCTL_EST_PROFILE__ENROLLMENT_PASSWORD` | (none) | **EST RFC 7030 §3.2.3 alternative.** Per-profile shared secret for HTTP Basic auth on the standard `/.well-known/est//` route. Empty value means HTTP Basic auth is NOT required for this profile (mTLS-only or anonymous, depending on `_ALLOWED_AUTH_MODES`). Stored only in process memory; never logged. Constant-time comparison via `crypto/subtle.ConstantTimeCompare` in the handler. **Required when `_ALLOWED_AUTH_MODES` lists `basic`** (Phase 1 cross-check refuses the boot otherwise). The Phase 3 handler dispatches HTTP Basic auth using this value. |