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
|
|
@ -89,10 +89,41 @@ removed, not a regression — the claim was false before and now says so.
|
|||
|
||||
```task
|
||||
id: AUDIT-WP-0009-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6de9945f-4dd1-57dd-898a-f59c35b1df6c"
|
||||
```
|
||||
Done 2026-09-10. `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`.
|
||||
|
||||
Three decisions worth recording, all of them refusals.
|
||||
|
||||
**The producer is not the receiver.** A receiver that could rewrite its own
|
||||
attestation could forge it, so the job is a separate workload and the receiver
|
||||
has no API-server egress. `audit-core-egress` is now scoped to
|
||||
`component: receiver` and a new `audit-core-attest-egress` carries the 6443
|
||||
rule; `tests/test_layer_conformance.py` asserts the receiver never gains it.
|
||||
|
||||
**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.
|
||||
|
||||
`audit_core/attest_publish.py` does the publish in stdlib rather than shelling
|
||||
out, because the image carries no `kubectl` and adding one to an audit
|
||||
receiver's image to write a single file is the worse trade.
|
||||
|
||||
The offsite copy stays an operator step: audit-core holds no Nextcloud
|
||||
credential and should not acquire one to publish a hash. Stated plainly in
|
||||
`docs/integrity.md` — until that copy exists, the delivered control is "defends
|
||||
against a database owner, not a cluster owner", and no stronger claim may be
|
||||
made from it. Applying the manifests remains an operator action.
|
||||
Schedule chain-head attestation so the precondition T01 enforces is normally
|
||||
met. `attest-chain` exists and is operator-run; `deploy/` has no job. Add one,
|
||||
write the attestation to the logical-offsite path already used by
|
||||
|
|
@ -275,10 +306,23 @@ was created and no production manifest was applied by this source change.
|
|||
|
||||
```task
|
||||
id: AUDIT-WP-0009-T10
|
||||
status: todo
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "ac3464fd-3df9-51a1-b1f4-3bf3c60e4265"
|
||||
```
|
||||
Done 2026-09-10. `layer.yaml` now lists four infrastructure contacts —
|
||||
`platform-pg`, `state-hub`, `kube-apiserver`, and 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` makes the totality claim mechanical rather
|
||||
than a promise to remember: the egress destinations are derived from
|
||||
`deploy/networkpolicies.yaml`, the registry from the pinned image digests, and
|
||||
a new contact appearing in the manifests without a row here fails the test. It
|
||||
also pins the declaration's own content — engine/evidence, no decision surface,
|
||||
`approval_validity_query: forbidden`, and an evidence bound that never claims
|
||||
occurrence.
|
||||
Make the §5 conformance check total. `layer.yaml` declares
|
||||
`tooling_contacts: []`, true under §5 as written — audit-core is an Engine and
|
||||
holds no `key-cape` or OpenBao client. Companion §4 asks that uncatalogued
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue