From 25996f86fab4364c78afc02447f7e81ffc456101 Mon Sep 17 00:00:00 2001 From: shankar0123 Date: Wed, 13 May 2026 20:48:20 +0000 Subject: [PATCH] fix(deploy): wire CERTCTL_DEMO_MODE_ACK_TS into the demo overlay path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2 SEC-H3 (commit 69a2b5c) added a fail-closed requirement: when CERTCTL_DEMO_MODE_ACK=true, the server refuses to start unless CERTCTL_DEMO_MODE_ACK_TS= is set and within the last 24h. The demo overlay (docker-compose.demo.yml) sets DEMO_MODE_ACK=true but didn't supply the paired TS, so: Failed to load configuration: phase-2 SEC-H3 fail-closed guard (missing TS): CERTCTL_DEMO_MODE_ACK=true requires CERTCTL_DEMO_MODE_ACK_TS= set within the last 24h — refuse to start. This bricks the cold-DB compose smoke job, the README quickstart (`docker compose -f .yml -f demo.yml up`), and every operator using the demo overlay locally — symptom: certctl-server container restart loop with the SEC-H3 message above. Fix is three-piece: 1. deploy/docker-compose.demo.yml passes the TS through from the shell env via `CERTCTL_DEMO_MODE_ACK_TS: "${CERTCTL_DEMO_MODE_ACK_TS:-}"`. The overlay can't hardcode the value (it would rot the next day) and SEC-H3 is designed to refresh on every up. 2. deploy/demo-up.sh — new helper that mints `CERTCTL_DEMO_MODE_ACK_TS=$(date +%s)` and forwards args to `docker compose up`. The SEC-H3 error message points operators at it. Replaces the bare `docker compose -f ... up` invocation in the overlay's docstring + README quickstart references. 3. .github/workflows/ci.yml cold-db-compose-smoke job exports a fresh TS before the initial up-d AND re-emits it into /tmp/_smoke.env so the force-recreate at step 4 inherits the value (--env-file replaces the shell-env source for compose-file interpolation, so omitting the re-emission would re-trip the guard). Other CI compose surfaces verified clean: - docker-compose.test.yml uses auth=api-key (not demo-mode); not affected. - security-deep-scan.yml uses the base compose without the demo overlay; not affected. Verified locally: YAML parses, bash syntax check passes on demo-up.sh, overlay's docstring + the SEC-H3 error message now agree on the helper script's existence. --- .github/workflows/ci.yml | 20 +++++++++++++++++- deploy/demo-up.sh | 38 ++++++++++++++++++++++++++++++++++ deploy/docker-compose.demo.yml | 29 ++++++++++++++++++++++---- 3 files changed, 82 insertions(+), 5 deletions(-) create mode 100755 deploy/demo-up.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a62ddc5..8b08219 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -346,6 +346,16 @@ jobs: # demo-mode or api-key, so the overlay is acceptable here. COMPOSE_FILES=(-f docker-compose.yml -f docker-compose.demo.yml) + # Phase 2 SEC-H3 (2026-05-13): the demo overlay sets + # CERTCTL_DEMO_MODE_ACK=true; the SEC-H3 fail-closed guard + # requires a paired CERTCTL_DEMO_MODE_ACK_TS within the last + # 24h (a static YAML value would rot). The overlay reads + # ${CERTCTL_DEMO_MODE_ACK_TS:-} from the shell, so we mint a + # fresh timestamp here and export it for every compose + # invocation in this job (initial up-d AND the force-recreate + # at step 4). + export CERTCTL_DEMO_MODE_ACK_TS="$(date +%s)" + log "1/4 down -v --remove-orphans" docker compose "${COMPOSE_FILES[@]}" down -v --remove-orphans 2>&1 | tail -3 || true @@ -359,7 +369,15 @@ jobs: log "4/4 minting day-0 admin (proves migration ladder + bootstrap path)" TOKEN="$(openssl rand -base64 32 | tr -d '\n')" - echo "CERTCTL_BOOTSTRAP_TOKEN=$TOKEN" > /tmp/_smoke.env + { + echo "CERTCTL_BOOTSTRAP_TOKEN=$TOKEN" + # Re-emit the demo-mode ACK TS into the --env-file so the + # force-recreate at step 4 inherits it. `--env-file` REPLACES + # the shell-env source for variable interpolation on compose + # operations that use it, so omitting this line would re-trip + # the SEC-H3 guard. + echo "CERTCTL_DEMO_MODE_ACK_TS=$CERTCTL_DEMO_MODE_ACK_TS" + } > /tmp/_smoke.env docker compose "${COMPOSE_FILES[@]}" --env-file /tmp/_smoke.env up -d --force-recreate certctl-server 2>&1 | tail -2 sleep 5 wait_for_service_healthy certctl-server diff --git a/deploy/demo-up.sh b/deploy/demo-up.sh new file mode 100755 index 0000000..50c10d3 --- /dev/null +++ b/deploy/demo-up.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# deploy/demo-up.sh — boot the certctl demo stack with the fresh +# CERTCTL_DEMO_MODE_ACK_TS the Phase 2 SEC-H3 guard requires. +# +# The demo overlay sets CERTCTL_DEMO_MODE_ACK=true. Phase 2 SEC-H3 +# (2026-05-13) pairs that with a fail-closed requirement: the server +# refuses to start unless CERTCTL_DEMO_MODE_ACK_TS= is set +# and is within the last 24h (with 1-minute future clock-skew tolerance). +# +# A static value in docker-compose.demo.yml would rot the next day, so +# the overlay passthroughs the value from the shell environment. This +# helper mints a fresh TS at run time and forwards any extra args to +# `docker compose up`, so operators can use it as a drop-in replacement +# for the bare command. Example: +# +# ./demo-up.sh -d # cold boot in detached mode +# ./demo-up.sh -d --pull always # forward any flags through +# +# The cold-DB compose smoke in .github/workflows/ci.yml does the same +# thing inline; this script exists so local operators don't have to +# remember the export. + +set -euo pipefail + +# cd to the deploy/ dir so the relative `-f` paths resolve regardless +# of where the operator invokes this from. The script lives next to +# the compose files it references. +cd "$(dirname "$0")" + +export CERTCTL_DEMO_MODE_ACK_TS="$(date +%s)" + +echo "[demo-up] minting CERTCTL_DEMO_MODE_ACK_TS=$CERTCTL_DEMO_MODE_ACK_TS" +echo "[demo-up] running: docker compose -f docker-compose.yml -f docker-compose.demo.yml up $*" + +exec docker compose \ + -f docker-compose.yml \ + -f docker-compose.demo.yml \ + up "$@" diff --git a/deploy/docker-compose.demo.yml b/deploy/docker-compose.demo.yml index 6de9abc..10e939d 100644 --- a/deploy/docker-compose.demo.yml +++ b/deploy/docker-compose.demo.yml @@ -5,8 +5,15 @@ # Layered on top of the production-shaped base (docker-compose.yml) to give # operators a one-command, zero-config demo path: # -# docker compose -f deploy/docker-compose.yml \ -# -f deploy/docker-compose.demo.yml up -d --build +# deploy/demo-up.sh -d --build +# +# (which forwards args to `docker compose up` after exporting the fresh +# CERTCTL_DEMO_MODE_ACK_TS that Phase 2 SEC-H3 requires). Equivalent +# manual invocation: +# +# CERTCTL_DEMO_MODE_ACK_TS=$(date +%s) docker compose \ +# -f deploy/docker-compose.yml \ +# -f deploy/docker-compose.demo.yml up -d --build # # What this overlay does: # @@ -14,6 +21,10 @@ # request is served as the synthetic admin actor `actor-demo-anon`; # the server emits a prominent ⚠ DEMO MODE WARN banner at boot with # a production-promotion checklist (cmd/server/main.go::emitDemoBanner). +# Phase 2 SEC-H3 (2026-05-13) pairs DEMO_MODE_ACK with a required +# DEMO_MODE_ACK_TS within the last 24h. The overlay reads +# ${CERTCTL_DEMO_MODE_ACK_TS:-} from the shell — use deploy/demo-up.sh +# (which exports a fresh TS) instead of bare `docker compose up`. # # 2. Flips CERTCTL_KEYGEN_MODE=server (the demo issues + holds the key on # the server to keep the dashboard populated; production deploys must @@ -48,8 +59,7 @@ # To start fresh (wipe previous data): # docker compose -f deploy/docker-compose.yml \ # -f deploy/docker-compose.demo.yml down -v -# docker compose -f deploy/docker-compose.yml \ -# -f deploy/docker-compose.demo.yml up -d --build +# deploy/demo-up.sh -d --build services: postgres: @@ -67,6 +77,17 @@ services: # reminds the operator on every start. CERTCTL_AUTH_TYPE: none CERTCTL_DEMO_MODE_ACK: "true" + # Phase 2 SEC-H3 (2026-05-13): DEMO_MODE_ACK=true requires a fresh + # DEMO_MODE_ACK_TS within the last 24h. The overlay can't hardcode + # a timestamp (it would rot the next day), so we passthrough from + # the shell. Operators set this via: + # CERTCTL_DEMO_MODE_ACK_TS=$(date +%s) docker compose \ + # -f docker-compose.yml -f docker-compose.demo.yml up -d + # The cold-DB smoke + any helper script (deploy/demo-up.sh, when + # it lands) export this before invoking compose. Empty value + # fails the SEC-H3 guard with a clear operator-facing error + # message pointing at this line. + CERTCTL_DEMO_MODE_ACK_TS: "${CERTCTL_DEMO_MODE_ACK_TS:-}" # Server-side keygen so the demo can populate the dashboard with # full lifecycle history. Production deploys leave this at the # code default `agent` (CertctlAgent generates ECDSA P-256 keys