Files
gstack/.github/workflows/cso-runtime-qualification.yml
T
Garry Tan 514fbfdc97 v1.91.176.0 cso: ignore the compiled helpers' leading #! line in the release contract's preparationSha256 (both sides) (#3333)
* cso: ignore the compiled helpers' leading #! line in preparationSha256, on both sides of the release contract

S1 (#3312) rewrote only line 1 of lib/cso/verifier.ts and
lib/cso/preparation-container.ts. That shebang is an unused string in the
compiled helper binaries, but it moved preparationSha256, so qualify run
39535948607 refused the run-5 statements staged at 133cb36 with
STALE_RUNTIME_QUALIFICATION. Garry chose this narrow rule (option 2).

The release contract re-measures a recorded preparationSha256 that differs
from the checkout's at the staged commit (git history), accepts a record
that matches it under the current or the legacy rule, and compares and
records the current-rule value. Both release workflows fetch full history.

* test(cso): prove the release-contract fingerprint rule without git history

Free-suite checkouts are depth 1, so the tests no longer read 133cb36 or
6b2b5f3. They rebuild the staged commit in a scratch repository from this
tree with both helpers' S1 shebang reverted. Its legacy fingerprint equals
the real run-5 record (b069f7ef...), so it is 133cb36's image source byte
for byte, and the contract accepts that record under the current rule.

* v1.91.176.0 stamp
2026-10-21 19:22:49 +00:00

163 lines
8.4 KiB
YAML

name: CSO Runtime Qualification
# A private evaluator sends only gate results, public runtime identities, and an
# optional opaque evaluation reference. Its held-out assertions remain outside
# this repository. GitHub authenticates the dispatch actor; the protected
# environment supplies the human review gate. The unprotected summary job runs
# first so the reviewer sees what the dispatch claims before approving.
on:
repository_dispatch:
types: [cso-runtime-qualified]
permissions:
contents: read
packages: read
attestations: read
jobs:
dispatch-summary:
if: github.ref == 'refs/heads/main' && github.event_name == 'repository_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions: {}
steps:
- name: Require the bounded payload and summarize it for the approver
run: |
set -euo pipefail
jq -e '
.action == "cso-runtime-qualified" and
((.client_payload | keys) as $keys | $keys == ["statements"] or (
$keys == ["evaluationRef", "statements"] and
(.client_payload.evaluationRef | type == "string" and test("^sha256:[a-f0-9]{64}$"))
)) and
(.client_payload.statements | type == "array" and length == 10) and
(.client_payload.statements | all(
type == "object" and
(.runtimeId | type == "string" and test("^[a-z0-9][a-z0-9._-]{0,100}$")) and
(.image | type == "string" and test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$")) and
(.sourceCommit | type == "string" and test("^[a-f0-9]{40}$"))
)) and
([.client_payload.statements[].sourceCommit] | unique | length == 1)
' "$GITHUB_EVENT_PATH"
jq -r '
"## CSO runtime qualification dispatch",
"",
"- evaluationRef: `\(.client_payload.evaluationRef // "none")`",
"- sourceCommit: `\(.client_payload.statements[0].sourceCommit)`",
"",
"| runtimeId | image |",
"| --- | --- |",
(.client_payload.statements[] | "| `\(.runtimeId)` | `\(.image)` |")
' "$GITHUB_EVENT_PATH" >> "$GITHUB_STEP_SUMMARY"
qualify:
needs: dispatch-summary
if: github.ref == 'refs/heads/main' && github.event_name == 'repository_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 40
environment: cso-runtime-release
permissions:
contents: read
packages: read
attestations: read
actions: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
with:
bun-version: 1.4.2
- name: Require the configured private-evaluator actor
env:
CSO_QUALIFICATION_ACTOR: ${{ vars.CSO_QUALIFICATION_ACTOR }}
run: |
set -euo pipefail
test -n "$CSO_QUALIFICATION_ACTOR"
test "$GITHUB_ACTOR" = "$CSO_QUALIFICATION_ACTOR"
- name: Recheck public visibility and anonymous pulls for every qualified digest
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir public-image-evidence
jq -c '.client_payload.statements[]' "$GITHUB_EVENT_PATH" | while IFS= read -r statement; do
runtime_id="$(printf '%s' "$statement" | jq -er '.runtimeId | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')"
image="$(printf '%s' "$statement" | jq -er '.image | select(test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$"))')"
platform="$(printf '%s' "$statement" | jq -er '.platform | select(. == "linux/amd64" or . == "linux/arm64")')"
bun run scripts/cso-public-ghcr.ts verify \
--image "$image" --platform "$platform" --repository "$GITHUB_REPOSITORY" \
--output "public-image-evidence/$runtime_id.json" --remove-after
done
- name: Verify each staged image attestation and normalize qualification statements
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
signer="https://github.com/$GITHUB_REPOSITORY/.github/workflows/cso-runtime-images.yml@refs/heads/main"
workflow="https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
qualified_at="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
mkdir qualification-evidence
jq -c '.client_payload.statements[]' "$GITHUB_EVENT_PATH" | while IFS= read -r statement; do
runtime_id="$(printf '%s' "$statement" | jq -er '.runtimeId | select(test("^[a-z0-9][a-z0-9._-]{0,100}$"))')"
image="$(printf '%s' "$statement" | jq -er '.image | select(test("^ghcr\\.io/garrytan/gstack/cso-staging/[a-z0-9-]+-(amd64|arm64)@sha256:[a-f0-9]{64}$"))')"
source_commit="$(printf '%s' "$statement" | jq -er '.sourceCommit | select(test("^[a-f0-9]{40}$"))')"
output="qualification-evidence/$runtime_id"
mkdir "$output"
gh attestation verify "oci://$image" --repo "$GITHUB_REPOSITORY" \
--cert-identity "$signer" --source-ref refs/heads/main --source-digest "$source_commit" \
--deny-self-hosted-runners --predicate-type https://slsa.dev/provenance/v1 \
--format json > "$output/provenance-verification.json"
gh attestation verify "oci://$image" --repo "$GITHUB_REPOSITORY" \
--cert-identity "$signer" --source-ref refs/heads/main --source-digest "$source_commit" \
--deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 \
--format json > "$output/sbom-verification.json"
provenance_digest="sha256:$(sha256sum "$output/provenance-verification.json" | cut -d ' ' -f 1)"
sbom_digest="sha256:$(sha256sum "$output/sbom-verification.json" | cut -d ' ' -f 1)"
printf '%s' "$statement" | jq \
--arg workflow "$workflow" --arg qualifiedAt "$qualified_at" \
--arg provenanceDigest "$provenance_digest" --arg sbomDigest "$sbom_digest" \
'{
schemaVersion:.schemaVersion,helperAbi:.helperAbi,state:.state,
buildRevision:.buildRevision,runtimeId:.runtimeId,stack:.stack,
platform:.platform,image:.image,versions:.versions,sourceCommit:.sourceCommit,
checks:.checks,workflow:$workflow,qualifiedAt:$qualifiedAt,
provenanceDigest:$provenanceDigest,sbomDigest:$sbomDigest
}' \
> "$output/qualified-runtime.json"
done
- name: Require one successful protected-main staging run with every native gate passed
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir staging-run-evidence
run_id="$(bun run scripts/cso-staging-run.ts run-id --repository "$GITHUB_REPOSITORY" \
qualification-evidence/*/provenance-verification.json)"
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" > staging-run-evidence/run.json
gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/actions/runs/$run_id/jobs?filter=latest&per_page=100" \
> staging-run-evidence/jobs.json
bun run scripts/cso-staging-run.ts verify --repository "$GITHUB_REPOSITORY" --run-id "$run_id" \
--event "$GITHUB_EVENT_PATH" --run staging-run-evidence/run.json --jobs staging-run-evidence/jobs.json
gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \
--name cso-requalification-triggers --dir requalification-download
mv requalification-download/requalification.json qualification-evidence/requalification.json
rmdir requalification-download
- name: Enforce the complete release contract before retaining evidence
run: |
set -euo pipefail
bun run scripts/cso-runtime-promotion.ts \
--evidence-root qualification-evidence \
--output runtime-catalog.proof.json
rm runtime-catalog.proof.json
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
with:
name: cso-qualified-runtime-statements
path: |
qualification-evidence
public-image-evidence
staging-run-evidence
if-no-files-found: error
retention-days: 30