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.