Skip to content

feat(pipeline): manifest v4 carries the verdict the two booleans coul… #20

feat(pipeline): manifest v4 carries the verdict the two booleans coul…

feat(pipeline): manifest v4 carries the verdict the two booleans coul… #20

Workflow file for this run

# Build, sign, attest, publish, then read it all back. Triggered by a v*.*.* tag.
name: release
on:
push:
tags: ['v*.*.*']
permissions: {}
jobs:
goreleaser:
name: goreleaser
runs-on: ubuntu-latest
permissions:
contents: write # create the GitHub Release
packages: write # push the image to GHCR
id-token: write # cosign keyless signing (Sigstore OIDC)
attestations: write # store the build provenance
artifact-metadata: write # required by push-to-registry, else it warns and skips
steps:
# Allowlist built from audited runs. A new dependency that phones somewhere new fails
# here, loudly, which is the point. Add the endpoint only after checking what it is.
- name: Harden the runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: block
allowed-endpoints: >
*.actions.githubusercontent.com:443
*.blob.core.windows.net:443
api.github.com:443
github.com:443
objects.githubusercontent.com:443
raw.githubusercontent.com:443
release-assets.githubusercontent.com:443
apk.cgr.dev:443
auth.docker.io:443
cgr.dev:443
fulcio.sigstore.dev:443
get.anchore.io:443
ghcr.io:443
goreleaser.com:443
pkg-containers.githubusercontent.com:443
production.cloudfront.docker.com:443
proxy.golang.org:443
registry-1.docker.io:443
rekor.sigstore.dev:443
toolbox-data.anchore.io:443
tuf-repo-cdn.sigstore.dev:443
tuf-repo.github.com:443
uploads.github.com:443
*.r2.cloudflarestorage.com:443
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # full history for the changelog
persist-credentials: false
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.26.6'
check-latest: false
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
# >= 2.6 emits Sigstore bundles, which GoReleaser's sign-blob config cannot consume.
cosign-release: 'v2.5.3'
- name: Install syft (SBOMs)
uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Without this, `git describe` picks a candidate tag sharing the commit and builds
# the wrong version under the right tag.
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
# Verify with: gh attestation verify <tarball> --repo gitdr-io/gitdr
- name: Attest build provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-checksums: dist/checksums.txt
# checksums.txt covers the tarballs only; the image needs its own subject.
- name: Read the published image digest
id: image
run: |
digest=$(jq -r '[.[] | select(.type == "Docker Manifest")][0].extra.Digest' dist/artifacts.json)
case "$digest" in
sha256:*) ;;
*) echo "no image digest in dist/artifacts.json, refusing to attest nothing" >&2; exit 1 ;;
esac
echo "digest=$digest" >> "$GITHUB_OUTPUT"
- name: Attest image provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-name: ghcr.io/gitdr-io/gitdr
subject-digest: ${{ steps.image.outputs.digest }}
push-to-registry: true
# Install: helm install oci://ghcr.io/gitdr-io/charts/gitdr
chart:
name: helm chart
needs: [goreleaser]
runs-on: ubuntu-latest
permissions:
contents: read # checkout
packages: write # push the chart to GHCR
id-token: write # cosign keyless
attestations: write # store the chart's build provenance
artifact-metadata: write
steps:
- name: Harden the runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: block
allowed-endpoints: >
*.actions.githubusercontent.com:443
*.blob.core.windows.net:443
api.github.com:443
github.com:443
objects.githubusercontent.com:443
raw.githubusercontent.com:443
release-assets.githubusercontent.com:443
fulcio.sigstore.dev:443
get.helm.sh:443
ghcr.io:443
pkg-containers.githubusercontent.com:443
rekor.sigstore.dev:443
tuf-repo-cdn.sigstore.dev:443
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
# >= 2.6 emits Sigstore bundles, which GoReleaser's sign-blob config cannot consume.
cosign-release: 'v2.5.3'
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Helm registry login
env:
GHCR_USER: ${{ github.repository_owner }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: printf '%s' "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USER" --password-stdin
- name: Package and push chart
id: push
env:
REF_NAME: ${{ github.ref_name }}
run: |
v="${REF_NAME#v}"
# Chart.yaml carries 0.0.0 placeholders; the released version comes from the tag.
# Fail rather than ship a chart that claims a version nobody can pull.
sed -i "s|image: ghcr.io/gitdr-io/gitdr:.*|image: ghcr.io/gitdr-io/gitdr:$v|" charts/gitdr/Chart.yaml
helm package charts/gitdr --version "$v" --app-version "$v"
grep -q "image: ghcr.io/gitdr-io/gitdr:$v" charts/gitdr/Chart.yaml || {
echo "the artifacthub image annotation did not take the release version" >&2; exit 1; }
out=$(helm push "gitdr-${v}.tgz" oci://ghcr.io/gitdr-io/charts 2>&1) || { echo "$out"; exit 1; }
echo "$out"
echo "digest=$(printf '%s' "$out" | awk '/[Dd]igest:/{print $NF}')" >> "$GITHUB_OUTPUT"
- name: Sign chart
env:
CHART_REF: ghcr.io/gitdr-io/charts/gitdr@${{ steps.push.outputs.digest }}
run: cosign sign --yes "$CHART_REF"
- name: Attest chart provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-name: ghcr.io/gitdr-io/charts/gitdr
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
# Read the release back. Everything above signs and attests; this is what checks it
# worked. v0.1.5 shipped a signed image with no provenance because nothing did.
verify:
name: verify the release
needs: [goreleaser, chart]
runs-on: ubuntu-latest
permissions:
contents: read # download the release assets
packages: read # read the image and chart from GHCR
attestations: read # read the provenance store
env:
# The only identity allowed to have signed any of this.
IDENTITY: https://github.com/gitdr-io/gitdr/.github/workflows/release.yml@refs/tags/${{ github.ref_name }}
ISSUER: https://token.actions.githubusercontent.com
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Harden the runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: block
allowed-endpoints: >
*.actions.githubusercontent.com:443
*.blob.core.windows.net:443
api.github.com:443
github.com:443
objects.githubusercontent.com:443
raw.githubusercontent.com:443
release-assets.githubusercontent.com:443
fulcio.sigstore.dev:443
ghcr.io:443
pkg-containers.githubusercontent.com:443
rekor.sigstore.dev:443
search.sigstore.dev:443
tuf-repo-cdn.sigstore.dev:443
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
# A missing signature fails verification below. An artifact that silently stops being
# produced leaves nothing to fail, so the set is asserted by name. tflint shipped 0.22.0
# without checksums.txt.sig and broke every install script that verified first.
- name: Every expected artifact is present
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
gh release download "$TAG" --repo "$GITHUB_REPOSITORY" --dir dl
v="${TAG#v}"
missing=0
for f in \
checksums.txt checksums.txt.pem checksums.txt.sig \
"gitdr_${v}_linux_amd64.tar.gz" "gitdr_${v}_linux_amd64.tar.gz.sbom.json" \
"gitdr_${v}_linux_arm64.tar.gz" "gitdr_${v}_linux_arm64.tar.gz.sbom.json"
do
if [ ! -s "dl/$f" ]; then echo "missing from the release: $f" >&2; missing=1; fi
done
[ "$missing" = "0" ] || { echo "the published asset set changed; downstream verification would break" >&2; exit 1; }
- name: Binaries — signature, checksums, provenance
env:
TAG: ${{ github.ref_name }}
run: |
set -euo pipefail
cd dl
cosign verify-blob checksums.txt \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
--certificate-identity "$IDENTITY" \
--certificate-oidc-issuer "$ISSUER"
sha256sum -c checksums.txt
for f in *.tar.gz; do
gh attestation verify "$f" --repo "$GITHUB_REPOSITORY" \
--signer-workflow gitdr-io/gitdr/.github/workflows/release.yml
done
# slsaprovenance1, not slsaprovenance: the bare name means the v0.2 predicate and
# rejects the v1 one the attest action emits.
- name: Image — signature and provenance
env:
VERSION: ${{ github.ref_name }}
run: |
set -euo pipefail
ref="ghcr.io/gitdr-io/gitdr:${VERSION#v}"
cosign verify "$ref" \
--certificate-identity "$IDENTITY" --certificate-oidc-issuer "$ISSUER" > /dev/null
cosign verify-attestation "$ref" --type slsaprovenance1 \
--certificate-identity "$IDENTITY" --certificate-oidc-issuer "$ISSUER" > /dev/null
- name: Chart — signature and provenance
env:
VERSION: ${{ github.ref_name }}
run: |
set -euo pipefail
ref="ghcr.io/gitdr-io/charts/gitdr:${VERSION#v}"
cosign verify "$ref" \
--certificate-identity "$IDENTITY" --certificate-oidc-issuer "$ISSUER" > /dev/null
cosign verify-attestation "$ref" --type slsaprovenance1 \
--certificate-identity "$IDENTITY" --certificate-oidc-issuer "$ISSUER" > /dev/null