AUDIT-WP-0009 T02/T10 — schedule attestation, and make the §5 check total
T02. deploy/attest-cronjob.yaml: daily at 03:17 UTC against the 168h window, its own ServiceAccount, and a Role reaching exactly one named ConfigMap — get/update/patch, no create, no list. audit_core/attest_publish.py does the publish in stdlib; the image carries no kubectl, and adding one to an audit receiver's image to write a single file is the worse trade. Three refusals, all deliberate: The producer is not the receiver. A receiver that could rewrite its own attestation could forge it. audit-core-egress is now scoped to component: receiver and a separate audit-core-attest-egress carries the 6443 rule, so the receiver never gains API-server reach. Asserted by test. It refuses to publish over a broken chain. A fresh head written over a break replaces an honest chain_break with a fresh-looking attestation. Stale degrades the claim visibly; false does not. Mounted as a directory, not subPath. Found while writing the manifest: a subPath ConfigMap mount is resolved once at pod start and never updates, so the daily attestation would land in the ConfigMap and never reach the running receiver — tamper_evidence would age out to false while the job reported success every night, silent in both directions. The offsite copy stays an operator step. audit-core holds no Nextcloud credential and should not acquire one to publish a hash, so docs/integrity.md states the bound plainly: until that copy exists the delivered control defends against a database owner, not a cluster owner, and no stronger claim may be made from it. T10. layer.yaml lists four infrastructure contacts — platform-pg, state-hub, kube-apiserver, the container registry — each with its role and whether another layer reads it. tooling_contacts stays [], which is true under §5 as written; the companion's totality request is met by the uncatalogued list rather than by inventing a Tooling row. tests/test_layer_conformance.py derives the egress destinations from the manifests and the registry from the pinned digests, so a new contact appearing in deploy/ without a row fails the test rather than waiting for a reviewer to notice. Applying the manifests remains an operator action; nothing here was applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nb7Q6ZmXppNDkTWytfYqfv Assistant: claude-code Assistant-Model: opus Assistant-Process: 2069992@bnt-lap001 Assistant-Session: 167dd7f8-2a25-4be1-aa46-3b6f1a5f94c6
This commit is contained in:
parent
3c2cdcdf79
commit
de9e3abe5f
9 changed files with 666 additions and 8 deletions
|
|
@ -68,9 +68,49 @@ itself is broken or cannot be walked. Unreadable is treated as absent
|
|||
deliberately: a malformed file must not hold up a claim that a missing
|
||||
file would drop.
|
||||
|
||||
Until `AUDIT-WP-0009-T02` schedules the attestation job, no attestation
|
||||
is mounted in production and `/readyz` reports `tamper_evidence: false`.
|
||||
That is the honest reading of the current state, not a regression.
|
||||
## How the attestation is produced and where it lives
|
||||
|
||||
`AUDIT-WP-0009-T02`, `deploy/attest-cronjob.yaml`.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Cadence | daily, `17 3 * * *` UTC, against the 168h window |
|
||||
| Producer | CronJob `audit-core-attest-chain`, its own ServiceAccount |
|
||||
| Published to | ConfigMap `audit-core-chain-head`, mounted read-only by the receiver at `/etc/audit-core/attestation/` |
|
||||
| Offsite copy | operator step, logical-offsite path (`rapp-postgres` / Nextcloud + age) |
|
||||
|
||||
Three properties of that arrangement are load-bearing, not incidental.
|
||||
|
||||
**The producer is not the receiver.** A receiver that could rewrite its own
|
||||
attestation could forge it. So the job runs as a separate workload whose Role
|
||||
reaches exactly one named ConfigMap, and NetworkPolicy gives the receiver no
|
||||
API-server egress at all. Asserted in `tests/test_layer_conformance.py`.
|
||||
|
||||
**It refuses to publish over a broken chain.** If `verify_chain` reports a
|
||||
break the job fails and leaves the previous attestation standing. Publishing a
|
||||
fresh head over a break would replace an honest `chain_break` with a
|
||||
fresh-looking attestation — a stale attestation degrades the claim visibly, a
|
||||
false one does not.
|
||||
|
||||
**The ConfigMap is mounted as a directory, not with `subPath`.** A `subPath`
|
||||
ConfigMap mount is resolved once at pod start and never updates, so the daily
|
||||
attestation would reach the ConfigMap and never the running receiver:
|
||||
`tamper_evidence` would age out to `false` while the job reported success every
|
||||
night. That failure is silent in both directions, which is why the mount shape
|
||||
is part of this contract rather than a deployment detail.
|
||||
|
||||
**What this reaches, and what it does not.** The in-cluster copy puts the
|
||||
attestation outside `platform-pg`, so a database owner who rewrites a suffix
|
||||
cannot also rewrite the head. It does **not** withstand a cluster owner, who
|
||||
can edit the ConfigMap. The offsite copy is the one that survives losing the
|
||||
cluster, and audit-core deliberately does not write it — this repo holds no
|
||||
Nextcloud credential and should not acquire one to publish a hash. Until an
|
||||
operator establishes that copy, the delivered control is "defends against a
|
||||
database owner", and no stronger claim may be made from it.
|
||||
|
||||
Before the first run the ConfigMap is empty and `/readyz` reports
|
||||
`tamper_evidence: false` with reason `no_attestation`. That is the correct
|
||||
day-one state, not a regression.
|
||||
|
||||
Do not write the attestation into the Barman prefix
|
||||
(`platform-pg/` on `resource:platform:audit-storage`). That copy is
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue