# .gitea/workflows/release-keyless-sign.yml # Keyless signing for StellaOps release artifacts # # This workflow signs release artifacts using keyless signing (Fulcio). # It demonstrates dogfooding of the keyless signing feature. # # Triggers: # - After release bundle is published # - Manual trigger for re-signing # # Artifacts signed: # - Container images # - CLI binaries # - SBOM documents # - Release manifest name: Release Keyless Signing on: release: types: [published] workflow_dispatch: inputs: version: description: 'Release version to sign (e.g., 2025.12.0)' required: true type: string dry_run: description: 'Dry run (skip actual signing)' required: false default: false type: boolean env: STELLAOPS_URL: "https://api.stella-ops.internal" REGISTRY: registry.stella-ops.org jobs: sign-images: runs-on: ubuntu-22.04 permissions: id-token: write contents: read packages: write outputs: scanner-attestation: ${{ steps.sign-scanner.outputs.attestation-digest }} cli-attestation: ${{ steps.sign-cli.outputs.attestation-digest }} gateway-attestation: ${{ steps.sign-gateway.outputs.attestation-digest }} steps: - name: Checkout uses: actions/checkout@v4 - name: Determine Version id: version run: | if [[ -n "${{ github.event.inputs.version }}" ]]; then VERSION="${{ github.event.inputs.version }}" else VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#v}" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "Release version: ${VERSION}" - name: Install StellaOps CLI run: | curl -sL https://get.stella-ops.org/cli | sh echo "$HOME/.stellaops/bin" >> $GITHUB_PATH - name: Log in to Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Get OIDC Token id: oidc run: | OIDC_TOKEN="${ACTIONS_ID_TOKEN}" if [[ -z "$OIDC_TOKEN" ]]; then echo "::error::OIDC token not available" exit 1 fi echo "::add-mask::${OIDC_TOKEN}" echo "token=${OIDC_TOKEN}" >> $GITHUB_OUTPUT - name: Sign Scanner Image id: sign-scanner if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | VERSION="${{ steps.version.outputs.version }}" IMAGE="${REGISTRY}/stellaops/scanner:${VERSION}" echo "Signing scanner image: ${IMAGE}" DIGEST=$(docker manifest inspect "${IMAGE}" -v | jq -r '.Descriptor.digest') RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type image \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') REKOR=$(echo "$RESULT" | jq -r '.rekorUuid') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT echo "rekor-uuid=${REKOR}" >> $GITHUB_OUTPUT # Push attestation to registry stella attest push \ --attestation "${ATTESTATION}" \ --registry "stellaops/scanner" - name: Sign CLI Image id: sign-cli if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | VERSION="${{ steps.version.outputs.version }}" IMAGE="${REGISTRY}/stellaops/cli:${VERSION}" echo "Signing CLI image: ${IMAGE}" DIGEST=$(docker manifest inspect "${IMAGE}" -v | jq -r '.Descriptor.digest') RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type image \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT stella attest push \ --attestation "${ATTESTATION}" \ --registry "stellaops/cli" - name: Sign Gateway Image id: sign-gateway if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | VERSION="${{ steps.version.outputs.version }}" IMAGE="${REGISTRY}/stellaops/gateway:${VERSION}" echo "Signing gateway image: ${IMAGE}" DIGEST=$(docker manifest inspect "${IMAGE}" -v | jq -r '.Descriptor.digest') RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type image \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT stella attest push \ --attestation "${ATTESTATION}" \ --registry "stellaops/gateway" sign-binaries: runs-on: ubuntu-22.04 permissions: id-token: write contents: read outputs: cli-linux-x64: ${{ steps.sign-cli-linux-x64.outputs.attestation-digest }} cli-linux-arm64: ${{ steps.sign-cli-linux-arm64.outputs.attestation-digest }} cli-darwin-x64: ${{ steps.sign-cli-darwin-x64.outputs.attestation-digest }} cli-darwin-arm64: ${{ steps.sign-cli-darwin-arm64.outputs.attestation-digest }} cli-windows-x64: ${{ steps.sign-cli-windows-x64.outputs.attestation-digest }} steps: - name: Checkout uses: actions/checkout@v4 - name: Determine Version id: version run: | if [[ -n "${{ github.event.inputs.version }}" ]]; then VERSION="${{ github.event.inputs.version }}" else VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#v}" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Install StellaOps CLI run: | curl -sL https://get.stella-ops.org/cli | sh echo "$HOME/.stellaops/bin" >> $GITHUB_PATH - name: Download Release Artifacts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | VERSION="${{ steps.version.outputs.version }}" mkdir -p artifacts # Download CLI binaries gh release download "v${VERSION}" \ --pattern "stellaops-cli-*" \ --dir artifacts \ || echo "No CLI binaries found" - name: Get OIDC Token id: oidc run: | OIDC_TOKEN="${ACTIONS_ID_TOKEN}" echo "::add-mask::${OIDC_TOKEN}" echo "token=${OIDC_TOKEN}" >> $GITHUB_OUTPUT - name: Sign CLI Binary (linux-x64) id: sign-cli-linux-x64 if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | BINARY="artifacts/stellaops-cli-linux-x64" if [[ -f "$BINARY" ]]; then DIGEST="sha256:$(sha256sum "$BINARY" | cut -d' ' -f1)" RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type binary \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT fi - name: Sign CLI Binary (linux-arm64) id: sign-cli-linux-arm64 if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | BINARY="artifacts/stellaops-cli-linux-arm64" if [[ -f "$BINARY" ]]; then DIGEST="sha256:$(sha256sum "$BINARY" | cut -d' ' -f1)" RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type binary \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT fi - name: Sign CLI Binary (darwin-x64) id: sign-cli-darwin-x64 if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | BINARY="artifacts/stellaops-cli-darwin-x64" if [[ -f "$BINARY" ]]; then DIGEST="sha256:$(sha256sum "$BINARY" | cut -d' ' -f1)" RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type binary \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT fi - name: Sign CLI Binary (darwin-arm64) id: sign-cli-darwin-arm64 if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | BINARY="artifacts/stellaops-cli-darwin-arm64" if [[ -f "$BINARY" ]]; then DIGEST="sha256:$(sha256sum "$BINARY" | cut -d' ' -f1)" RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type binary \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT fi - name: Sign CLI Binary (windows-x64) id: sign-cli-windows-x64 if: ${{ github.event.inputs.dry_run != 'true' }} env: STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }} run: | BINARY="artifacts/stellaops-cli-windows-x64.exe" if [[ -f "$BINARY" ]]; then DIGEST="sha256:$(sha256sum "$BINARY" | cut -d' ' -f1)" RESULT=$(stella attest sign \ --keyless \ --artifact "${DIGEST}" \ --type binary \ --rekor \ --output json) ATTESTATION=$(echo "$RESULT" | jq -r '.attestationDigest') echo "attestation-digest=${ATTESTATION}" >> $GITHUB_OUTPUT fi verify-signatures: needs: [sign-images, sign-binaries] runs-on: ubuntu-22.04 permissions: contents: read packages: read steps: - name: Install StellaOps CLI run: | curl -sL https://get.stella-ops.org/cli | sh echo "$HOME/.stellaops/bin" >> $GITHUB_PATH - name: Determine Version id: version run: | if [[ -n "${{ github.event.inputs.version }}" ]]; then VERSION="${{ github.event.inputs.version }}" else VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#v}" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Verify Scanner Image if: ${{ github.event.inputs.dry_run != 'true' }} run: | VERSION="${{ steps.version.outputs.version }}" IMAGE="${REGISTRY}/stellaops/scanner:${VERSION}" DIGEST=$(docker manifest inspect "${IMAGE}" -v | jq -r '.Descriptor.digest') stella attest verify \ --artifact "${DIGEST}" \ --certificate-identity "stella-ops.org/git.stella-ops.org:ref:refs/tags/v${VERSION}" \ --certificate-oidc-issuer "https://git.stella-ops.org" \ --require-rekor - name: Summary run: | VERSION="${{ steps.version.outputs.version }}" cat >> $GITHUB_STEP_SUMMARY << EOF ## Release v${VERSION} Signed ### Container Images | Image | Attestation | |-------|-------------| | scanner | \`${{ needs.sign-images.outputs.scanner-attestation }}\` | | cli | \`${{ needs.sign-images.outputs.cli-attestation }}\` | | gateway | \`${{ needs.sign-images.outputs.gateway-attestation }}\` | ### CLI Binaries | Platform | Attestation | |----------|-------------| | linux-x64 | \`${{ needs.sign-binaries.outputs.cli-linux-x64 }}\` | | linux-arm64 | \`${{ needs.sign-binaries.outputs.cli-linux-arm64 }}\` | | darwin-x64 | \`${{ needs.sign-binaries.outputs.cli-darwin-x64 }}\` | | darwin-arm64 | \`${{ needs.sign-binaries.outputs.cli-darwin-arm64 }}\` | | windows-x64 | \`${{ needs.sign-binaries.outputs.cli-windows-x64 }}\` | ### Verification \`\`\`bash stella attest verify \\ --artifact "sha256:..." \\ --certificate-identity "stella-ops.org/git.stella-ops.org:ref:refs/tags/v${VERSION}" \\ --certificate-oidc-issuer "https://git.stella-ops.org" \`\`\` EOF