1- # Release, triggered by a v*.*.* tag. GoReleaser builds the static linux binaries +
2- # multi-arch GHCR image, cosign keyless-signs the checksums and the image (GitHub OIDC, no
3- # key material), publishes a GitHub Release, and attaches build provenance to the binaries.
4- # Uses the built-in GITHUB_TOKEN for GHCR and Releases, no personal access token.
1+ # Build, sign, attest, publish, then read it all back. Triggered by a v*.*.* tag.
52name : release
63
74on :
@@ -19,20 +16,39 @@ jobs:
1916 packages : write # push the image to GHCR
2017 id-token : write # cosign keyless signing (Sigstore OIDC)
2118 attestations : write # store the build provenance
22- # Lets the attest action also record where the artifact is hosted and whether it is
23- # still active — the "linked artifacts" view. Separate from the attestation itself,
24- # which works without it; push-to-registry without this permission is what produced
25- # the storage-record warnings on v0.1.6.
26- artifact-metadata : write
19+ artifact-metadata : write # required by push-to-registry, else it warns and skips
2720 steps :
28- # Audit, not block, and deliberately: a release reaches Sigstore, GHCR, the Go proxy,
29- # the Docker and Chainguard registries, and three tool downloads. One endpoint missing
30- # from an allowlist here means a failed release, not a caught intrusion. The read-only
31- # workflows are blocked; this one is watched, and its egress is reviewed per release.
21+ # Allowlist built from audited runs. A new dependency that phones somewhere new fails
22+ # here, loudly, which is the point. Add the endpoint only after checking what it is.
3223 - name : Harden the runner
3324 uses : step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
3425 with :
35- egress-policy : audit
26+ egress-policy : block
27+ allowed-endpoints : >
28+ *.actions.githubusercontent.com:443
29+ *.blob.core.windows.net:443
30+ api.github.com:443
31+ github.com:443
32+ objects.githubusercontent.com:443
33+ raw.githubusercontent.com:443
34+ release-assets.githubusercontent.com:443
35+ apk.cgr.dev:443
36+ auth.docker.io:443
37+ cgr.dev:443
38+ fulcio.sigstore.dev:443
39+ get.anchore.io:443
40+ ghcr.io:443
41+ goreleaser.com:443
42+ pkg-containers.githubusercontent.com:443
43+ production.cloudfront.docker.com:443
44+ proxy.golang.org:443
45+ registry-1.docker.io:443
46+ rekor.sigstore.dev:443
47+ toolbox-data.anchore.io:443
48+ tuf-repo-cdn.sigstore.dev:443
49+ tuf-repo.github.com:443
50+ uploads.github.com:443
51+ *.r2.cloudflarestorage.com:443
3652 - name : Checkout
3753 uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
3854 with :
5066 - name : Install cosign
5167 uses : sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
5268 with :
53- # Pin: cosign >= 2.6 defaults to the new Sigstore bundle format, which breaks
54- # GoReleaser's --output-signature/--output-certificate sign-blob config. v2.5.3
55- # keeps the classic .sig/.pem outputs that any cosign version can verify.
69+ # >= 2.6 emits Sigstore bundles, which GoReleaser's sign-blob config cannot consume.
5670 cosign-release : ' v2.5.3'
5771 - name : Install syft (SBOMs)
5872 uses : anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
@@ -69,27 +83,16 @@ jobs:
6983 args : release --clean
7084 env :
7185 GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
72- # Build the tag that triggered this run, not whatever `git describe` picks.
73- #
74- # When two tags sit on the same commit — a release candidate and the release that
75- # promotes it unchanged — describe chooses one of them, and it chose the candidate:
76- # the run built 0.1.6-rc.2 under the v0.1.6 tag and failed uploading assets that
77- # already existed. Silent in the worst case, since a green run would have published
78- # the wrong version.
86+ # Without this, `git describe` picks a candidate tag sharing the commit and builds
87+ # the wrong version under the right tag.
7988 GORELEASER_CURRENT_TAG : ${{ github.ref_name }}
80- # Build provenance for every artifact in checksums.txt: what built it, from which
81- # commit, in which workflow. Signed through Sigstore and written to the public
82- # transparency log. Verify with:
83- # gh attestation verify gitdr_0.1.0_linux_amd64.tar.gz --repo gitdr-io/gitdr
89+ # Verify with: gh attestation verify <tarball> --repo gitdr-io/gitdr
8490 - name : Attest build provenance
8591 uses : actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
8692 with :
8793 subject-checksums : dist/checksums.txt
8894
89- # The image needs its own subject. checksums.txt covers the tarballs only, so until
90- # now someone pulling the container could verify who signed it but not where it was
91- # built — a signature says "we made this", provenance says "here is the commit and
92- # the workflow run that made it". Both, or the claim is half a claim.
95+ # checksums.txt covers the tarballs only; the image needs its own subject.
9396 - name : Read the published image digest
9497 id : image
9598 run : |
@@ -106,8 +109,7 @@ jobs:
106109 subject-digest : ${{ steps.image.outputs.digest }}
107110 push-to-registry : true
108111
109- # Package the Helm chart and push it to GHCR as an OCI artifact, then cosign-sign it by
110- # digest (keyless, GitHub OIDC). Install: helm install oci://ghcr.io/gitdr-io/charts/gitdr
112+ # Install: helm install oci://ghcr.io/gitdr-io/charts/gitdr
111113 chart :
112114 name : helm chart
113115 needs : [goreleaser]
@@ -117,12 +119,26 @@ jobs:
117119 packages : write # push the chart to GHCR
118120 id-token : write # cosign keyless
119121 attestations : write # store the chart's build provenance
120- artifact-metadata : write # record where the chart is hosted, see the goreleaser job
122+ artifact-metadata : write
121123 steps :
122124 - name : Harden the runner
123125 uses : step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
124126 with :
125- egress-policy : audit
127+ egress-policy : block
128+ allowed-endpoints : >
129+ *.actions.githubusercontent.com:443
130+ *.blob.core.windows.net:443
131+ api.github.com:443
132+ github.com:443
133+ objects.githubusercontent.com:443
134+ raw.githubusercontent.com:443
135+ release-assets.githubusercontent.com:443
136+ fulcio.sigstore.dev:443
137+ get.helm.sh:443
138+ ghcr.io:443
139+ pkg-containers.githubusercontent.com:443
140+ rekor.sigstore.dev:443
141+ tuf-repo-cdn.sigstore.dev:443
126142 - name : Checkout
127143 uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
128144 with :
@@ -132,9 +148,7 @@ jobs:
132148 - name : Install cosign
133149 uses : sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
134150 with :
135- # Pin: cosign >= 2.6 defaults to the new Sigstore bundle format, which breaks
136- # GoReleaser's --output-signature/--output-certificate sign-blob config. v2.5.3
137- # keeps the classic .sig/.pem outputs that any cosign version can verify.
151+ # >= 2.6 emits Sigstore bundles, which GoReleaser's sign-blob config cannot consume.
138152 cosign-release : ' v2.5.3'
139153 - name : Log in to GHCR
140154 uses : docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
@@ -168,14 +182,8 @@ jobs:
168182 subject-digest : ${{ steps.push.outputs.digest }}
169183 push-to-registry : true
170184
171- # Read the release back.
172- #
173- # Everything above signs and attests; nothing above checks that it worked. That gap is how
174- # v0.1.5 shipped with a signed image carrying no provenance and nobody noticing until
175- # someone went looking by hand. It is the same failure this product exists to prevent, one
176- # layer up: a process that reports success is not evidence, the artifact is.
177- #
178- # This job fails the release if any published artifact is unsigned or unattested.
185+ # Read the release back. Everything above signs and attests; this is what checks it
186+ # worked. v0.1.5 shipped a signed image with no provenance because nothing did.
179187 verify :
180188 name : verify the release
181189 needs : [goreleaser, chart]
@@ -185,29 +193,35 @@ jobs:
185193 packages : read # read the image and chart from GHCR
186194 attestations : read # read the provenance store
187195 env :
188- # The identity that is allowed to have signed any of this. A signature from anything
189- # else is not a pass.
196+ # The only identity allowed to have signed any of this.
190197 IDENTITY : https://github.com/gitdr-io/gitdr/.github/workflows/release.yml@refs/tags/${{ github.ref_name }}
191198 ISSUER : https://token.actions.githubusercontent.com
192199 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
193200 steps :
194201 - name : Harden the runner
195202 uses : step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1
196203 with :
197- egress-policy : audit
204+ egress-policy : block
205+ allowed-endpoints : >
206+ *.actions.githubusercontent.com:443
207+ *.blob.core.windows.net:443
208+ api.github.com:443
209+ github.com:443
210+ objects.githubusercontent.com:443
211+ raw.githubusercontent.com:443
212+ release-assets.githubusercontent.com:443
213+ fulcio.sigstore.dev:443
214+ ghcr.io:443
215+ pkg-containers.githubusercontent.com:443
216+ rekor.sigstore.dev:443
217+ search.sigstore.dev:443
218+ tuf-repo-cdn.sigstore.dev:443
198219 - name : Install cosign
199220 uses : sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
200221
201- # The asset set itself, before anything is verified.
202- #
203- # tflint shipped 0.22.0 without checksums.txt.sig and broke every install script that
204- # verified before installing; the file was simply absent and nothing noticed. A missing
205- # signature is caught below by the verification failing, but an artifact that quietly
206- # stops being produced — an SBOM, an architecture — is not: there is nothing left to
207- # fail. So the expected set is asserted by name.
208- #
209- # Adding an artifact means adding it here. That is the point: the set consumers depend
210- # on should not be able to change by accident.
222+ # A missing signature fails verification below. An artifact that silently stops being
223+ # produced leaves nothing to fail, so the set is asserted by name. tflint shipped 0.22.0
224+ # without checksums.txt.sig and broke every install script that verified first.
211225 - name : Every expected artifact is present
212226 env :
213227 TAG : ${{ github.ref_name }}
@@ -238,18 +252,15 @@ jobs:
238252 --certificate-identity "$IDENTITY" \
239253 --certificate-oidc-issuer "$ISSUER"
240254
241- # Every line of the signed list, against the bytes actually published.
242255 sha256sum -c checksums.txt
243256
244257 for f in *.tar.gz; do
245258 gh attestation verify "$f" --repo "$GITHUB_REPOSITORY" \
246259 --signer-workflow gitdr-io/gitdr/.github/workflows/release.yml
247260 done
248261
249- # slsaprovenance1, not slsaprovenance: the bare shorthand means the SLSA v0.2 predicate,
250- # while actions/attest-build-provenance emits v1. The wrong one rejects a perfectly good
251- # attestation with "none of the attestations matched the predicate type", which is a
252- # false alarm from the very job whose purpose is to be trusted when it complains.
262+ # slsaprovenance1, not slsaprovenance: the bare name means the v0.2 predicate and
263+ # rejects the v1 one the attest action emits.
253264 - name : Image — signature and provenance
254265 env :
255266 VERSION : ${{ github.ref_name }}
0 commit comments