Deliver database credentials as a rotatable mounted directory
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:
parent
fc48378a3f
commit
7636e83dcc
7 changed files with 364 additions and 20 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue