Skip to content

feat(s131): signing, provenance, SBOM and AI-BOM for the headless image - #124

Open
HackTuah wants to merge 6 commits into
mainfrom
slice/131-container-supply-chain
Open

HackTuah wants to merge 6 commits into
mainfrom
slice/131-container-supply-chain

Conversation

@HackTuah

@HackTuah HackTuah commented Oct 8, 2026

Copy link
Copy Markdown
Member

Slice 131: signing, provenance, SBOM and AI-BOM for the headless image. Stacked on #117 (slice 130): this PR's base is 130's branch. Merge #117 first.

The slice stays in_progress. The publish job has never run: it needs the project's signing key and a green image job, and the image job needs the OTP bump. The AI-BOM's run on the real image waits on #119 and the legal review (D7).

What lands

  • Held-key signing (cosign P-256; KMS or PKCS#11 key references work), with no transparency log, so a disconnected or classified site needs only the public key.
    • The cost: a stolen key signs silently.
    • The mitigation: one job holds the key, and GitHub's logged platform provenance is requested beside it.
  • SLSA v1 provenance: names the builder, the repository, the commit, every manifest resource and both bases by digest. An image without a matching revision is refused.
  • SBOM attestation: mix trinity.sbom for the same commit, matched on the image.
  • CycloneDX 1.6 ML-BOM: weights digest, licence, training datasets with their terms quoted, the risk and the legal review status, and the embedding space's manifest. It lists no models until 133 merges and D7 clears.
  • Build L1, with the reason it is not L2. A check fails if the claim outruns the mechanism.
  • docs/regulated/image-verify.md: offline verification a stranger runs with cosign, jq and sha256sum. Each step has its own exit status, and the tests and the workflow run that exact block.
  • The publish job: after a fully green image job, on main, a v* tag or by hand.

Two cosign behaviours, measured and checked around

  • cosign verify --key passes an image that carries only an attestation and no signature.
  • cosign does not check layer blobs: a tampered layer verifies. The documented sequence exits 10 there, where cosign verify alone exits 0.

Criteria

  • Proven: AC1 to AC6 and AC8. AC1, AC2 and AC3 were also run on a clean build of the real hardened image, signed and verified offline.
  • Partly proven: AC7, on a fixture; the run on the real image is held.

Evidence

  • Author's gate: exit 0, 1097 passed (20 properties, 1077 tests), 30 excluded, prod_check: PASS, coverage 80.70%.
  • Reviewer re-run before commit:
    • the 4 new test files: 53 passed, 12 excluded;
    • --only image_signing with real cosign: 12 passed;
    • mix format --check-formatted OK; mix credo --strict --all found no issues;
    • no private key material in the diff.

A 130 defect this found

F-131-2: scripts/headless_image.sh build TAG failed after a successful build ("dir: unbound variable"), and the workflow calls it exactly that way. It is reproduced in isolation and fixed on #117 itself as fix(s130), merged in here.

Owner actions

  1. Generate the signing key pair. Store COSIGN_PRIVATE_KEY and COSIGN_PASSWORD as repository secrets, commit the public half as ci/headless/cosign.pub, and make the GHCR package public.
  2. Approve the OTP 28.5.0.7 bump.
  3. Commission the D7 legal review.

Slice 131, stacked on slice 130 (PR #117).

- Signing with a held key (cosign P-256, any key reference including KMS
  or PKCS#11), without a transparency log, so a disconnected or classified
  site needs only the public key (D-131-1). The cost: a stolen key signs
  silently, so one job holds it, and GitHub's logged platform provenance
  is requested beside it for connected consumers.
- SLSA v1 provenance naming the builder, the repository, the commit,
  every hardening-manifest resource and both bases by digest. An image
  whose revision label is empty or names another commit is refused.
- The SBOM attestation is mix trinity.sbom for the same commit, matched on
  the image.
- A CycloneDX 1.6 ML-BOM from ci/headless/ai_models.yaml:
  - weights digest, licence, and every training dataset with its SPDX id
    or quoted terms;
  - the risk and the legal review status as a consideration;
  - the embedding space's manifest.
  The image's models file lists no models until slice 133 merges and the
  owner's legal review (D7) clears.
- The level claim is Build L1 (ci/headless/slsa.yaml); a check fails if it
  is raised beyond what the workflow's mechanism supports.
- docs/regulated/image-verify.md is the verification a stranger runs,
  offline, with cosign, jq and sha256sum. Each step has its own exit
  status. The tests and the workflow run that exact block, so the page
  cannot drift.
- The publish job runs only after the image job passes, and only on main,
  a v* tag or by hand.
- Two cosign behaviours were measured and are checked around: cosign
  verify passes an image that carries only an attestation, and it does
  not check layer blobs.

Proven on a clean build of the real hardened image. A flipped layer bit
exits 10 where cosign verify alone exits 0. The image also inherited UBI9
micro's revision and created labels (F-131-1), so both are now always
set. Red evidence R1 to R7 is in the slice NOTES.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
…tainer-supply-chain

Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Base automatically changed from slice/130-image-hardening-iron-bank to main October 8, 2026 16:54
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant