From 21e64eb380d47be56df95394dbdc1f3d96054303 Mon Sep 17 00:00:00 2001 From: Lior Date: Wed, 27 May 2026 03:26:42 -0400 Subject: [PATCH] feat(B-0853.1): cosign keyless OIDC signing for ISO blob in build-ai-cluster-iso workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B-0853 sub-row .1 — smallest end-to-end slice of the sigstore artifact- signing substrate. Composes with the existing build-ai-cluster-iso CI flow to sign the freshly-built ISO via GitHub OIDC + Fulcio CA + Rekor transparency log, with zero key management. Three workflow changes: 1. Add `id-token: write` to workflow-level permissions (required for sigstore keyless OIDC; no private key material handled). 2. Insert Install-cosign + Sign-ISO-with-cosign steps after the Locate-ISO-metadata step + before Upload-ISO-artifact. 3. Add second Upload step for the .sig + .pem alongside the ISO (separate artifact bundle so verifiers can download just the small signature pair without re-fetching the ~1.5GB ISO). Pin verified via gh API 2026-05-27 per .claude/rules/dep-pin-search- first-authority.md: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 (published 2026-05-07) Verification command published in workflow comments: cosign verify-blob \ --certificate .pem --signature .sig \ --certificate-identity-regexp '^https://github.com/Lucent-Financial-Group/Zeta' \ --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ Security: all inputs to cosign sign-blob come from steps.iso.outputs of THIS workflow via env-var hop (ISO_PATH env in run block). Matches the workflow's existing discipline at the QEMU boot-test step; no github.event.* interpolation in any run: block. The id-token scope is workflow-bound; granted permission cannot be exfiltrated to mint signatures for other workflows. Composes with: - B-0853 (parent row) — sigstore/cosign artifact signing free-stuff scope - B-0843 — artifact attestation; this lands the primitive - B-0850 — cluster substrate that consumes signed ISO - B-0830 (deferred) — release-attach work; sig + pem can attach to GitHub release when release tag created - Aaron 2026-05-27 authorization: "please start on the free stuff and backlog it" What this is NOT: - NOT container image signing (B-0853.2; sibling slice) - NOT cosign verify gate in zeta-install.sh (B-0853.5) - NOT SLSA provenance attestations (B-0853.7) - NOT cluster-side image policy webhook (B-0853.6) Empirical validation: workflow run on this PR triggers the new steps; sig + pem published to artifacts; rekor entry verifiable via rekor-cli. --- .github/workflows/build-ai-cluster-iso.yml | 76 ++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/.github/workflows/build-ai-cluster-iso.yml b/.github/workflows/build-ai-cluster-iso.yml index c8c0d9c011..aa62f738da 100644 --- a/.github/workflows/build-ai-cluster-iso.yml +++ b/.github/workflows/build-ai-cluster-iso.yml @@ -71,6 +71,12 @@ on: permissions: contents: read + id-token: write # B-0853.1: sigstore/cosign keyless OIDC signing. Fulcio CA + # mints a short-lived cert from the GitHub-issued OIDC token; + # Rekor transparency log records the signature. No private + # key material is handled in this workflow. The token-issuance + # scope is workflow-bound; granted permission cannot be + # exfiltrated to mint signatures for other workflows. concurrency: group: build-ai-cluster-iso-${{ github.workflow }}-${{ github.ref }} @@ -300,6 +306,63 @@ jobs: echo "to flash the stick. (ISO is ~1.5–2 GiB.)" } >> "$GITHUB_STEP_SUMMARY" + # B-0853.1 — sigstore/cosign keyless OIDC signing of the ISO blob. + # Composes with B-0853 (sigstore artifact signing free-stuff substrate). + # + # Keyless model: GitHub OIDC token (from id-token: write permission at + # workflow level) is exchanged with Fulcio CA for a short-lived cert + # bound to this workflow's identity. cosign then signs the ISO blob + # with that cert + records the signature in the Rekor transparency log. + # No long-lived private key material in this workflow. + # + # Verification (any consumer): + # cosign verify-blob \ + # --certificate .pem \ + # --signature .sig \ + # --certificate-identity-regexp '^https://github.com/Lucent-Financial-Group/Zeta' \ + # --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \ + # + # + # Security: inputs to cosign are filesystem paths from steps.iso.outputs + # of THIS workflow (no github.event.* interpolation). Same discipline + # as the QEMU boot-test step above. + - name: Install cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + # Pin verified via gh API 2026-05-27: + # gh api repos/sigstore/cosign-installer/releases/latest + # tag: v4.1.2; published: 2026-05-07T01:27:27Z + # Per .claude/rules/dep-pin-search-first-authority.md + + - name: Sign ISO with cosign (keyless OIDC + Fulcio + Rekor) + env: + # Pass ISO path via env (not direct ${{ }} interpolation in run:) + # per the workflow's existing security discipline. + ISO_PATH: ${{ steps.iso.outputs.path }} + ISO_NAME: ${{ steps.iso.outputs.name }} + run: | + set -euo pipefail + # --yes: skip interactive Fulcio confirmation (CI is non-interactive) + # Output files emitted alongside the ISO: + # .sig — signature (base64; small) + # .pem — short-lived Fulcio cert (the verifier's anchor) + cosign sign-blob --yes \ + --output-signature "${ISO_PATH}.sig" \ + --output-certificate "${ISO_PATH}.pem" \ + "${ISO_PATH}" + # Sanity: both artifacts present and non-empty. + test -s "${ISO_PATH}.sig" || { echo "::error::Empty signature"; exit 1; } + test -s "${ISO_PATH}.pem" || { echo "::error::Empty certificate"; exit 1; } + { + echo "## ISO signed via sigstore (cosign keyless OIDC)" + echo "" + echo "| Artifact | Path |" + echo "|---|---|" + echo "| Signature | \`${ISO_NAME}.sig\` |" + echo "| Certificate | \`${ISO_NAME}.pem\` |" + echo "" + echo "Verification: see workflow run logs for the canonical \`cosign verify-blob\` command." + } >> "$GITHUB_STEP_SUMMARY" + - name: Upload ISO as workflow artifact # Available for download from the workflow run page for ~90 days. uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 @@ -309,3 +372,16 @@ jobs: if-no-files-found: error retention-days: 90 compression-level: 0 # ISO is already compressed; re-zipping wastes time + + - name: Upload cosign signature + certificate as workflow artifact + # B-0853.1 sibling upload — sig + pem alongside the ISO for consumers. + # Separate artifact (not bundled with ISO) so consumers downloading + # just the ISO don't pay the (small) cost; verifiers can grab both. + uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + with: + name: ${{ steps.iso.outputs.name }}.cosign + path: | + ${{ steps.iso.outputs.path }}.sig + ${{ steps.iso.outputs.path }}.pem + if-no-files-found: error + retention-days: 90