From e2bf3ddb9488e2890e7a36e7d579e6db3c3b51d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Mon, 25 May 2026 22:59:09 +0200 Subject: [PATCH] fix(infra): refuse compose up when production secrets are missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit infrastructure/docker-compose.yml had \`\${VAR:-default}\` fallbacks for JWT_SECRET, SESSION_SECRET, and POSTGRES_PASSWORD that silently substituted known-weak values (\`change-me-in-production\`, \`trails\`) when the env wasn't set. A misconfigured prod deploy would happily come up with these defaults — JWT/session forgery + a guessable Postgres password. Switched the four critical substitutions to \`\${VAR:?message}\` so \`docker compose up\` fails loud instead. Mirrors the existing pattern already used for BROUTER_URL/BROUTER_AUTH_TOKEN and (in staging) for JWT_SECRET/SESSION_SECRET. Sites fixed: - journal: DATABASE_URL (POSTGRES_PASSWORD), JWT_SECRET, SESSION_SECRET - planner: DATABASE_URL (POSTGRES_PASSWORD) - postgres: POSTGRES_PASSWORD (container itself) - exporter: DATA_SOURCE_NAME (POSTGRES_PASSWORD) - staging: both DATABASE_URLs Production already provides all three via SOPS-encrypted secrets.app.env, so this is a defense-in-depth change with no behavior change on a properly-configured deploy. Updated infrastructure/.env.example to make SESSION_SECRET explicit (was missing) and call out the trio as required. Co-Authored-By: Claude Opus 4.7 (1M context) --- infrastructure/.env.example | 4 ++++ infrastructure/docker-compose.staging.yml | 4 ++-- infrastructure/docker-compose.yml | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/infrastructure/.env.example b/infrastructure/.env.example index 85d59dc..0c41a05 100644 --- a/infrastructure/.env.example +++ b/infrastructure/.env.example @@ -2,8 +2,12 @@ # Copy to .env and fill in values DOMAIN=trails.cool +# These three are REQUIRED for compose up — the production +# docker-compose.yml refuses to start without them. Generate strong +# random values; never reuse anything from this example file. POSTGRES_PASSWORD=change-me JWT_SECRET=change-me +SESSION_SECRET=change-me S3_ACCESS_KEY= S3_SECRET_KEY= diff --git a/infrastructure/docker-compose.staging.yml b/infrastructure/docker-compose.staging.yml index 3b1d60d..b5c6c00 100644 --- a/infrastructure/docker-compose.staging.yml +++ b/infrastructure/docker-compose.staging.yml @@ -48,7 +48,7 @@ services: # to its own planner subdomain. PLANNER_URL: ${PLANNER_URL:-https://planner.${DOMAIN}} IS_FLAGSHIP: "" - DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/${STAGING_DATABASE:?STAGING_DATABASE must be set} + DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/${STAGING_DATABASE:?STAGING_DATABASE must be set} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set} SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set} NODE_ENV: production @@ -86,7 +86,7 @@ services: BROUTER_URL: ${BROUTER_URL:?BROUTER_URL must be set} BROUTER_AUTH_TOKEN: ${BROUTER_AUTH_TOKEN:?BROUTER_AUTH_TOKEN must be set} OVERPASS_URLS: ${OVERPASS_URLS:-https://lz4.overpass-api.de/api/interpreter,https://overpass-api.de/api/interpreter} - DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/${STAGING_DATABASE} + DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/${STAGING_DATABASE} NODE_ENV: production PORT: 3001 SENTRY_RELEASE: ${SENTRY_RELEASE:-} diff --git a/infrastructure/docker-compose.yml b/infrastructure/docker-compose.yml index 01c1c04..029b211 100644 --- a/infrastructure/docker-compose.yml +++ b/infrastructure/docker-compose.yml @@ -37,9 +37,9 @@ services: # "Powered by trails.cool" footer link when not. Default empty = # self-host, which is the safer default. IS_FLAGSHIP: ${IS_FLAGSHIP:-} - DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails - JWT_SECRET: ${JWT_SECRET:-change-me-in-production} - SESSION_SECRET: ${SESSION_SECRET:-change-me-in-production} + DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/trails + JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set in SOPS secrets.app.env} + SESSION_SECRET: ${SESSION_SECRET:?SESSION_SECRET must be set in SOPS secrets.app.env} NODE_ENV: production PORT: 3000 SENTRY_RELEASE: ${SENTRY_RELEASE:-} @@ -84,7 +84,7 @@ services: # community instance misbehaves. Override per-env with # OVERPASS_URLS in the SOPS file. OVERPASS_URLS: ${OVERPASS_URLS:-https://lz4.overpass-api.de/api/interpreter,https://overpass-api.de/api/interpreter} - DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails + DATABASE_URL: postgres://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/trails NODE_ENV: production PORT: 3001 SENTRY_RELEASE: ${SENTRY_RELEASE:-} @@ -108,7 +108,7 @@ services: - trails-shared environment: POSTGRES_USER: trails - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-trails} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env} POSTGRES_DB: trails volumes: - pgdata:/var/lib/postgresql/data @@ -129,7 +129,7 @@ services: image: prometheuscommunity/postgres-exporter:latest restart: unless-stopped environment: - DATA_SOURCE_NAME: postgresql://trails:${POSTGRES_PASSWORD:-trails}@postgres:5432/trails?sslmode=disable + DATA_SOURCE_NAME: postgresql://trails:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD must be set in SOPS secrets.app.env}@postgres:5432/trails?sslmode=disable PG_EXPORTER_EXTEND_QUERY_PATH: /etc/postgres-exporter/queries.yml volumes: - ./postgres/queries.yml:/etc/postgres-exporter/queries.yml:ro