§9 prod compose secrets (todo §9); the base compose.yml was already the full prod stack (web + Postgres + Kratos/Keto/Hydra + migrations + bootstrap, no source mount) but set REQUIRE_SECURE_SECRETS=true without ever passing CSRF_SECRET into web, so docker compose -f compose.yml up couldn't boot. Wired CSRF_SECRET: ${CSRF_SECRET:-dev-insecure-csrf-secret} — env-supplied with the throwaway as the only fallback; config.ts's existing REQUIRE_SECURE_SECRETS logic rejects that throwaway so a forgotten prod secret fails loud (verified prod-unset→reject, prod-set→real, dev→throwaway+toggle-off→boots). Used :- not :? because compose interpolates the base per-file before merging the dev override (confirmed empirically), so :? would also break the zero-config dev up. Tests-first: compose.test.ts guards secret-via-env + no-source-mount + prod/dev toggle split + postgres-creds-via-env. README prod section corrected (dropped the stale planned note). typecheck + 310 units green.

This commit is contained in:
2026-06-20 01:05:15 +02:00
parent 56047815a0
commit b3b51db52b
4 changed files with 23 additions and 3 deletions

View File

@@ -57,6 +57,20 @@ test("prod base publishes no internal Ory ports; dev exposes the host-facing one
assert.match(override, /"4444:4444"/, "dev publishes hydra public");
});
test("prod base supplies the app secret via env and mounts no source; dev override flips it", () => {
// §9 prod compose: CSRF_SECRET comes from the environment (dev-throwaway fallback that
// REQUIRE_SECURE_SECRETS rejects in prod — see config.ts); the base never bind-mounts the
// source tree (runs the built image), while the dev override does for live editing.
assert.match(webBlock, /CSRF_SECRET:\s*\$\{CSRF_SECRET\b/, "base wires CSRF_SECRET from env");
assert.doesNotMatch(webBlock, /-\s+\.:\/app\b/, "base mounts no source tree");
assert.match(override, /-\s+\.:\/app\b/, "dev override bind-mounts the source");
// Secret/cookie hardening: enforced in prod, off in dev so the throwaway + http cookies pass.
assert.match(webBlock, /REQUIRE_SECURE_SECRETS:\s*"true"/, "base enforces real secrets");
assert.match(override, /REQUIRE_SECURE_SECRETS:\s*"false"/, "dev allows the throwaway");
// Postgres credentials are env-supplied (dev default), never a baked-in literal.
assert.match(compose, /POSTGRES_PASSWORD:\s*\$\{POSTGRES_PASSWORD\b/, "postgres password via env");
});
test("a one-shot bootstrap seeds the stack before web starts", () => {
// §3 MVP bar: `bootstrap` runs after kratos+keto are healthy, seeds the admin +
// JWKS, then exits; web waits for it to complete. Live seeding is boot-verified.