Files
atomic-design-poc/docs/project/archive/backlog/WP-55-openzaak-secrets-tls.md
T
ehoandClaude Opus 5 12f17d9d73 docs: archive the finished backlogs (RD-30)
Two backlog trees are complete: `docs/project/backlog/` (75 files, every
WP done) and `docs/project/refactor-backlog-setup/` (the arc before it).
Move both under `docs/project/archive/` with `git mv`, so history stays
intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them,
because it points at the now-archived backlog README.

Add `docs/project/archive/README.md`. It states that these trees are
historical and names the two directories that are still live.

Repoint every inbound reference named in RD-30's Files table: CLAUDE.md,
the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the
`document-feature` and `new-ssp` skills, and the readable-codebase PLAN,
README, and RD-19 ticket. Fix two upward-relative links inside the moved
WP files (WP-68, WP-69) that gained a directory level and would otherwise
break. Repoint `.prettierignore`'s two agent-prompt exclusions to their
new path, so prettier keeps leaving those files' exact wording alone.

Mark RD-30 done and check off its acceptance criteria; flip its README
row to done.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:00:38 +02:00

4.8 KiB

WP-55 — Real secrets + TLS for the OpenZaak harness

Status: done Phase: 10 — OpenZaak production hardening

Why

backend/openzaak/docker-compose.openzaak.yml is explicitly a throwaway dev/test harness: SECRET_KEY: wp-54-local-harness-not-for-prod, POSTGRES_HOST_AUTH_METHOD=trust (no DB password), IS_HTTPS: 'no', DISABLE_2FA: 'true'. Before anything else in this phase can be called "production," the instance needs real secrets, real DB auth, and TLS. The BFF side is already fine — ZgwOptions.cs binds from IConfiguration, so this is a deploy-config change, not application code.

Read first

Decisions (pre-made, don't relitigate)

  • Secrets come from the deployment environment (env vars / secrets manager), never checked into compose or appsettings.
  • TLS termination happens at a reverse proxy/ingress in front of OpenZaak — OpenZaak itself doesn't need built-in cert handling.
  • The existing dev harness stays as-is for local iteration (WP-54's trimmed rig is intentional and still valuable); this WP adds a production compose/override or an env-driven parameterization of the same file, not a replacement of the dev rig.

Files

  • backend/openzaak/docker-compose.openzaak.yml (or a new docker-compose.openzaak.prod.yml override)
  • backend/openzaak/README.md
  • backend/src/BigRegister.Api/appsettings*.json / Zgw/ZgwOptions.cs (confirm only, likely no change)

Steps

  1. Parameterize SECRET_KEY, DB user/password, and the ZGW JWT secret via env vars; remove hardcoded values from the committed file.
  2. Switch POSTGRES_HOST_AUTH_METHOD from trust to password auth, password from env.
  3. Set IS_HTTPS: 'yes'; document the required reverse-proxy/ingress TLS termination.
  4. Update backend/openzaak/README.md with the required env vars and the TLS note.
  5. Confirm the BFF's JWT secret already comes from config — no code change expected.

Acceptance criteria

  • No secret value is hardcoded in any committed compose/config file.
  • The production compose fails fast (or docs state clearly) when secrets aren't supplied — no silent fallback to a real-looking default.
  • README documents exactly which env vars must be set and how TLS is terminated.

Deviation from the original plan

The WP's own "Files" section expected the secret to be parameterized directly inside docker-compose.openzaak.yml's environment or a straight env-var override. That covers SECRET_KEY/DB password/IS_HTTPS fine (compose does key-based environment merging across -f files even though the base file writes some blocks as YAML mappings and others as anchors), but the ZGW client secret lives inside setup_configuration/data.yaml, a file OpenZaak's own setup_configuration management command reads — compose has no mechanism to interpolate env vars inside a mounted file's contents. Solved by templating that one file (data.prod.yaml.template, no secret) + a tiny host-side render-prod-secrets.sh (envsubst, fail-fast via ${VAR:?...}) that produces a gitignored data.prod.yaml, which docker-compose.openzaak.prod.yml mounts over the container's data.yaml (bind-mounting a single file inside an already bind-mounted read-only directory works fine in Docker/Podman — verified via docker compose config with the override applied). No new dependency: envsubst is part of gettext, already present on this machine.

Verified for real: docker compose -f docker-compose.openzaak.yml -f docker-compose.openzaak.prod.yml config succeeds with all required env vars set and both environment overrides (SECRET_KEY, DB password/auth method) present in the merged output; fails with a clear ${VAR:?...} error when any is missing. render-prod-secrets.sh itself fails fast (tested) when OPENZAAK_CLIENT_SECRET etc. are unset, and its rendered data.prod.yaml was inspected and matched the template with real values substituted. cd backend && dotnet test (WP-54 harness untouched): 159/159 green. The dev harness (docker-compose.openzaak.yml alone, setup_configuration/data.yaml) is untouched.

Verification

docker compose -f backend/openzaak/docker-compose.openzaak.yml config with required env vars set; cd backend && dotnet test (WP-54 harness tests unaffected); manual: the local dev harness still works with its dev-only values documented as dev-only.

Out of scope

Catalogus provisioning (WP-56), client scope narrowing (WP-57), notifications (WP-58).

Risks

If TLS/secrets docs lag an actual deploy, someone could ship with dev defaults — mitigate by making the prod compose fail without required env vars rather than silently defaulting.