a11oy / .github /workflows /uds-sign-release.yml
betterwithage's picture
sync(space): full source mirror — resolve all GitHub<->Space drift (CTO)
a6a5d8e verified
Raw
History Blame
10.8 kB
name: UDS Sign Release
# Builds the versioned tar.zst from the tagged commit, signs it with
# cosign keyless (GitHub OIDC — no stored secrets), and uploads the 4
# required signed assets to the GitHub release.
#
# Trigger options:
# 1. Automatic: fires on `release: types: [published]` for any uds-v*
# 2. Manual: workflow_dispatch with `tag_name` input (for backfilling)
#
# 5-asset output pattern (matches what `gh release upload` actually uploads):
# a11oy-uds-<version>.tar.zst (zstd source archive)
# a11oy-uds-<version>.tar.zst.sha256 (checksum)
# a11oy-uds-<version>.tar.zst.sigstore.json (cosign bundle — use this to verify)
# a11oy-uds-<version>.tar.zst.sig (copy of the bundle; see note below)
# a11oy-uds-dev.pub (keyless verification instructions)
#
# NOTE on the .sig sidecar: cosign emits the legacy bundle format
# {base64Signature, cert, rekorBundle}; the extractor below looks for
# messageSignature/verificationMaterial (absent in that format) and therefore
# always falls through to copying the whole .sigstore.json bundle into .sig.
# Verification MUST use --bundle <pkg>.sigstore.json (NOT --signature <pkg>.sig).
# The .sig file is retained only for asset-shape parity with v0.1.0/v0.2.0.
#
# Cosign keyless verification:
# cosign verify-blob \
# --certificate-identity-regexp "https://github.com/szl-holdings/a11oy/.github/workflows/uds-sign-release.yml.*" \
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
# --bundle a11oy-uds-<version>.tar.zst.sigstore.json \
# a11oy-uds-<version>.tar.zst
#
# Doctrine v7: no fake signatures, no fabricated assets.
#
# Satisfies FA-001 (founder-action release-signing) per the PhD Crypto
# verdict (Finding E/F: live Sigstore Fulcio+Rekor keyless chain verified)
# and the PhD Systems Scope-1 finding (signed deployable artifact at
# uds-v* tags). This is the real verify step — NOT an `echo OK` stub
# (cf. PhD Crypto Finding D1 on the slsa.yml no-op).
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag_name:
description: 'Release tag to sign (e.g. uds-v0.3.0)'
required: true
type: string
permissions:
contents: read
jobs:
build-and-sign:
name: Build tar.zst, sign, upload
runs-on: ubuntu-latest
permissions:
contents: write # upload release assets
id-token: write # cosign keyless OIDC token
# Only run for uds-v* tags (ignore v1.0.0-alpha etc.)
if: |
(github.event_name == 'release' && startsWith(github.event.release.tag_name, 'uds-v')) ||
(github.event_name == 'workflow_dispatch' && startsWith(inputs.tag_name, 'uds-v'))
steps:
- name: Resolve tag name
id: tag
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ inputs.tag_name }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
# Extract version: uds-v0.3.0 -> 0.3.0
VERSION="${TAG#uds-v}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tarball=a11oy-uds-${VERSION}.tar.zst" >> "$GITHUB_OUTPUT"
echo "sha256=a11oy-uds-${VERSION}.tar.zst.sha256" >> "$GITHUB_OUTPUT"
echo "sig=a11oy-uds-${VERSION}.tar.zst.sig" >> "$GITHUB_OUTPUT"
echo "bundle=a11oy-uds-${VERSION}.tar.zst.sigstore.json" >> "$GITHUB_OUTPUT"
echo "pubkey=a11oy-uds-dev.pub" >> "$GITHUB_OUTPUT"
- name: Checkout tag
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ steps.tag.outputs.tag }}
fetch-depth: 0
- name: Install Syft (for embedded SBOM)
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh \
| sh -s -- -b /usr/local/bin v1.44.0
syft version
- name: Build tar.zst with embedded Syft SBOM
run: |
TAG="${{ steps.tag.outputs.tag }}"
VERSION="${{ steps.tag.outputs.version }}"
TARBALL="${{ steps.tag.outputs.tarball }}"
echo "Building ${TARBALL} from ${TAG} (SBOM embedded inside the tarball)..."
# 1) Materialise the exact source tree that ships in the tarball.
STAGE="$(mktemp -d)"
PREFIX="a11oy-uds-${VERSION}"
git archive --format=tar --prefix="${PREFIX}/" HEAD | tar -x -C "${STAGE}"
# 2) Generate the SBOM over that materialised tree, in BOTH
# CycloneDX and SPDX JSON, per SLSA L1 supply-chain evidence.
mkdir -p "${STAGE}/${PREFIX}/deploy"
syft "dir:${STAGE}/${PREFIX}" \
-o cyclonedx-json="${STAGE}/${PREFIX}/deploy/sbom.cyclonedx.json" \
-o spdx-json="${STAGE}/${PREFIX}/deploy/sbom.spdx.json"
echo "SBOMs embedded at ${PREFIX}/deploy/:"
ls -la "${STAGE}/${PREFIX}/deploy/"
# 3) Re-archive the tree (now INCLUDING the SBOMs) and compress.
# The cosign signature computed later therefore covers the SBOM.
tar -C "${STAGE}" -cf - "${PREFIX}" | zstd -19 -T0 -o "${TARBALL}"
rm -rf "${STAGE}"
echo "Built ${TARBALL}: $(wc -c < "${TARBALL}") bytes (includes deploy/sbom.cyclonedx.json + deploy/sbom.spdx.json)"
- name: Compute sha256
run: |
SHA256="${{ steps.tag.outputs.sha256 }}"
TARBALL="${{ steps.tag.outputs.tarball }}"
sha256sum "${TARBALL}" > "${SHA256}"
echo "sha256 checksum:"
cat "${SHA256}"
- name: Install cosign
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Sign with cosign keyless (GitHub OIDC)
env:
COSIGN_EXPERIMENTAL: "1"
run: |
TARBALL="${{ steps.tag.outputs.tarball }}"
BUNDLE="${{ steps.tag.outputs.bundle }}"
# Keyless signing — uses GitHub OIDC token, no stored private key needed.
# Verification identity: the workflow URL + OIDC issuer.
cosign sign-blob \
--yes \
--bundle "${BUNDLE}" \
"${TARBALL}"
echo "Signed. Bundle written to ${BUNDLE}"
ls -la "${BUNDLE}"
- name: Extract raw sig from bundle (for .sig sidecar)
run: |
BUNDLE="${{ steps.tag.outputs.bundle }}"
SIG="${{ steps.tag.outputs.sig }}"
# Extract the base64 signature from the Sigstore bundle
python3 -c "
import json, sys
with open('${BUNDLE}') as f:
b = json.load(f)
# Try messageSignature first, then dsseEnvelope
sig = (b.get('messageSignature', {}).get('signature')
or b.get('verificationMaterial', {}).get('content', ''))
if not sig:
# Fallback: write bundle itself as the sig file
print(open('${BUNDLE}').read(), end='')
sys.exit(0)
print(sig, end='')
" > "${SIG}" || cp "${BUNDLE}" "${SIG}"
echo "sig file written: $(wc -c < "${SIG}") bytes"
- name: Generate keyless pubkey placeholder
run: |
PUBKEY="${{ steps.tag.outputs.pubkey }}"
TAG="${{ steps.tag.outputs.tag }}"
# For keyless signing there is no traditional pub key.
# The verification identity is the workflow URL + OIDC issuer.
# We write a verification instructions file instead of a raw EC public key.
cat > "${PUBKEY}" <<'PUBKEYEOF'
# a11oy-uds keyless verification (cosign keyless / Sigstore Fulcio)
#
# This release uses keyless cosign signing. There is no stored private key.
# Verify with:
#
# cosign verify-blob \
# --certificate-identity-regexp \
# "https://github.com/szl-holdings/a11oy/.github/workflows/uds-sign-release.yml.*" \
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
# --bundle a11oy-uds-VERSION.tar.zst.sigstore.json \
# a11oy-uds-VERSION.tar.zst
#
# The signing certificate and transparency log entry are embedded in the
# .sigstore.json bundle attached to this release.
PUBKEYEOF
echo "pubkey placeholder written"
- name: Verify signature (self-check)
env:
COSIGN_EXPERIMENTAL: "1"
run: |
TARBALL="${{ steps.tag.outputs.tarball }}"
BUNDLE="${{ steps.tag.outputs.bundle }}"
cosign verify-blob \
--certificate-identity-regexp \
"https://github.com/szl-holdings/a11oy/.github/workflows/uds-sign-release.yml.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--bundle "${BUNDLE}" \
"${TARBALL}" \
&& echo "Self-verification PASSED"
- name: Upload signed assets to GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.tag.outputs.tag }}"
TARBALL="${{ steps.tag.outputs.tarball }}"
SHA256="${{ steps.tag.outputs.sha256 }}"
SIG="${{ steps.tag.outputs.sig }}"
BUNDLE="${{ steps.tag.outputs.bundle }}"
PUBKEY="${{ steps.tag.outputs.pubkey }}"
echo "Uploading assets to release ${TAG}..."
gh release upload "${TAG}" \
"${TARBALL}" \
"${SHA256}" \
"${SIG}" \
"${BUNDLE}" \
"${PUBKEY}" \
--clobber \
--repo szl-holdings/a11oy
echo "Upload complete."
- name: Print verification instructions
run: |
TAG="${{ steps.tag.outputs.tag }}"
VERSION="${{ steps.tag.outputs.version }}"
echo ""
echo "=== Verification Instructions ==="
echo "BASE=https://github.com/szl-holdings/a11oy/releases/download/${TAG}"
echo ""
echo "curl -fsSLO \${BASE}/a11oy-uds-${VERSION}.tar.zst"
echo "curl -fsSLO \${BASE}/a11oy-uds-${VERSION}.tar.zst.sha256"
echo "curl -fsSLO \${BASE}/a11oy-uds-${VERSION}.tar.zst.sigstore.json"
echo ""
echo "sha256sum -c a11oy-uds-${VERSION}.tar.zst.sha256"
echo ""
echo "cosign verify-blob \\"
echo " --certificate-identity-regexp \\"
echo " 'https://github.com/szl-holdings/a11oy/.github/workflows/uds-sign-release.yml.*' \\"
echo " --certificate-oidc-issuer https://token.actions.githubusercontent.com \\"
echo " --bundle a11oy-uds-${VERSION}.tar.zst.sigstore.json \\"
echo " a11oy-uds-${VERSION}.tar.zst"