feat(pipeline): manifest v4 carries the verdict the two booleans coul… #20
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |