Add deployment manifests, custody-class guard and request counters
AUDIT-WP-0005-T03 (progress). Manifests validated --dry-run=server --validate=strict against railiance01; not applied, since deployment is gated on RAPP-POSTGRES-WP-0002 and T02 credentials. Nothing here mutates the cluster. Conventions read off the deployed user-engine workload rather than invented: digest-pinned image from forgejo.coulomb.social, runAsNonRoot with RuntimeDefault seccomp, no privilege escalation, all capabilities dropped, readOnlyRootFilesystem, probes on a named http port, same resource envelope. The namespace carries railiance.io/postgres-client: platform-pg, which is what platform-pg-consumer-ingress in rapp-postgres admits; without that label the pod cannot reach the database at all. NetworkPolicies default-deny both directions, then permit ingress from the user-engine namespace only, a separately labelled operator read path, and egress to PostgreSQL in databases plus DNS. Three decisions worth naming. Liveness is /healthz while readiness is /readyz, so a database outage drops the pod from the Service rather than restarting it in a loop. readOnlyRootFilesystem enforces the empty-filesystem property rather than trusting it, so the SQLite fallback physically cannot accumulate audit records on ephemeral storage. AUDIT_CORE_REQUIRE_CUSTODY_CLASS=archive makes a missing database URL a startup failure instead of a silent downgrade to the development store. Counters deferred from WP-0004-T06 are exposed as JSON at /v1/stats behind the read privilege, not as Prometheus exposition format: the cluster runs no Prometheus, no ServiceMonitor CRD and no other scrape target, so an exposition endpoint would target a scrape path that does not exist. Usable with curl now and a small step from /metrics later. Tests 77 -> 80. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
88d16847ff
commit
2f4e1adf66
6 changed files with 393 additions and 5 deletions
|
|
@ -400,3 +400,37 @@ def test_counters_survive_restart(tmp_path):
|
|||
reopened = IngestionApplication(SQLiteAuditBackend(path), "opaque")
|
||||
_, body = invoke(reopened, None, path="/v1/secret-findings", method="GET", body=b"")
|
||||
assert body["secret_findings"][0]["occurrences"] == 1
|
||||
|
||||
|
||||
# --- deployment guards and counters (WP-0005-T03) ---------------------------
|
||||
|
||||
def test_required_custody_class_refuses_a_development_backend(tmp_path):
|
||||
"""Losing AUDIT_CORE_DATABASE_URL must fail to start, not silently
|
||||
downgrade custody to the development store."""
|
||||
backend = SQLiteAuditBackend(str(tmp_path / "dev.db"))
|
||||
with pytest.raises(ValueError, match="does not meet the required"):
|
||||
IngestionApplication(backend, "opaque", require_custody_class="archive")
|
||||
|
||||
|
||||
def test_counters_track_each_outcome(tmp_path):
|
||||
app, _ = bound_app(tmp_path, may_read=True)
|
||||
invoke(app, event()) # accepted
|
||||
invoke(app, event()) # duplicate
|
||||
invoke(app, event(subject="other")) # conflict
|
||||
invoke(app, event(id="e2", tenant="tenant:coulomb"), key="e2") # rejected
|
||||
invoke(app, event(), token="nope") # unauthorized
|
||||
|
||||
_, body = invoke(app, None, path="/v1/stats", method="GET", body=b"")
|
||||
counts = body["counts"]
|
||||
assert counts["accepted"] == 1
|
||||
assert counts["duplicate"] == 1
|
||||
assert counts["conflict"] == 1
|
||||
assert counts["rejected"] == 1
|
||||
assert counts["unauthorized"] == 1
|
||||
assert body["since"]
|
||||
|
||||
|
||||
def test_stats_require_the_read_privilege(tmp_path):
|
||||
app, _ = bound_app(tmp_path, may_read=False)
|
||||
status, _ = invoke(app, None, path="/v1/stats", method="GET", body=b"")
|
||||
assert status.startswith("403")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue