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()