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

@ -316,3 +316,96 @@ def test_missing_connection_information_is_a_clear_error(monkeypatch):
with pytest.raises(ValueError, match="no connection information"):
PostgresAuditBackend()
# --- rotating credentials from a mounted directory (T02) --------------------
def _write_credentials(directory, *, user, password, url):
import urllib.parse
parsed = urllib.parse.urlparse(url)
(directory / "username").write_text(user)
(directory / "password").write_text(password)
(directory / "host").write_text(parsed.hostname or "127.0.0.1")
(directory / "port").write_text(str(parsed.port or 5432))
(directory / "dbname").write_text((parsed.path or "/postgres").lstrip("/"))
@pg_only
def test_rotating_a_mounted_credential_needs_no_restart(tmp_path):
"""The property env vars cannot provide.
A dynamic lease changes while the pod runs. Reading the credential at
connection time rather than at startup is what makes that rotation
invisible to senders — no restart, no delivery gap.
"""
import psycopg
from audit_core.postgres_backend import PostgresAuditBackend
schema = f"rot_{uuid.uuid4().hex[:10]}"
role = f"lease_{uuid.uuid4().hex[:8]}"
admin = psycopg.connect(PG_URL, autocommit=True)
directory = tmp_path / "db"
directory.mkdir()
backend = None
try:
admin.execute(f"CREATE ROLE {role} LOGIN PASSWORD 'first' SUPERUSER")
_write_credentials(directory, user=role, password="first", url=PG_URL)
backend = PostgresAuditBackend(
"", schema=schema, credential_dir=str(directory), min_size=1, max_size=2
)
event = make_event("rot-1")
assert backend.accept(event, digest(event)).duplicate is False
# Rotate: new password in the database, new password in the mount.
# The process is never restarted and the object is never rebuilt.
admin.execute(f"ALTER ROLE {role} PASSWORD 'second'")
(directory / "password").write_text("second")
# Existing pooled sessions stay authenticated after a password change,
# so they must be terminated for this to test anything at all — without
# this the pool keeps serving on the old connections and an
# implementation that read the credential once at startup would pass.
admin.execute(
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity "
"WHERE usename = %s AND pid <> pg_backend_pid()", (role,)
)
backend.pool.check() # discard the dead connections and reopen
event2 = make_event("rot-2")
assert backend.accept(event2, digest(event2)).duplicate is False
assert backend.get("rot-1")["event_id"] == "rot-1"
finally:
if backend:
with backend.pool.connection() as conn:
conn.execute(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE')
backend.close()
admin.execute(
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity "
"WHERE usename = %s AND pid <> pg_backend_pid()", (role,)
)
admin.execute(f"DROP ROLE IF EXISTS {role}")
admin.close()
@pg_only
def test_a_stale_mounted_credential_fails_closed(tmp_path):
"""A revoked lease must surface as unavailable, not as silent success."""
import psycopg
from audit_core.postgres_backend import PostgresAuditBackend
role = f"lease_{uuid.uuid4().hex[:8]}"
admin = psycopg.connect(PG_URL, autocommit=True)
directory = tmp_path / "db"
directory.mkdir()
try:
admin.execute(f"CREATE ROLE {role} LOGIN PASSWORD 'only' SUPERUSER")
_write_credentials(directory, user=role, password="wrong-password", url=PG_URL)
with pytest.raises((BackendUnavailableError, Exception)):
PostgresAuditBackend("", credential_dir=str(directory), migrate=True)
finally:
admin.execute(f"DROP ROLE IF EXISTS {role}")
admin.close()