CI/CD Pipeline (AWS Self-Hosted Runners) #2
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
| name: CI/CD Pipeline | |
| permissions: | |
| contents: write # gh-release | |
| packages: write # GHCR push | |
| id-token: write # Keyless-Signatures & Attestations | |
| attestations: write # actions/attest-build-provenance | |
| security-events: write # upload-sarif | |
| actions: read | |
| on: | |
| push: | |
| tags: | |
| - "[0-9]+.[0-9]+.[0-9]+" | |
| - "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "SemVer version to release (e.g., 1.2.3, no leading 'v')" | |
| required: true | |
| type: string | |
| publish_latest: | |
| description: "Also publish the 'latest' image tag" | |
| required: true | |
| type: boolean | |
| default: false | |
| publish_minor: | |
| description: "Also publish the 'major.minor' image tag (e.g., 1.2)" | |
| required: true | |
| type: boolean | |
| default: false | |
| target_branch: | |
| description: "Branch to tag" | |
| required: false | |
| default: "main" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }} | |
| cancel-in-progress: true | |
| jobs: | |
| prepare: | |
| if: github.event_name == 'workflow_dispatch' | |
| name: Prepare release (create tag) | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate version input | |
| shell: bash | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if ! [[ "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then | |
| echo "Invalid version: $INPUT_VERSION (expected X.Y.Z or X.Y.Z-rc.N)" >&2 | |
| exit 1 | |
| fi | |
| - name: Create and push tag | |
| shell: bash | |
| env: | |
| TARGET_BRANCH: ${{ inputs.target_branch }} | |
| VERSION: ${{ inputs.version }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git fetch --prune origin | |
| git checkout "$TARGET_BRANCH" | |
| git pull --ff-only origin "$TARGET_BRANCH" | |
| if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then | |
| echo "Tag $VERSION already exists" >&2 | |
| exit 1 | |
| fi | |
| git tag -a "$VERSION" -m "Release $VERSION" | |
| git push origin "refs/tags/$VERSION" | |
| release: | |
| if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.actor != 'github-actions[bot]') }} | |
| name: Build and Release | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 120 | |
| env: | |
| DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }} | |
| GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Capture created timestamp | |
| run: echo "IMAGE_CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 | |
| with: | |
| registry: docker.io | |
| username: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} | |
| - name: Log in to GHCR | |
| uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Normalize image names to lowercase | |
| run: | | |
| set -euo pipefail | |
| echo "GHCR_IMAGE=${GHCR_IMAGE,,}" >> "$GITHUB_ENV" | |
| echo "DOCKERHUB_IMAGE=${DOCKERHUB_IMAGE,,}" >> "$GITHUB_ENV" | |
| shell: bash | |
| - name: Extract tag name | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| echo "TAG=${INPUT_VERSION}" >> $GITHUB_ENV | |
| else | |
| echo "TAG=${{ github.ref_name }}" >> $GITHUB_ENV | |
| fi | |
| shell: bash | |
| - name: Validate pushed tag format (no leading 'v') | |
| if: ${{ github.event_name == 'push' }} | |
| shell: bash | |
| env: | |
| TAG_GOT: ${{ env.TAG }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "$TAG_GOT" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then | |
| echo "Tag OK: $TAG_GOT" | |
| exit 0 | |
| fi | |
| echo "ERROR: Tag '$TAG_GOT' is not allowed. Use 'X.Y.Z' or 'X.Y.Z-rc.N' (no leading 'v')." >&2 | |
| exit 1 | |
| - name: Wait for tag to be visible (dispatch only) | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| set -euo pipefail | |
| for i in {1..90}; do | |
| if git ls-remote --tags origin "refs/tags/${TAG}" | grep -qE "refs/tags/${TAG}$"; then | |
| echo "Tag ${TAG} is visible on origin"; exit 0 | |
| fi | |
| echo "Tag not yet visible, retrying... ($i/90)" | |
| sleep 2 | |
| done | |
| echo "Tag ${TAG} not visible after waiting"; exit 1 | |
| shell: bash | |
| - name: Update version in main.go | |
| run: | | |
| TAG=${{ env.TAG }} | |
| if [ -f main.go ]; then | |
| sed -i 's/version_replaceme/'"$TAG"'/' main.go | |
| echo "Updated main.go with version $TAG" | |
| else | |
| echo "main.go not found" | |
| fi | |
| - name: Ensure repository is at the tagged commit (dispatch only) | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --tags --force | |
| git checkout "refs/tags/${TAG}" | |
| echo "Checked out $(git rev-parse --short HEAD) for tag ${TAG}" | |
| shell: bash | |
| - name: Detect release candidate (rc) | |
| run: | | |
| set -euo pipefail | |
| if [[ "${TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then | |
| echo "IS_RC=true" >> $GITHUB_ENV | |
| else | |
| echo "IS_RC=false" >> $GITHUB_ENV | |
| fi | |
| shell: bash | |
| - name: Install Go | |
| uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 | |
| with: | |
| go-version-file: go.mod | |
| - name: Resolve publish-latest flag | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PL_INPUT: ${{ inputs.publish_latest }} | |
| PL_VAR: ${{ vars.PUBLISH_LATEST }} | |
| run: | | |
| set -euo pipefail | |
| val="false" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| if [ "${PL_INPUT}" = "true" ]; then val="true"; fi | |
| else | |
| if [ "${PL_VAR}" = "true" ]; then val="true"; fi | |
| fi | |
| echo "PUBLISH_LATEST=$val" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Resolve publish-minor flag | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PM_INPUT: ${{ inputs.publish_minor }} | |
| PM_VAR: ${{ vars.PUBLISH_MINOR }} | |
| run: | | |
| set -euo pipefail | |
| val="false" | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| if [ "${PM_INPUT}" = "true" ]; then val="true"; fi | |
| else | |
| if [ "${PM_VAR}" = "true" ]; then val="true"; fi | |
| fi | |
| echo "PUBLISH_MINOR=$val" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Cache Go modules | |
| if: ${{ hashFiles('**/go.sum') != '' }} | |
| uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Go vet & test | |
| if: ${{ hashFiles('**/go.mod') != '' }} | |
| run: | | |
| go version | |
| go vet ./... | |
| go test ./... -race -covermode=atomic | |
| shell: bash | |
| - name: Resolve license fallback | |
| run: echo "IMAGE_LICENSE=${{ github.event.repository.license.spdx_id || 'NOASSERTION' }}" >> $GITHUB_ENV | |
| shell: bash | |
| - name: Resolve registries list (GHCR always, Docker Hub only if creds) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| images="${GHCR_IMAGE}" | |
| if [ -n "${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}" ] && [ -n "${{ secrets.DOCKER_HUB_USERNAME }}" ]; then | |
| images="${images}\n${DOCKERHUB_IMAGE}" | |
| fi | |
| { | |
| echo 'IMAGE_LIST<<EOF' | |
| echo -e "$images" | |
| echo 'EOF' | |
| } >> "$GITHUB_ENV" | |
| - name: Docker meta | |
| id: meta | |
| uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 | |
| with: | |
| images: ${{ env.IMAGE_LIST }} | |
| tags: | | |
| type=semver,pattern={{version}},value=${{ env.TAG }} | |
| type=semver,pattern={{major}}.{{minor}},value=${{ env.TAG }},enable=${{ env.PUBLISH_MINOR == 'true' && env.IS_RC != 'true' }} | |
| type=raw,value=latest,enable=${{ env.IS_RC != 'true' }} | |
| flavor: | | |
| latest=false | |
| labels: | | |
| org.opencontainers.image.title=${{ github.event.repository.name }} | |
| org.opencontainers.image.version=${{ env.TAG }} | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.source=${{ github.event.repository.html_url }} | |
| org.opencontainers.image.url=${{ github.event.repository.html_url }} | |
| org.opencontainers.image.documentation=${{ github.event.repository.html_url }} | |
| org.opencontainers.image.description=${{ github.event.repository.description }} | |
| org.opencontainers.image.licenses=${{ env.IMAGE_LICENSE }} | |
| org.opencontainers.image.created=${{ env.IMAGE_CREATED }} | |
| org.opencontainers.image.ref.name=${{ env.TAG }} | |
| org.opencontainers.image.authors=${{ github.repository_owner }} | |
| - name: Echo build config (non-secret) | |
| shell: bash | |
| env: | |
| IMAGE_TITLE: ${{ github.event.repository.name }} | |
| IMAGE_VERSION: ${{ env.TAG }} | |
| IMAGE_REVISION: ${{ github.sha }} | |
| IMAGE_SOURCE_URL: ${{ github.event.repository.html_url }} | |
| IMAGE_URL: ${{ github.event.repository.html_url }} | |
| IMAGE_DESCRIPTION: ${{ github.event.repository.description }} | |
| IMAGE_LICENSE: ${{ env.IMAGE_LICENSE }} | |
| DOCKERHUB_IMAGE: ${{ env.DOCKERHUB_IMAGE }} | |
| GHCR_IMAGE: ${{ env.GHCR_IMAGE }} | |
| DOCKER_HUB_USER: ${{ secrets.DOCKER_HUB_USERNAME }} | |
| REPO: ${{ github.repository }} | |
| OWNER: ${{ github.repository_owner }} | |
| WORKFLOW_REF: ${{ github.workflow_ref }} | |
| REF: ${{ github.ref }} | |
| REF_NAME: ${{ github.ref_name }} | |
| RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| echo "=== OCI Label Values ===" | |
| echo "org.opencontainers.image.title=${IMAGE_TITLE}" | |
| echo "org.opencontainers.image.version=${IMAGE_VERSION}" | |
| echo "org.opencontainers.image.revision=${IMAGE_REVISION}" | |
| echo "org.opencontainers.image.source=${IMAGE_SOURCE_URL}" | |
| echo "org.opencontainers.image.url=${IMAGE_URL}" | |
| echo "org.opencontainers.image.description=${IMAGE_DESCRIPTION}" | |
| echo "org.opencontainers.image.licenses=${IMAGE_LICENSE}" | |
| echo | |
| echo "=== Images ===" | |
| echo "DOCKERHUB_IMAGE=${DOCKERHUB_IMAGE}" | |
| echo "GHCR_IMAGE=${GHCR_IMAGE}" | |
| echo "DOCKER_HUB_USERNAME=${DOCKER_HUB_USER}" | |
| echo | |
| echo "=== GitHub Kontext ===" | |
| echo "repository=${REPO}" | |
| echo "owner=${OWNER}" | |
| echo "workflow_ref=${WORKFLOW_REF}" | |
| echo "ref=${REF}" | |
| echo "ref_name=${REF_NAME}" | |
| echo "run_url=${RUN_URL}" | |
| echo | |
| echo "=== docker/metadata-action outputs (Tags/Labels), raw ===" | |
| echo "::group::tags" | |
| echo "${{ steps.meta.outputs.tags }}" | |
| echo "::endgroup::" | |
| echo "::group::labels" | |
| echo "${{ steps.meta.outputs.labels }}" | |
| echo "::endgroup::" | |
| - name: Build and push (Docker Hub + GHCR) | |
| id: build | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64,linux/arm64,linux/arm/v7 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha,scope=${{ github.repository }} | |
| cache-to: type=gha,mode=max,scope=${{ github.repository }} | |
| provenance: mode=max | |
| sbom: true | |
| - name: Compute image digest refs | |
| run: | | |
| echo "DIGEST=${{ steps.build.outputs.digest }}" >> $GITHUB_ENV | |
| echo "GHCR_REF=$GHCR_IMAGE@${{ steps.build.outputs.digest }}" >> $GITHUB_ENV | |
| echo "DH_REF=$DOCKERHUB_IMAGE@${{ steps.build.outputs.digest }}" >> $GITHUB_ENV | |
| echo "Built digest: ${{ steps.build.outputs.digest }}" | |
| shell: bash | |
| - name: Attest build provenance (GHCR) | |
| id: attest-ghcr | |
| uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 | |
| with: | |
| subject-name: ${{ env.GHCR_IMAGE }} | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| push-to-registry: true | |
| show-summary: true | |
| - name: Attest build provenance (Docker Hub) | |
| continue-on-error: true | |
| id: attest-dh | |
| uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 | |
| with: | |
| subject-name: index.docker.io/fosrl/${{ github.event.repository.name }} | |
| subject-digest: ${{ steps.build.outputs.digest }} | |
| push-to-registry: true | |
| show-summary: true | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 | |
| with: | |
| cosign-release: 'v3.0.2' | |
| - name: Sanity check cosign private key | |
| env: | |
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | |
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| cosign public-key --key env://COSIGN_PRIVATE_KEY >/dev/null | |
| shell: bash | |
| - name: Sign GHCR image (digest) with key (recursive) | |
| env: | |
| COSIGN_YES: "true" | |
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | |
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| echo "Signing ${GHCR_REF} (digest) recursively with provided key" | |
| cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${GHCR_REF}" | |
| echo "Waiting 30 seconds for signatures to propagate..." | |
| sleep 30 | |
| shell: bash | |
| - name: Generate SBOM (SPDX JSON) | |
| uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1 | |
| with: | |
| image-ref: ${{ env.GHCR_IMAGE }}@${{ steps.build.outputs.digest }} | |
| format: spdx-json | |
| output: sbom.spdx.json | |
| - name: Validate SBOM JSON | |
| run: jq -e . sbom.spdx.json >/dev/null | |
| shell: bash | |
| - name: Minify SBOM JSON (optional hardening) | |
| run: jq -c . sbom.spdx.json > sbom.min.json && mv sbom.min.json sbom.spdx.json | |
| shell: bash | |
| - name: Create SBOM attestation (GHCR, private key) | |
| env: | |
| COSIGN_YES: "true" | |
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | |
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | |
| run: | | |
| set -euo pipefail | |
| cosign attest \ | |
| --key env://COSIGN_PRIVATE_KEY \ | |
| --type spdxjson \ | |
| --predicate sbom.spdx.json \ | |
| "${GHCR_REF}" | |
| shell: bash | |
| - name: Create SBOM attestation (Docker Hub, private key) | |
| continue-on-error: true | |
| env: | |
| COSIGN_YES: "true" | |
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | |
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | |
| COSIGN_DOCKER_MEDIA_TYPES: "1" | |
| run: | | |
| set -euo pipefail | |
| cosign attest \ | |
| --key env://COSIGN_PRIVATE_KEY \ | |
| --type spdxjson \ | |
| --predicate sbom.spdx.json \ | |
| "${DH_REF}" | |
| shell: bash | |
| - name: Keyless sign & verify GHCR digest (OIDC) | |
| env: | |
| COSIGN_YES: "true" | |
| WORKFLOW_REF: ${{ github.workflow_ref }} # owner/repo/.github/workflows/<file>@refs/tags/<tag> | |
| ISSUER: https://token.actions.githubusercontent.com | |
| run: | | |
| set -euo pipefail | |
| echo "Keyless signing ${GHCR_REF}" | |
| cosign sign --rekor-url https://rekor.sigstore.dev --recursive "${GHCR_REF}" | |
| echo "Verify keyless (OIDC) signature policy on ${GHCR_REF}" | |
| cosign verify \ | |
| --certificate-oidc-issuer "${ISSUER}" \ | |
| --certificate-identity "https://github.com/${WORKFLOW_REF}" \ | |
| "${GHCR_REF}" -o text | |
| shell: bash | |
| - name: Sign Docker Hub image (digest) with key (recursive) | |
| continue-on-error: true | |
| env: | |
| COSIGN_YES: "true" | |
| COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} | |
| COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} | |
| COSIGN_DOCKER_MEDIA_TYPES: "1" | |
| run: | | |
| set -euo pipefail | |
| echo "Signing ${DH_REF} (digest) recursively with provided key (Docker media types fallback)" | |
| cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${DH_REF}" | |
| shell: bash | |
| - name: Keyless sign & verify Docker Hub digest (OIDC) | |
| continue-on-error: true | |
| env: | |
| COSIGN_YES: "true" | |
| ISSUER: https://token.actions.githubusercontent.com | |
| COSIGN_DOCKER_MEDIA_TYPES: "1" | |
| run: | | |
| set -euo pipefail | |
| echo "Keyless signing ${DH_REF} (force public-good Rekor)" | |
| cosign sign --rekor-url https://rekor.sigstore.dev --recursive "${DH_REF}" | |
| echo "Keyless verify via Rekor (strict identity)" | |
| if ! cosign verify \ | |
| --rekor-url https://rekor.sigstore.dev \ | |
| --certificate-oidc-issuer "${ISSUER}" \ | |
| --certificate-identity "https://github.com/${{ github.workflow_ref }}" \ | |
| "${DH_REF}" -o text; then | |
| echo "Rekor verify failed — retry offline bundle verify (no Rekor)" | |
| if ! cosign verify \ | |
| --offline \ | |
| --certificate-oidc-issuer "${ISSUER}" \ | |
| --certificate-identity "https://github.com/${{ github.workflow_ref }}" \ | |
| "${DH_REF}" -o text; then | |
| echo "Offline bundle verify failed — ignore tlog (TEMP for debugging)" | |
| cosign verify \ | |
| --insecure-ignore-tlog=true \ | |
| --certificate-oidc-issuer "${ISSUER}" \ | |
| --certificate-identity "https://github.com/${{ github.workflow_ref }}" \ | |
| "${DH_REF}" -o text || true | |
| fi | |
| fi | |
| - name: Verify signature (public key) GHCR digest + tag | |
| env: | |
| COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} | |
| run: | | |
| set -euo pipefail | |
| TAG_VAR="${TAG}" | |
| echo "Verifying (digest) ${GHCR_REF}" | |
| cosign verify --key env://COSIGN_PUBLIC_KEY "$GHCR_REF" -o text | |
| echo "Verifying (tag) $GHCR_IMAGE:$TAG_VAR" | |
| cosign verify --key env://COSIGN_PUBLIC_KEY "$GHCR_IMAGE:$TAG_VAR" -o text | |
| shell: bash | |
| - name: Verify SBOM attestation (GHCR) | |
| env: | |
| COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} | |
| run: cosign verify-attestation --key env://COSIGN_PUBLIC_KEY --type spdxjson "$GHCR_REF" -o text | |
| shell: bash | |
| - name: Verify SLSA provenance (GHCR) | |
| env: | |
| ISSUER: https://token.actions.githubusercontent.com | |
| WFREF: ${{ github.workflow_ref }} | |
| run: | | |
| set -euo pipefail | |
| # (optional) show which predicate types are present to aid debugging | |
| cosign download attestation "$GHCR_REF" \ | |
| | jq -r '.payload | @base64d | fromjson | .predicateType' | sort -u || true | |
| # Verify the SLSA v1 provenance attestation (predicate URL) | |
| cosign verify-attestation \ | |
| --type 'https://slsa.dev/provenance/v1' \ | |
| --certificate-oidc-issuer "$ISSUER" \ | |
| --certificate-identity "https://github.com/${WFREF}" \ | |
| --rekor-url https://rekor.sigstore.dev \ | |
| "$GHCR_REF" -o text | |
| shell: bash | |
| - name: Verify signature (public key) Docker Hub digest | |
| continue-on-error: true | |
| env: | |
| COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} | |
| COSIGN_DOCKER_MEDIA_TYPES: "1" | |
| run: | | |
| set -euo pipefail | |
| echo "Verifying (digest) ${DH_REF} with Docker media types" | |
| cosign verify --key env://COSIGN_PUBLIC_KEY "${DH_REF}" -o text | |
| shell: bash | |
| - name: Verify signature (public key) Docker Hub tag | |
| continue-on-error: true | |
| env: | |
| COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }} | |
| COSIGN_DOCKER_MEDIA_TYPES: "1" | |
| run: | | |
| set -euo pipefail | |
| echo "Verifying (tag) $DOCKERHUB_IMAGE:$TAG with Docker media types" | |
| cosign verify --key env://COSIGN_PUBLIC_KEY "$DOCKERHUB_IMAGE:$TAG" -o text | |
| shell: bash | |
| # - name: Trivy scan (GHCR image) | |
| # id: trivy | |
| # uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1 | |
| # with: | |
| # image-ref: ${{ env.GHCR_IMAGE }}@${{ steps.build.outputs.digest }} | |
| # format: sarif | |
| # output: trivy-ghcr.sarif | |
| # ignore-unfixed: true | |
| # vuln-type: os,library | |
| # severity: CRITICAL,HIGH | |
| # exit-code: ${{ (vars.TRIVY_FAIL || '0') }} | |
| # - name: Upload SARIF,trivy | |
| # if: ${{ always() && hashFiles('trivy-ghcr.sarif') != '' }} | |
| # uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 | |
| # with: | |
| # sarif_file: trivy-ghcr.sarif | |
| # category: Image Vulnerability Scan | |
| - name: Build binaries | |
| env: | |
| CGO_ENABLED: "0" | |
| GOFLAGS: "-trimpath" | |
| run: | | |
| set -euo pipefail | |
| TAG_VAR="${TAG}" | |
| make -j 10 go-build-release tag=$TAG_VAR | |
| shell: bash | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 | |
| with: | |
| tag_name: ${{ env.TAG }} | |
| generate_release_notes: true | |
| prerelease: ${{ env.IS_RC == 'true' }} | |
| files: | | |
| bin/* | |
| fail_on_unmatched_files: true | |
| draft: true | |
| body: | | |
| ## Container Images | |
| - GHCR: `${{ env.GHCR_REF }}` | |
| - Docker Hub: `${{ env.DH_REF || 'N/A' }}` | |
| **Digest:** `${{ steps.build.outputs.digest }}` |