feat(openzaak): real secrets + TLS for the production OpenZaak harness (WP-55)
CI / changes (push) Successful in 8s
CI / lint (push) Successful in 12s
CI / frontend (push) Successful in 14s
CI / storybook-a11y (push) Successful in 17s
CI / backend (push) Successful in 1m51s
CI / semgrep (push) Successful in 1m13s
CI / e2e (push) Successful in 2m56s
CI / api-client-drift (push) Successful in 1m41s

docker-compose.openzaak.prod.yml layers real SECRET_KEY/DB password/site
domain/allowed-hosts (all required, fail-fast via ${VAR:?...}) on top of the
WP-54 dev harness, switches Postgres off trust auth, and sets IS_HTTPS for a
front-facing reverse-proxy TLS setup. The ZGW client secret lives inside a
file setup_configuration reads rather than a compose env var, so it's
templated (data.prod.yaml.template, no secret) and rendered host-side via
render-prod-secrets.sh into a gitignored data.prod.yaml, mounted over the
container's dev data.yaml. ZgwOptions.cs already binds from IConfiguration,
so the BFF side needed no code change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-30 12:27:21 +02:00
co-authored by Claude Sonnet 5
parent f21c3c7ca2
commit 3588057a75
9 changed files with 162 additions and 11 deletions
@@ -1,6 +1,6 @@
# WP-55 — Real secrets + TLS for the OpenZaak harness
Status: todo
Status: done
Phase: 10 — OpenZaak production hardening
## Why
@@ -45,10 +45,35 @@ change, not application code.
## 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
- [x] No secret value is hardcoded in any committed compose/config file.
- [x] 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.
- [x] 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