Deliver database credentials as a rotatable mounted directory
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

AUDIT-WP-0005-T02 (progress). rapp-postgres has landed platform-pg with the
audit_core database, roles, and dynamic credential provisioning, so
audit-core's side is now built against it.

In-cluster delivery is a mounted directory rather than environment variables.
A dynamic lease rotates while the pod runs and an env var is fixed at process
start, so env delivery would force a restart on every rotation - and every
restart is a delivery gap, which is what this task forbids.
CredentialDirectory is re-read on every connection attempt via psycopg_pool's
callable kwargs, so a rotated lease takes effect with no restart. Rotation is
logged by password fingerprint, never by value.

deploy/externalsecrets.yaml follows the ClusterSecretStore -> ExternalSecret ->
Secret pattern already used by activity-core and rapp-qonto, at a 15m refresh
rather than the default 1h since the interval bounds how long a revoked lease
can stay mounted. All manifests validated --dry-run=server --validate=strict.

The rotation test was initially vacuous: it passed against a deliberately naive
implementation that read credentials once at startup, because pooled sessions
stay authenticated after a password change and nothing forced a reconnect. It
now terminates the role's sessions first, and is verified to fail against the
naive implementation and pass against the real one. Tests 82 -> 84.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-12 01:36:28 +02:00
parent fc48378a3f
commit 7636e83dcc
7 changed files with 364 additions and 20 deletions

View file

@ -115,7 +115,7 @@ cluster and belongs to T05.
```task
id: AUDIT-WP-0005-T02
status: todo
status: progress
priority: high
state_hub_task_id: "831b2472-0d80-4369-a5e3-eb08ef3526b1"
```
@ -133,6 +133,57 @@ Done when audit-core runs against a provisioned database using a credential it
never received as a literal, and rotating that credential does not drop
events.
Progress 2026-08-12: rapp-postgres has landed the platform — `platform-pg` is
healthy, the `audit_core` database exists with `audit_core_owner`/`_migrate`/
`_app`, and RAPP-POSTGRES-WP-0002-T04 delivered dynamic credential
provisioning. audit-core's side is now built against it.
**Two consumption paths, both supported.** The rapp-postgres playbook has the
`railiance-platform` broker inject `PGUSER`/`PGPASSWORD`/`PGHOST`/`PGPORT`/
`PGDATABASE` into a child process; audit-core previously accepted only
`AUDIT_CORE_DATABASE_URL`, which would have meant assembling a DSN by hand from
those variables and putting the credential back into audit-core's own
configuration — the thing the lane exists to prevent. An empty conninfo now
lets libpq read them directly.
**In-cluster delivery is a mounted directory, not environment variables**
(`AUDIT_CORE_CREDENTIAL_DIR`, decision by Bernd). This is the design point:
a dynamic lease rotates *while the pod runs*, and an environment variable is
fixed at process start. Env delivery would force a restart on every rotation,
and every restart is a delivery gap — precisely what this task forbids.
`audit_core.credentials.CredentialDirectory` is re-read on every connection
attempt, exploiting psycopg_pool's support for a callable `kwargs`, so a
rotated lease is picked up by the next connection with no restart.
Rotation is logged by password fingerprint, never by value.
`deploy/externalsecrets.yaml` follows the ClusterSecretStore → ExternalSecret →
Secret pattern already used by activity-core and rapp-qonto, with a 15m refresh
rather than the platform default 1h — the interval bounds how long a revoked
lease can stay mounted.
The rotation test was initially **vacuous** and was caught by running it
against a deliberately naive implementation that read credentials once at
startup: it passed. Existing pooled sessions stay authenticated after a
password change, so nothing forced a reconnect. The test now terminates the
role's sessions before checking, and has been verified to fail against the
naive implementation with an authentication error and pass against the real
one. Tests 82 -> 84.
**Security finding, fixed.** `scripts/isolation-test.sh` in rapp-postgres left
three login roles on the production cluster after its remote run —
`audit_login` (member of `audit_core_app`, so full read/write on the audit
trail), `audit_migrate_login`, and `probe_login` — with the committed literal
password `probe` and no expiry. Roles dropped from `platform-pg` on 2026-08-12
after confirming no active sessions; the group roles are untouched and remain
NOLOGIN. The harness now uses a per-run random password, `VALID UNTIL` one
hour, and an exit trap that drops the roles in every mode including on failure.
Remaining before done: the `openbao-audit-core` ClusterSecretStore and the
`rapp-postgres/audit-core-runtime` OpenBao role are prerequisites owned by
rapp-postgres/railiance-platform, not this repo. Once they exist, apply and
verify a live lease and a live rotation.
## T03 - Deploy the receiver
```task