Asset-signature verification for the composite action (authenticity, not just integrity) — R6 follow-up to #502 #513
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit#513
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up filed from Surveyor's review 4531 on #511 (S1, pass-with-disclosure — non-blocking, landed with #502).
The gap
The composite
action.yml(#502, ADR-0008 §4a lean-D) fetchesrt-<os>-<arch>and verifies it against the release'schecksums.txtvia sha256. That gives integrity — the asset matches the checksum manifest — but not authenticity: the asset andchecksums.txtare same-origin (both fetched from the same Forgejo release, no signature). A compromise that can rewrite the release asset can rewritechecksums.txtin the same motion, and the checksum check passes.This is honestly scoped in #502 as a minimal prototype per ADR-0009 §9 ("prototyped in Phase 0a") and is fine for the current private, single-audience distribution model. But ADR-0008 §6 R6 (distribution integrity, couples to §4a) has an authenticity half with no tracker — only #367 exists, which is unrelated docs. Filing so the deferral is tracked, not just commented (Surveyor's framing).
Scope when picked up
goreleaser.yml(goreleaser has nativesigns:support — cosign / gpg / minisign).action.ymlbefore the sha256 check (signature over the checksums.txt, then checksums.txt over the asset — the standard goreleaser chain).action.ymlinline (the inline pointer was deferred out of the #502 PR to avoid staling the head-pinned approval Bosun was landing — it belongs in this follow-up's own PR, which touchesaction.ymlregardless).Formal acceptance criteria and disposition
The following seven ACs were recorded before implementation in comment 104351. They are checked here against PR #1058; the selected method is minisign and the gate remains fail-closed.
AC1 — Trusted verification root and rotation. The release channel uses the operator-managed
RELEASE_TOOLKIT_MINISIGN_PUBLIC_KEYActions variable, delivered explicitly to the composite FETCH path; it is not learned from the release being verified. The variable accepts concatenated complete public-key blocks for overlap rotation. Documentation names operator control, key-id recording, append-and-verify rotation, old-key support window, and retirement; missing or unusable root fails.Evidence: PR #1058 changes
composite/bootstrap-rt/action.yml, all six reusable callers,scripts/fetch-rt.sh, anddocs/integration.md.AC2 — Publish-side signature. GoReleaser signs the exact generated
checksums.txtwith the operator-heldRELEASE_TOOLKIT_MINISIGN_SECRET_KEYand publisheschecksums.txt.minisig; signing capability and secret presence are checked before publish, and publish writes the secret only to a mode-0600 runner-temporary file.Evidence:
.goreleaser.yamlsignspipe and.forgejo/workflows/goreleaser.yml; pinned GoReleaser v2.17.0checkreports1 configuration file(s) validated.AC3 — Verification ordering. The composite's shared FETCH implementation downloads the detached sidecar and verifies it with minisign before parsing or using
checksums.txt, selecting an asset, accepting a cache hit, or computing SHA-256. The existing checksum and install path continue only after signature success. The root Dockeraction.ymldoes not fetch release assets and carries the tracker linkage separately.Evidence:
scripts/fetch-rt.sh,composite/bootstrap-rt/action.yml, and the structuraltests/workflows.bats#513 arm.AC4 — Fail-closed error contract. Missing sidecar/unsigned manifest, missing or invalid public key, unverifiable or wrong-key signature, and a changed manifest each produce an attributable nonzero failure; no path warns, skips, or continues to asset use.
Evidence:
tests/fetch-rt.batsarms for tampered checksums, missing signature, missing trusted root, and wrong/unverifiable key all pass with the required early-failure assertions.AC5 — Artifact-checkable controls. The implementation includes a valid signed-manifest positive control, tampered-manifest/signature, missing-signature, missing-key, wrong-key, and overlapping-key-rotation controls. The pre-existing valid checksum/asset mismatch control remains covered, so authenticity does not weaken integrity verification.
Evidence:
bats tests/fetch-rt.batsreports1..17with all tests passing; the full suite reports1..153with all tests passing.AC6 — Key-material hygiene. The private signing key enters only through the operator secret, is written to a 0600 temporary file, is not committed, embedded in a release asset, passed as a command argument, or printed; the workflow unsets the secret before invoking GoReleaser. The repository tests use non-secret fake key fixtures only.
Evidence: workflow and GoReleaser diff plus the staged-source scan report
nonefor private-key material. Public-key delivery is external operator configuration, not repository content.AC7 — Documentation and tracker linkage. The threat model now states the precise same-origin authenticity protection for
checksums.txtwithout claiming general supply-chain attestation.action.ymlreferencesrelease-toolkit#513; integration/ADR/Arc42 docs name the signature asset, trust-root delivery, rotation, fail-closed behavior, and thealcatraz-infra#528image dependency.Evidence: PR #1058 documentation changes and
action.ymltracker comment.Verification evidence
bats --print-output-on-failure tests/→1..153, all tests passed.go run ./cmd/rt fragment-check changelog.d→ supported kinds/header/reference checks pass; density PASS.shellcheck scripts/fetch-rt.sh scripts/bootstrap-rt.sh scripts/release-assets.sh,bash -n ..., andgit diff --check→ pass.YAML parsed: 23 files.go run github.com/goreleaser/goreleaser/v2@v2.17.0 check --config .goreleaser.yaml→1 configuration file(s) validated.checksums.txt;scripts/fetch-rt.shverified the detached signature and checksum before installing the test asset → exit 0.Status
Implemented in PR #1058 and merged at exact merge commit
cfffa822b188e361aafeff57ef163d176d81c670; its reviewed PR head was9e443cebe073927c8b09da0048c01e3af705f1eb. Official Sentry review 6171 was exact-bound and current, and Forgejo CI was 25/25 successful. Currentmainmatches the merge commit.The merged tree enforces minisign authenticity before checksum and asset use, with hard failures for missing, invalid, wrong-key, or unverifiable signatures. The operator-managed trust root and private signing secret remain deployment configuration; no key material is placed in the repository. All seven acceptance criteria are discharged.
Not urgent
Gated on the distribution model widening beyond private single-audience. The integrity check is the correct floor for now; this is the authenticity ceiling for when adoption justifies it.
Refs: #502 · #511 (review 4531, S1) · ADR-0008 §4a lean-D + §6 R6 · ADR-0009 §9
✅ OPERATOR RULING: minisign.
Not cosign, not GPG — and the deciding question was who verifies, not which is strongest.
Today the verifier is us and any adopter on a private forge.
checksums.txt⚠️ The gap this closes, restated so the scope stays honest: the asset and its
checksums.txtare same-origin, so anything able to rewrite one can rewrite both. Integrity yes, authenticity no — confirmed at source by @shipwright:action.ymlcarries 2 checksum references and 0 for any signing tool.Explicit acceptance criteria: minisign asset authenticity
No implementation changes are being made before these criteria are recorded.
checksums.txtwith the operator-held minisign private key and publish the detached signature alongside it (for examplechecksums.txt.minisig). Signing or upload failure must fail the publish path; a release must not be reported complete without the signature.action.yml, download the detached signature and verify it with minisign and the trusted public key before parsing/usingchecksums.txt, before selecting an asset, and before any sha256 or cache acceptance. Only a valid signature permits the existing checksum and install path to continue.checksums.txtagainst same-origin release rewriting; do not claim general supply-chain attestation. Add an inline reference to this tracker inaction.yml, and document the trust-root delivery/rotation contract and signature asset name.Verification evidence for the completed PR must include the exact publish/sign and composite verification commands, the positive and inverse-control outputs/exit codes, and the CI result.
Artifact correction requested by Sentry review 6168: the exact tree has 17 tests in
tests/fetch-rt.bats, so its exact-tree run is1..17; only the fullbats tests/run is1..153. Please correct AC5/evidence to those two counts. No code change or new review request is needed; preserve the existing exact head and review row.