mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:51:31 +00:00
a849c8b8cf
Bundle 2 closure (2026-05-12 acquisition diligence audit). Closes the
"docker compose up == accidental production" hazard: pre-Bundle-2 the
base deploy/docker-compose.yml WAS the demo path (AUTH_TYPE=none +
DEMO_MODE_ACK=true + KEYGEN_MODE=server + DEMO_SEED=true + literal
change-me-... placeholder creds), the README claimed "drop the demo
overlay for a clean install", and ENVIRONMENTS.md table documented
auth-type default as api-key — three contradictory stories layered on
the same compose file.
Source findings closed:
R2 R3 C1 D9 finding-2 S9 (repo audit)
SEC-H2 SEC-M1 SEC-M3 OPS-M3 LOW-5 HIGH-6 (cowork audit)
Compose split (deploy/docker-compose.yml + deploy/docker-compose.demo.yml):
The base now ships production-shaped — no AUTH_TYPE override, no
KEYGEN_MODE override, no DEMO_MODE_ACK, no DEMO_SEED, no literal
placeholder fallbacks. POSTGRES_PASSWORD / CERTCTL_AUTH_SECRET /
CERTCTL_CONFIG_ENCRYPTION_KEY / CERTCTL_API_KEY / CERTCTL_AGENT_ID
must come from deploy/.env (sample template in deploy/.env.example +
root .env.example). The demo overlay carries the full demo posture
(every env var + every placeholder credential) so the
`-f docker-compose.demo.yml` one-flag flip remains a zero-config
populated-dashboard path.
Fail-closed startup guards (internal/config/config.go::Validate):
Three new gates layered on the existing HIGH-12 demo-mode listen-bind
guard. All three exempt CERTCTL_DEMO_MODE_ACK=true so the demo overlay
keeps working:
• HIGH-6: AUTH_SECRET = "change-me-in-production" → refuse
• HIGH-6: CONFIG_ENCRYPTION_KEY = "change-me-32-char..." → refuse
• LOW-5: CORS_ORIGINS contains "*" (CWE-942 + CWE-352) → refuse
Visible DEMO MODE banner (cmd/server/main.go): every boot under
DEMO_MODE_ACK=true now emits a prominent WARN line with a 6-step
production-promotion checklist. The 2026-04-19 incident (a screenshot
run that kept running for three days) drove this; the per-startup
banner makes the posture unmissable in any log scraper.
Agent enrollment doc alignment:
• docs/reference/configuration.md L83: corrected the non-existent
URL `POST /api/v1/agents/register` to the real route
`POST /api/v1/agents`; added the bootstrap-token note and the
install-agent.sh handoff sequence.
• docs/reference/architecture.md L154: replaced "agents register
themselves at first heartbeat" (false — cmd/agent/main.go fail-
fasts when CERTCTL_AGENT_ID is unset) with the actual two-step
operator-driven flow (REST or GUI registration first, returned ID
fed to install-agent.sh second).
Tests + CI guard:
• 9 new TestValidate_Bundle2_* cases in internal/config/config_test.go
covering: placeholder-secret refused + demo-ack exempt; placeholder
encryption-key refused + demo-ack exempt; real key not mistaken for
placeholder; wildcard CORS refused + demo-ack exempt; wildcard mixed
into a concrete allowlist still refused; concrete allowlist accepted.
• scripts/ci-guards/B2-compose-base-no-demo-env.sh: greps the base
compose for any of the demo-mode env vars + placeholder credentials.
Comments stripped before checking so the narrative header in the
base file can still reference the overlay's posture in prose.
Cold-DB CI smoke (.github/workflows/ci.yml::cold-db-compose-smoke):
Switched to layering -f docker-compose.demo.yml on top of the base —
the new production base requires real env vars the smoke doesn't have,
and the smoke's purpose (catch migration-on-cold-DB regressions + the
bootstrap-token mint path) is orthogonal to which auth posture the
boot lands in.
Receipts:
• Current first-run truth table
compose flag → posture
-f docker-compose.yml (production)
→ requires .env;
fail-fasts on
missing AUTH_SECRET
/ CONFIG_ENCRYPTION
_KEY / POSTGRES
_PASSWORD; agent
fail-fasts on
missing AGENT_ID
-f docker-compose.yml -f docker-compose.demo.yml (demo)
→ zero-config;
AUTH_TYPE=none +
DEMO_MODE_ACK=true
+ KEYGEN=server +
DEMO_SEED=true;
boot banner WARN
-f docker-compose.yml -f docker-compose.dev.yml (dev)
→ base + PgAdmin
+ debug logging
-f docker-compose.test.yml (test, standalone)
→ production-shape
posture, real CA
backends
• Verification (PATH=/tmp/go/bin export GO* paths to /tmp):
gofmt -l # clean (no diffs)
go vet ./internal/config ./cmd/server # clean
go test -short -count=1 ./internal/config/... # PASS (cumulative +
all 9 new Bundle 2
cases green)
go test -short -count=1 # PASS (no regression
./internal/connector/target/configcheck in the Bundle 1 -
closure tests)
go build ./cmd/server ./cmd/agent # clean
./cmd/cli ./cmd/mcp-server
bash scripts/ci-guards/B2-compose-base-no-demo-env.sh # clean
bash scripts/ci-guards/H-1-encryption-key-min-length.sh # clean
bash scripts/ci-guards/G-3-env-docs-drift.sh # clean
Remaining operator warnings (not blocking; tracked in CLAUDE.md
"Open decisions"):
• The first `docker compose -f docker-compose.yml up -d` against a
pre-Bundle-2 .env (placeholder values still in place) will now
fail-fast. This is the intended posture but operators upgrading
from v2.0.x via .env-from-old-master need to rotate before
upgrading. The CHANGELOG note for the v2.1.0 release should
call this out alongside Auth Bundle 2's other breaking changes.
Audit-Closes: BUNDLE-2 R2 R3 C1 D9 S9 SEC-H2 SEC-M1 SEC-M3 OPS-M3 LOW-5 HIGH-6
91 lines
4.8 KiB
Bash
91 lines
4.8 KiB
Bash
# Certctl Configuration Example
|
|
# Copy this file to .env and configure for your environment
|
|
# DO NOT commit .env with real secrets to version control
|
|
|
|
# ==============================================================================
|
|
# PostgreSQL (used by Docker Compose for the postgres container)
|
|
# ==============================================================================
|
|
POSTGRES_DB=certctl
|
|
POSTGRES_USER=certctl
|
|
POSTGRES_PASSWORD=replace-with-openssl-rand-hex-32
|
|
|
|
# ==============================================================================
|
|
# Certctl Server
|
|
# All server vars use the CERTCTL_ prefix (see internal/config/config.go)
|
|
# ==============================================================================
|
|
# IMPORTANT: keep the password segment of CERTCTL_DATABASE_URL in sync with
|
|
# POSTGRES_PASSWORD above. If you deploy via `deploy/docker-compose.yml`,
|
|
# this value is *overridden* by the compose file's
|
|
# `postgres://certctl:${POSTGRES_PASSWORD:-certctl}@postgres:5432/...`
|
|
# interpolation — but if you run the binary directly with this .env loaded
|
|
# (e.g. `set -a; source .env; ./certctl-server`), update *both* lines.
|
|
# Background: editing POSTGRES_PASSWORD after the postgres data directory
|
|
# has been initialized once does NOT rotate the password — initdb only
|
|
# seeds pg_authid on first boot of an empty volume. See docs/quickstart.md
|
|
# "Warning" callout and `internal/repository/postgres/db.go::wrapPingError`
|
|
# for the SQLSTATE 28P01 diagnostic that fires when the two drift.
|
|
CERTCTL_DATABASE_URL=postgres://certctl:replace-with-openssl-rand-hex-32@postgres:5432/certctl?sslmode=disable
|
|
CERTCTL_SERVER_HOST=0.0.0.0
|
|
CERTCTL_SERVER_PORT=8443
|
|
CERTCTL_LOG_LEVEL=info
|
|
CERTCTL_LOG_FORMAT=json
|
|
|
|
# Auth type: "api-key" (production), "none" (demo/development), or
|
|
# "oidc" (Auth Bundle 2 - native OIDC SSO via coreos/go-oidc/v3, ships
|
|
# in Bundle 2 phases 5+6; setting CERTCTL_AUTH_TYPE=oidc on a build
|
|
# without Bundle 2 wired triggers a clear refuse-to-start error rather
|
|
# than a silent fallback to api-key). For JWT / SAML / LDAP, continue to
|
|
# run an authenticating gateway in front of certctl (oauth2-proxy /
|
|
# Envoy ext_authz / Traefik ForwardAuth / Pomerium) and set
|
|
# CERTCTL_AUTH_TYPE=none on the upstream - see docs/architecture.md
|
|
# "Authenticating-gateway pattern". G-1 removed the in-process "jwt"
|
|
# option (no JWT middleware shipped - silent auth downgrade); see
|
|
# docs/upgrade-to-v2-jwt-removal.md if you previously set
|
|
# CERTCTL_AUTH_TYPE=jwt.
|
|
#
|
|
# Bundle 2 closure (2026-05-12): the docker-compose base file no longer
|
|
# defaults to AUTH_TYPE=none. The base ships production-shaped; the demo
|
|
# overlay (deploy/docker-compose.demo.yml) flips this baseline into the
|
|
# populated-dashboard demo path.
|
|
CERTCTL_AUTH_TYPE=api-key
|
|
# Required when CERTCTL_AUTH_TYPE is "api-key". Generate with:
|
|
# openssl rand -base64 32
|
|
# The Bundle 2 fail-closed Validate() REFUSES TO START if this value
|
|
# equals the placeholder string "change-me-in-production" outside of
|
|
# demo mode (CERTCTL_DEMO_MODE_ACK=true).
|
|
CERTCTL_AUTH_SECRET=replace-with-openssl-rand-base64-32
|
|
|
|
# Bundle 2 closure: AES-256-GCM key for encrypting issuer/target config
|
|
# secrets at rest. Required for any deployment that uses the dynamic
|
|
# config GUI to store issuer credentials. Generate with:
|
|
# openssl rand -base64 32
|
|
# Minimum 32 bytes. The Bundle 2 fail-closed Validate() REFUSES TO
|
|
# START if this value equals the placeholder string
|
|
# "change-me-32-char-encryption-key" outside of demo mode.
|
|
CERTCTL_CONFIG_ENCRYPTION_KEY=replace-with-openssl-rand-base64-32
|
|
|
|
# ==============================================================================
|
|
# Certctl Agent
|
|
# ==============================================================================
|
|
# HTTPS-only as of v2.2 (TLS 1.3 pinned). Agents reject http:// URLs at
|
|
# startup. Use the docker-compose self-signed bootstrap CA bundle from
|
|
# `deploy/test/certs/ca.crt` or supply your own via CERTCTL_SERVER_CA_BUNDLE_PATH.
|
|
CERTCTL_SERVER_URL=https://localhost:8443
|
|
# Matches one of the server's CERTCTL_AUTH_SECRET rotation values. The
|
|
# placeholder is rejected outside demo mode (Bundle 2 fail-closed guard).
|
|
CERTCTL_API_KEY=replace-with-openssl-rand-base64-32
|
|
CERTCTL_AGENT_NAME=local-agent
|
|
# Returned from `POST /api/v1/agents` during agent enrollment. The agent
|
|
# fail-fasts at startup with "agent-id flag or CERTCTL_AGENT_ID env var
|
|
# is required" if this is unset.
|
|
# CERTCTL_AGENT_ID=agent-from-registration-response
|
|
|
|
# ==============================================================================
|
|
# Optional: Scheduler Tuning (defaults are usually fine)
|
|
# ==============================================================================
|
|
# CERTCTL_SCHEDULER_RENEWAL_CHECK_INTERVAL=1h
|
|
# CERTCTL_SCHEDULER_JOB_PROCESSOR_INTERVAL=30s
|
|
# CERTCTL_SCHEDULER_AGENT_HEALTH_CHECK_INTERVAL=2m
|
|
# CERTCTL_SCHEDULER_NOTIFICATION_PROCESS_INTERVAL=1m
|
|
# CERTCTL_DATABASE_MAX_CONNS=25
|