mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-11 16:58:53 +00:00
fix(bundle-5): Operational Liveness + Bootstrap — 4 audit findings closed
Closes Audit-2026-04-25 H-006 (High), H-007 (High), M-011 (Medium),
L-006 (Low — verified-already-closed via C-1 master closure in v2.0.54).
Hardens the orchestrator-facing surface — k8s probes, agent enrollment,
shutdown audit drain, scheduler config plumbing.
What changed
- internal/api/handler/health.go — split contract:
* /health stays shallow 200 (k8s liveness — process alive)
* /ready accepts *sql.DB; runs db.PingContext(2s); 503 on failure
* Nil DB path returns 200 + db=not_configured (test fixtures)
- internal/api/handler/agent_bootstrap.go (NEW) — verifyBootstrapToken:
* empty expected = warn-mode pass-through
* non-empty = `Authorization: Bearer <token>` required
* crypto/subtle.ConstantTimeCompare; length-mismatch path runs dummy
compare to keep timing uniform
* ErrBootstrapTokenInvalid sentinel
- internal/api/handler/agents.go — RegisterAgent calls verifyBootstrapToken
BEFORE body parse so unauth probes don't even allocate a JSON decoder
- internal/config/config.go — two new env vars:
* CERTCTL_AGENT_BOOTSTRAP_TOKEN (Auth.AgentBootstrapToken)
* CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS (Server.AuditFlushTimeoutSeconds)
- cmd/server/main.go — 3 changes:
* pass *sql.DB into NewHealthHandler (H-006)
* pass cfg.Auth.AgentBootstrapToken into NewAgentHandler (H-007)
* configurable shutdown audit-flush timeout (M-011)
* one-shot startup WARN when bootstrap token unset (deprecation)
- new tests: agent_bootstrap_test.go (full deny/accept/warn-mode coverage,
constant-time compare path, length-mismatch); health_test.go extended
with /ready DB-probe failure (503), nil-DB pass-through, /health-shallow
L-006 verified
- cmd/server/main.go:557 already calls
sched.SetShortLivedExpiryCheckInterval(cfg.Scheduler.ShortLivedExpiryCheckInterval)
per the C-1 master closure in v2.0.54. Bundle 5 confirms; no code change.
Threat model: TB-1 (operator/orchestrator), TB-2 (Agent↔Server).
- CWE-754 (Improper Check for Unusual or Exceptional Conditions) for H-006
- CWE-306 + CWE-288 (Missing Authentication for Critical Function) for H-007
Verification
- go vet ./... → clean
- go build ./... → clean
- go test -short -count=1 ./... → all packages pass
- targeted Bundle-5 regressions → all pass
- npx tsc --noEmit (web) → clean
- npx vitest run (web) → in-flight (sandbox 45s
ceiling exceeded; no failure markers in dot stream; no frontend
changes in this bundle so no regression risk)
- python3 yaml.safe_load(api/openapi.yaml) → 89 paths
Backward compatibility
- Bootstrap token defaults to empty (warn-mode) — existing demo
deployments unaffected. Server logs deprecation WARN; v2.2.0 will
require it.
- Audit flush timeout default 30s preserves prior behaviour.
- Helm chart already routes readiness probe to /ready (no chart change
needed); now /ready actually probes the DB.
Bundle 5 of the 2026-04-25 comprehensive audit.
This commit is contained in:
@@ -682,6 +682,16 @@ type ServerConfig struct {
|
||||
Port int // Server port (default: 8080). Set via CERTCTL_SERVER_PORT.
|
||||
MaxBodySize int64 // Maximum request body size in bytes (default: 1MB). Set via CERTCTL_MAX_BODY_SIZE.
|
||||
TLS ServerTLSConfig // HTTPS-only TLS configuration. Both CertPath and KeyPath are required.
|
||||
|
||||
// AuditFlushTimeoutSeconds is the budget (in seconds) main.go gives the
|
||||
// audit middleware to drain in-flight recordings during graceful
|
||||
// shutdown. Bundle-5 / Audit M-011: pre-Bundle-5 this was hard-coded
|
||||
// 30s, which dropped events silently in high-volume environments
|
||||
// because the same context governed HTTP server shutdown + audit
|
||||
// flush. Post-Bundle-5: configurable; default 30s preserves prior
|
||||
// behaviour. WARN-log on deadline exceeded, but never exit hard.
|
||||
// Setting: CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS environment variable.
|
||||
AuditFlushTimeoutSeconds int
|
||||
}
|
||||
|
||||
// ServerTLSConfig holds the server-side TLS material.
|
||||
@@ -892,6 +902,25 @@ type AuthConfig struct {
|
||||
// non-empty, this takes precedence over the legacy Secret field.
|
||||
// Setting: CERTCTL_API_KEYS_NAMED="name1:key1,name2:key2:admin"
|
||||
NamedKeys []NamedAPIKey
|
||||
|
||||
// AgentBootstrapToken is the pre-shared secret enforced on the agent
|
||||
// registration endpoint (POST /api/v1/agents). Bundle-5 / Audit H-007 /
|
||||
// CWE-306 + CWE-288: pre-Bundle-5, any host with network reach to the
|
||||
// server could self-register an agent and start polling for work — no
|
||||
// shared secret required. Post-Bundle-5: when this field is non-empty,
|
||||
// the registration handler requires `Authorization: Bearer <token>`
|
||||
// (constant-time comparison via crypto/subtle.ConstantTimeCompare); 401
|
||||
// on missing/wrong/malformed.
|
||||
//
|
||||
// Backwards compatibility: when empty (the v2.0.x default), the server
|
||||
// logs a startup WARN announcing the v2.2.0 deprecation — the field
|
||||
// will become required in v2.2.0 and unset will fail-loud — and accepts
|
||||
// registrations as today. Existing demo deploys that don't set it keep
|
||||
// working through v2.1.x.
|
||||
//
|
||||
// Generation guidance: `openssl rand -hex 32` (256-bit entropy).
|
||||
// Setting: CERTCTL_AGENT_BOOTSTRAP_TOKEN environment variable.
|
||||
AgentBootstrapToken string
|
||||
}
|
||||
|
||||
// RateLimitConfig contains rate limiting configuration.
|
||||
@@ -938,6 +967,9 @@ func Load() (*Config, error) {
|
||||
CertPath: getEnv("CERTCTL_SERVER_TLS_CERT_PATH", ""),
|
||||
KeyPath: getEnv("CERTCTL_SERVER_TLS_KEY_PATH", ""),
|
||||
},
|
||||
// Bundle-5 / M-011: configurable shutdown audit-flush budget.
|
||||
// Default 30s preserves pre-Bundle-5 behaviour.
|
||||
AuditFlushTimeoutSeconds: getEnvInt("CERTCTL_AUDIT_FLUSH_TIMEOUT_SECONDS", 30),
|
||||
},
|
||||
Database: DatabaseConfig{
|
||||
URL: getEnv("CERTCTL_DATABASE_URL", "postgres://localhost/certctl"),
|
||||
@@ -973,6 +1005,10 @@ func Load() (*Config, error) {
|
||||
Secret: getEnv("CERTCTL_AUTH_SECRET", ""),
|
||||
// NamedKeys is populated from CERTCTL_API_KEYS_NAMED below so Load()
|
||||
// can surface parse errors alongside other config errors.
|
||||
|
||||
// Bundle-5 / Audit H-007: agent-registration bootstrap secret.
|
||||
// Empty (default) = warn-mode pass-through; v2.2.0 will require it.
|
||||
AgentBootstrapToken: getEnv("CERTCTL_AGENT_BOOTSTRAP_TOKEN", ""),
|
||||
},
|
||||
RateLimit: RateLimitConfig{
|
||||
Enabled: getEnvBool("CERTCTL_RATE_LIMIT_ENABLED", true),
|
||||
|
||||
Reference in New Issue
Block a user