From c7ed1014d572f9a4954016a75d880a89b4065bc1 Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Tue, 13 Oct 2026 15:08:38 -0300 Subject: [PATCH] release: check attestations by the file's digest cosign verify-blob-attestation given a file reads it whole and refuses one over COSIGN_MAX_ATTACHMENT_SIZE (128 MiB). The tarball is 338 MB, so v20261013.01's run failed after signing it: "size of layer (338500782) exceeded the limit (134217728)". --digest/--digestAlg checks the in-toto subject against the file's sha256 without reading the file into cosign; verified locally with cosign v3.0.5 over a 200 MB file (the digest passes, another is refused). docs/releasing.md gives the operator the same command. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/release.yml | 18 ++++++++++++------ docs/releasing.md | 6 +++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8b3bf0..b12e087 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -205,29 +205,35 @@ jobs: # Each file is signed and attested, then each bundle checked against the identity that made # it, file by file: a bundle nothing verifies is a step that can quietly stop working. + # + # An attestation is checked by the file's digest, not the file: verify-blob-attestation reads + # a blob whole and refuses one over COSIGN_MAX_ATTACHMENT_SIZE (128 MiB), and the tarball is + # 338 MB (v20261013.01's run failed there). The digest is what the in-toto subject names. - name: Sign and attest what the release offers env: IDENTITY: ${{ github.server_url }}/${{ github.repository }}/.github/workflows/release.yml@${{ github.ref }} run: | set -euo pipefail + attested() { + cosign verify-blob-attestation --type "$1" --bundle "$2" \ + --digest "$(sha256sum "$3" | cut -d' ' -f1)" --digestAlg sha256 \ + --certificate-identity "${IDENTITY}" \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com + } for f in _output/release/*.tar.gz _output/release/*.tar.gz.sha256 _output/release/machine.env _output/release/*.cdx.json; do cosign sign-blob --yes --bundle "${f}.sigstore.json" "${f}" > /dev/null cosign attest-blob --yes --type slsaprovenance1 --predicate _output/provenance.json \ --bundle "${f}.provenance.sigstore.json" "${f}" > /dev/null cosign verify-blob --bundle "${f}.sigstore.json" --certificate-identity "${IDENTITY}" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com "${f}" - cosign verify-blob-attestation --type slsaprovenance1 --bundle "${f}.provenance.sigstore.json" \ - --certificate-identity "${IDENTITY}" \ - --certificate-oidc-issuer https://token.actions.githubusercontent.com "${f}" + attested slsaprovenance1 "${f}.provenance.sigstore.json" "${f}" done # The tarball's SBOM (hack/release), attested over it: what it is made of, by the # workflow that built it. The same SBOM is inside it, at usr/share/spin-stack/sbom. for f in _output/release/*.tar.gz; do cosign attest-blob --yes --type cyclonedx --predicate "${f%.tar.gz}.cdx.json" \ --bundle "${f}.cdx.sigstore.json" "${f}" > /dev/null - cosign verify-blob-attestation --type cyclonedx --bundle "${f}.cdx.sigstore.json" \ - --certificate-identity "${IDENTITY}" \ - --certificate-oidc-issuer https://token.actions.githubusercontent.com "${f}" + attested cyclonedx "${f}.cdx.sigstore.json" "${f}" done go tool attestgate files -require provenance _output/release go tool attestgate files -require cdx -except '*.sha256,machine.env,*.cdx.json' _output/release diff --git a/docs/releasing.md b/docs/releasing.md index 3567806..76eea6d 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -48,9 +48,13 @@ id=https://github.com/spin-stack/spin-machine/.github/workflows/release.yml@refs cosign verify-blob --bundle f.sigstore.json --certificate-identity "$id" \ --certificate-oidc-issuer https://token.actions.githubusercontent.com f cosign verify-blob-attestation --type slsaprovenance1 --bundle f.provenance.sigstore.json \ - --certificate-identity "$id" --certificate-oidc-issuer https://token.actions.githubusercontent.com f + --digest "$(sha256sum f | cut -d' ' -f1)" --digestAlg sha256 \ + --certificate-identity "$id" --certificate-oidc-issuer https://token.actions.githubusercontent.com ``` +The attestation is checked by the file's digest: given the file, `verify-blob-attestation` reads it +whole and refuses one over 128 MiB (`COSIGN_MAX_ATTACHMENT_SIZE`), which the tarball is. + Every release ran on `refs/heads/main`, and that is the ref its certificate names; which release a file is, its provenance says (`buildDefinition.externalParameters.version`), and whoever takes it holds it to the version they asked for.