← Recipes

阅读中文版本

Attest Release Archives and Verify Before Deploy

Sign a build artifact with GitHub attestations, then verify its digest and source ref in the deploy job so a swapped archive never ships.

~25 minsupply-chaingithub-actionsprovenance

Task

Prove that the tarball promoted to production was produced by the trusted workflow from the expected branch.

When to use this

A deploy job downloads a release archive by URL. If the storage bucket is writable by another team, a swapped archive with the same name deploys untrusted code.

Prerequisites

  • GitHub Actions with id-token: write and attestations: write
  • GitHub CLI 2.49 or newer
  • A release artifact built inside the workflow

Steps

1. Build the archive and attest it by subject path in the same job.

permissions:
  contents: read
  id-token: write
  attestations: write

steps:
  - uses: actions/checkout@v4
  - run: tar -czf dist/payments-1.5.0.tar.gz -C build .
  - uses: actions/attest-build-provenance@v2
    with:
      subject-path: dist/payments-1.5.0.tar.gz

Expected output: An attestation is stored against the archive digest in the repository attestation store.

2. Verify the downloaded archive against the repository and the release branch.

gh attestation verify dist/payments-1.5.0.tar.gz --repo acme/payments --source-ref refs/heads/main

Expected output: Verification prints the signer workflow, commit, and source ref, and exits with code 0.

3. Bind the deploy to the verified digest instead of the file name.

sha256sum dist/payments-1.5.0.tar.gz

Expected output: The printed digest matches the attestation subject and is recorded in the deployment manifest.

4. Test that a modified archive is rejected.

printf 'x' >> dist/payments-1.5.0.tar.gz && gh attestation verify dist/payments-1.5.0.tar.gz --repo acme/payments

Expected output: Verification exits non-zero with no matching attestation, proving the gate blocks a modified archive.

Troubleshooting

Problem

Verification reports no attestations found

Solution

Attestations are bound to the repository and the exact digest; verify the bytes that were built against the repository that built them.

Problem

Verification fails on the source ref

Solution

Pass the branch that produced the artifact, or omit --source-ref only when any branch is acceptable by policy.

Limitations

  • Attestations prove origin and integrity, not that the artifact is free of vulnerabilities.
  • Private repositories require an authenticated gh session or token to read attestations.

Release files need the same proof as images

Container images usually get signing and provenance because the registry supports it; release tarballs are downloaded from a bucket and trusted by file name. That trust is misplaced. Anyone who can write to the bucket can replace the archive, and a deploy script that downloads payments-1.5.0.tar.gz will happily unpack whatever it finds. Attestations bind a digest to the workflow identity and the commit that produced it, so the deploy job can check both before unpacking.

Attest in the build job

Attestation must happen in the job that builds the artifact, with id-token: write so GitHub can issue the OIDC token that signs the statement. Pass subject-path rather than a digest you computed by hand; the action hashes the file and records that hash. Save the archive as a workflow artifact so the deploy job consumes the same bytes, then verify the download before use.

- uses: actions/attest-build-provenance@v2
  with:
    subject-path: dist/payments-1.5.0.tar.gz
- run: gh attestation verify dist/payments-1.5.0.tar.gz --repo acme/payments --source-ref refs/heads/main

Verify, then publish the digest

gh attestation verify checks the signature, the repository, the workflow, and the source ref. Make the deploy fail closed: no attestation, a different repository, or a different branch should all stop the release. Record the artifact digest alongside the deployment, because the digest is the only identifier that cannot be reused for a different file. Run the tamper test once in a staging pipeline; a gate that has never rejected anything is not known to work.