Skip to content

Assayward Release Gate #4

Assayward Release Gate

Assayward Release Gate #4

name: Assayward Release Gate
# Triggered on the RELEASE WORKFLOW COMPLETING, not on `release: published`.
# GoReleaser creates the release using GITHUB_TOKEN, and GitHub deliberately
# suppresses events raised by that token so a workflow cannot trigger itself in
# a loop. The side effect is that a `release: published` trigger never fires on
# the normal release path: this gate had zero runs across every tagged release
# before this change. Keying off workflow_run makes the gate actually run.
on:
workflow_run:
workflows: ["Release"]
types: [completed]
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
assayward-gate:
runs-on: ubuntu-latest
# A failed release has nothing worth gating.
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Build
run: make build
- name: Self-attest with forgeseal pipeline
run: |
./bin/forgeseal pipeline \
--dir . \
--output-dir ./forgeseal-output \
--sign \
--attest \
--keyed=false
# The gated artifact must be the one the provenance actually attests.
# `forgeseal pipeline --attest` records the dependency SBOM as its
# subject, so hashing the binary here produced a digest that appears in
# no attestation: assayward answered SUBJECT_DIGEST_MISMATCH and every
# downstream check then reported its evidence missing. Hashing the
# attested subject binds the policy to real evidence. (Never caught
# before because this workflow's trigger meant it never ran.)
- name: Compute attested subject digest
id: digest
run: |
test -f ./forgeseal-output/sbom.cdx.json
DIGEST="sha256:$(sha256sum ./forgeseal-output/sbom.cdx.json | awk '{print $1}')"
echo "value=$DIGEST" >> "$GITHUB_OUTPUT"
- name: Obtain svidmint SVID
id: svid
if: vars.SVIDMINT_URL != ''
run: |
# Exchange GitHub OIDC token for a SPIFFE SVID from svidmint
OIDC_TOKEN=$(curl -sSfL \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=svidmint" | jq -r .value)
RESPONSE=$(curl -sSfL \
-X POST "${{ vars.SVIDMINT_URL }}/v1/attest" \
-H "Content-Type: application/json" \
-d "{\"token\": \"${OIDC_TOKEN}\", \"audience\": \"${{ steps.digest.outputs.value }}\"}")
echo "svid=$(echo "$RESPONSE" | jq -r .svid)" >> "$GITHUB_OUTPUT"
echo "bundle=$(echo "$RESPONSE" | jq -r .spiffe_bundle)" >> "$GITHUB_OUTPUT"
env:
ACTIONS_ID_TOKEN_REQUEST_URL: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }}
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${{ env.ACTIONS_ID_TOKEN_REQUEST_TOKEN }}
- name: Note SVID skipped
if: vars.SVIDMINT_URL == ''
run: echo "SVIDMINT_URL not configured; SVID identity step skipped"
- name: Assayward gate
uses: sns45/assayward-action@v0.1
with:
forgeseal-output: ./forgeseal-output
image: forgeseal-artifact@${{ steps.digest.outputs.value }}
svid: ${{ steps.svid.outputs.svid }}
spiffe-bundle: ${{ steps.svid.outputs.bundle }}
policy-file: ./assayward-dogfood-policy.yaml