From 17ef377edbd0e62187358509768bd22149a176ae Mon Sep 17 00:00:00 2001 From: Shankar Date: Sun, 26 Apr 2026 00:03:03 +0000 Subject: [PATCH] =?UTF-8?q?fix(bundle-5):=20CI=20green-up=20=E2=80=94=20dr?= =?UTF-8?q?op=20unused=20sync.Once=20+=20document=20new=20env=20vars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two CI gate failures from the Bundle 5 push: 1. golangci-lint (unused) — agent_bootstrap.go declared `var bootstrapWarnOnce sync.Once` but never called .Do(). The one-shot WARN actually lives in cmd/server/main.go (per-process at startup, not per-request) so the handler-side variable was dead code. Dropped the var + sync import; left a comment explaining where the WARN lives. 2. G-3 env-var docs guardrail — Bundle 5 added two new env vars (CERTCTL_AGENT_BOOTSTRAP_TOKEN, CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS) but the G-3 closure CI step asserts every CERTCTL_* env defined in internal/config/config.go is mentioned in docs/features.md. Added three new sub-sections to docs/features.md after the Body Size Limits block: * Agent Bootstrap Token (H-007 contract + generation guidance) * Graceful Shutdown Audit Flush (M-011 timeout knob) * Liveness vs Readiness Probes (H-006 /health vs /ready table) No production behaviour change; pure CI-gate fix. Verification - go vet ./internal/api/handler/... → clean - go test -count=1 -run 'TestVerifyBootstrapToken|TestRegisterAgent_BootstrapToken' ./internal/api/handler/... → all pass - grep CERTCTL_AGENT_BOOTSTRAP_TOKEN docs/features.md → present - grep CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS docs/features.md → present --- docs/features.md | 29 +++++++++++++++++++++++++ internal/api/handler/agent_bootstrap.go | 7 +++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/features.md b/docs/features.md index 3e69c19..dafa2a3 100644 --- a/docs/features.md +++ b/docs/features.md @@ -88,6 +88,35 @@ Preflight responses include `Access-Control-Max-Age` for caching. |---|---|---| | `CERTCTL_MAX_BODY_SIZE` | `1048576` (1 MB) | Maximum request body in bytes | +### Agent Bootstrap Token + + + +Pre-shared secret enforced on `POST /api/v1/agents`. When set, the registration handler requires `Authorization: Bearer ` and verifies via `crypto/subtle.ConstantTimeCompare` BEFORE the JSON body parse — defeats both timing oracles and unauth payload allocation. Mismatch / missing / malformed → `401 invalid_or_missing_bootstrap_token`. + +| Env Var | Default | Description | +|---|---|---| +| `CERTCTL_AGENT_BOOTSTRAP_TOKEN` | `""` (warn-mode pass-through) | Bearer token agents must present on first registration. v2.2.0 will require it; unset emits a one-shot startup deprecation WARN. Generate with `openssl rand -hex 32`. | + +### Graceful Shutdown Audit Flush + + + +On SIGTERM / SIGINT, the server drains in-flight audit recordings before closing the DB pool. The drain budget is shared with the HTTP server graceful shutdown. + +| Env Var | Default | Description | +|---|---|---| +| `CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS` | `30` | Total budget (seconds) for HTTP shutdown + scheduler completion + audit-event drain. WARN-log on deadline exceeded; never exit hard. | + +### Liveness vs Readiness Probes + + + +| Endpoint | Purpose | Probe | +|---|---|---| +| `GET /health` | Liveness — process alive only. Returns 200 unconditionally; never restart pods for DB hiccups. | k8s `livenessProbe` | +| `GET /ready` | Readiness — runs `db.PingContext` with 2 s ceiling. Returns 503 + `{"status":"db_unavailable"}` when DB unreachable so k8s drains the pod. | k8s `readinessProbe` | + ### Query Features All list endpoints support: diff --git a/internal/api/handler/agent_bootstrap.go b/internal/api/handler/agent_bootstrap.go index 3e7542d..ad84337 100644 --- a/internal/api/handler/agent_bootstrap.go +++ b/internal/api/handler/agent_bootstrap.go @@ -5,7 +5,6 @@ import ( "errors" "net/http" "strings" - "sync" ) // Bundle-5 / Audit H-007 / CWE-306 + CWE-288: @@ -42,9 +41,9 @@ import ( // Handlers translate this into HTTP 401 with a fixed error string. var ErrBootstrapTokenInvalid = errors.New("invalid or missing agent bootstrap token") -// bootstrapWarnOnce gates the one-shot deprecation WARN to a single emission -// per process so a busy registration endpoint doesn't flood the log. -var bootstrapWarnOnce sync.Once +// Operator-visible deprecation WARN for the warn-mode default lives in +// cmd/server/main.go — emitted once at startup, not per-request, so a +// busy registration endpoint doesn't flood the log. // verifyBootstrapToken returns nil when the request should proceed and // ErrBootstrapTokenInvalid when it should be rejected.