fix(harness): generate SECRETS_ENCRYPTION_KEY per-run instead of hardcoding

Replaces the hardcoded base64 sentinel (630dd0da) with a per-run
generation in up.sh, exported into compose's interpolation environment.

Why:
- Hardcoding a 32-byte base64 string in the repo, even one labelled
  "test-only", sets a bad muscle-memory pattern. The next agent or
  contributor copies the shape into another harness — or worse, into a
  staging .env — and the test-only sentinel turns into something
  someone treats as a real key.
- Secret scanners flag key-shaped values regardless of the surrounding
  comment claiming intent. Avoiding the literal entirely sidesteps the
  false-positive.
- A fresh key per harness lifetime more closely mimics prod's
  per-tenant isolation, exercising the same code paths without any
  pretense of stable encrypted-data fixtures (which the harness wipes
  on every ./down.sh anyway).

Implementation:
- up.sh: `openssl rand -base64 32` if SECRETS_ENCRYPTION_KEY isn't
  already set in the caller's env. Honoring a pre-set value lets a
  debug session pin a key for reproducibility (e.g. when investigating
  encrypted-row corruption).
- compose.yml: `${SECRETS_ENCRYPTION_KEY:?…}` makes a misuse loud —
  running `docker compose up` directly bypassing up.sh fails fast with
  a clear error pointing at the right entry point, rather than a 100s
  unhealthy-tenant timeout.

Both paths verified via `docker compose config`:
- with key exported: value interpolates cleanly
- without it: "required variable SECRETS_ENCRYPTION_KEY is missing a
  value: must be set — run via tests/harness/up.sh, which generates
  one per run"
This commit is contained in:
Hongming Wang 2026-04-30 13:30:14 -07:00
parent 630dd0dae7
commit 9dae0503ee
2 changed files with 23 additions and 7 deletions

View File

@ -86,13 +86,13 @@ services:
PLATFORM_URL: "http://tenant:8080"
MOLECULE_ENV: "production"
# SECRETS_ENCRYPTION_KEY is required when MOLECULE_ENV=production —
# crypto.InitStrict() refuses to boot without it ("32 bytes raw or
# base64-encoded"). The harness uses a clearly-test sentinel so the
# production code path is exercised end-to-end (including the
# encrypted-secret reads/writes) without coupling to a real key.
# Value is base64 of the literal string "harness-test-only-not-for-prod!!"
# (exactly 32 bytes). Do NOT copy this to any other environment.
SECRETS_ENCRYPTION_KEY: "aGFybmVzcy10ZXN0LW9ubHktbm90LWZvci1wcm9kISE="
# crypto.InitStrict() refuses to boot without it. up.sh generates a
# fresh 32-byte key per harness lifetime via `openssl rand -base64 32`
# and exports it into this compose file's interpolation environment.
# The :? sentinel makes the misuse loud — running `docker compose up`
# directly without going through up.sh fails fast with a clear error
# rather than getting a confusing tenant-unhealthy timeout.
SECRETS_ENCRYPTION_KEY: "${SECRETS_ENCRYPTION_KEY:?must be set — run via tests/harness/up.sh, which generates one per run}"
# ADMIN_TOKEN flips the platform into strict-auth mode (matches
# production's CP-minted token configuration). Seeded value lets
# E2E scripts authenticate without going through CP.

View File

@ -18,6 +18,22 @@ for arg in "$@"; do
esac
done
# Generate a per-run encryption key. The tenant runs with
# MOLECULE_ENV=production (intentional, to replay prod-shape bugs), and
# crypto.InitStrict() refuses to boot without SECRETS_ENCRYPTION_KEY.
# Generate fresh so:
# - No key-shaped string lives in the repo (avoids muscle-memorying a
# hardcoded value into other places + secret-scanner false positives).
# - Each harness lifetime gets a unique key, mimicking prod's per-tenant
# isolation. Persistence across runs isn't required — the harness DB
# is wiped on every ./down.sh.
# Honor a caller-supplied value if already exported (lets a debug session
# pin a key for reproducibility).
if [ -z "${SECRETS_ENCRYPTION_KEY:-}" ]; then
SECRETS_ENCRYPTION_KEY=$(openssl rand -base64 32)
export SECRETS_ENCRYPTION_KEY
fi
if [ "$REBUILD" = true ]; then
docker compose -f compose.yml build --no-cache tenant cp-stub
fi