Files
git.stella-ops.org/.github/workflows/examples/stellaops-sign.yml
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

217 lines
7.4 KiB
YAML

# .github/workflows/examples/stellaops-sign.yml
# StellaOps Keyless Sign Reusable Workflow
#
# This reusable workflow enables keyless signing of artifacts using Sigstore Fulcio.
# It uses OIDC identity tokens from GitHub Actions to obtain ephemeral signing certificates.
#
# Usage:
# jobs:
# sign:
# uses: stella-ops/templates/.github/workflows/stellaops-sign.yml@v1
# with:
# artifact-digest: sha256:abc123...
# artifact-type: image
# permissions:
# id-token: write
# contents: read
#
# Prerequisites:
# - StellaOps API accessible from runner
# - OIDC token permissions granted
#
# See: docs/modules/signer/guides/keyless-signing.md
name: StellaOps Keyless Sign
on:
workflow_call:
inputs:
artifact-digest:
description: 'SHA256 digest of artifact to sign (e.g., sha256:abc123...)'
required: true
type: string
artifact-type:
description: 'Type of artifact: image, sbom, verdict, report'
required: false
type: string
default: 'image'
stellaops-url:
description: 'StellaOps API URL'
required: false
type: string
default: 'https://api.stella-ops.org'
push-attestation:
description: 'Push attestation to OCI registry'
required: false
type: boolean
default: true
predicate-type:
description: 'Custom predicate type URI (optional)'
required: false
type: string
default: ''
include-rekor:
description: 'Log signature to Rekor transparency log'
required: false
type: boolean
default: true
cli-version:
description: 'StellaOps CLI version to use'
required: false
type: string
default: 'latest'
outputs:
attestation-digest:
description: 'Digest of created attestation'
value: ${{ jobs.sign.outputs.attestation-digest }}
rekor-uuid:
description: 'Rekor transparency log UUID (if logged)'
value: ${{ jobs.sign.outputs.rekor-uuid }}
certificate-identity:
description: 'OIDC identity bound to certificate'
value: ${{ jobs.sign.outputs.certificate-identity }}
signed-at:
description: 'Signing timestamp (UTC ISO-8601)'
value: ${{ jobs.sign.outputs.signed-at }}
jobs:
sign:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC token
contents: read # Required for checkout
packages: write # Required if pushing to GHCR
outputs:
attestation-digest: ${{ steps.sign.outputs.attestation-digest }}
rekor-uuid: ${{ steps.sign.outputs.rekor-uuid }}
certificate-identity: ${{ steps.sign.outputs.certificate-identity }}
signed-at: ${{ steps.sign.outputs.signed-at }}
steps:
- name: Validate Inputs
run: |
if [[ ! "${{ inputs.artifact-digest }}" =~ ^sha256:[a-f0-9]{64}$ ]] && \
[[ ! "${{ inputs.artifact-digest }}" =~ ^sha512:[a-f0-9]{128}$ ]]; then
echo "::error::Invalid artifact-digest format. Expected sha256:... or sha512:..."
exit 1
fi
VALID_TYPES="image sbom verdict report binary"
if [[ ! " $VALID_TYPES " =~ " ${{ inputs.artifact-type }} " ]]; then
echo "::error::Invalid artifact-type. Must be one of: $VALID_TYPES"
exit 1
fi
- name: Install StellaOps CLI
uses: stella-ops/setup-cli@v1
with:
version: ${{ inputs.cli-version }}
- name: Get OIDC Token
id: oidc
run: |
set -euo pipefail
# Request OIDC token with sigstore audience
OIDC_TOKEN=$(curl -sLS "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sigstore" \
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
| jq -r '.value')
if [[ -z "$OIDC_TOKEN" || "$OIDC_TOKEN" == "null" ]]; then
echo "::error::Failed to obtain OIDC token"
exit 1
fi
# Mask token in logs
echo "::add-mask::${OIDC_TOKEN}"
echo "token=${OIDC_TOKEN}" >> $GITHUB_OUTPUT
# Extract identity for logging (non-sensitive)
IDENTITY=$(echo "$OIDC_TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq -r '.sub // "unknown"' 2>/dev/null || echo "unknown")
echo "identity=${IDENTITY}" >> $GITHUB_OUTPUT
- name: Keyless Sign
id: sign
env:
STELLAOPS_OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
STELLAOPS_URL: ${{ inputs.stellaops-url }}
run: |
set -euo pipefail
SIGN_ARGS=(
--keyless
--artifact "${{ inputs.artifact-digest }}"
--type "${{ inputs.artifact-type }}"
--output json
)
# Add optional predicate type
if [[ -n "${{ inputs.predicate-type }}" ]]; then
SIGN_ARGS+=(--predicate-type "${{ inputs.predicate-type }}")
fi
# Add Rekor logging option
if [[ "${{ inputs.include-rekor }}" == "true" ]]; then
SIGN_ARGS+=(--rekor)
fi
echo "::group::Signing artifact"
RESULT=$(stella attest sign "${SIGN_ARGS[@]}")
echo "$RESULT" | jq .
echo "::endgroup::"
# Extract outputs
ATTESTATION_DIGEST=$(echo "$RESULT" | jq -r '.attestationDigest // empty')
REKOR_UUID=$(echo "$RESULT" | jq -r '.rekorUuid // empty')
CERT_IDENTITY=$(echo "$RESULT" | jq -r '.certificateIdentity // empty')
SIGNED_AT=$(echo "$RESULT" | jq -r '.signedAt // empty')
if [[ -z "$ATTESTATION_DIGEST" ]]; then
echo "::error::Signing failed - no attestation digest returned"
exit 1
fi
echo "attestation-digest=${ATTESTATION_DIGEST}" >> $GITHUB_OUTPUT
echo "rekor-uuid=${REKOR_UUID}" >> $GITHUB_OUTPUT
echo "certificate-identity=${CERT_IDENTITY}" >> $GITHUB_OUTPUT
echo "signed-at=${SIGNED_AT}" >> $GITHUB_OUTPUT
- name: Push Attestation
if: ${{ inputs.push-attestation }}
env:
STELLAOPS_URL: ${{ inputs.stellaops-url }}
run: |
set -euo pipefail
echo "::group::Pushing attestation to registry"
stella attest push \
--attestation "${{ steps.sign.outputs.attestation-digest }}" \
--registry "${{ github.repository }}"
echo "::endgroup::"
- name: Generate Summary
run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF'
## Attestation Created
| Field | Value |
|-------|-------|
| **Artifact** | `${{ inputs.artifact-digest }}` |
| **Type** | `${{ inputs.artifact-type }}` |
| **Attestation** | `${{ steps.sign.outputs.attestation-digest }}` |
| **Rekor UUID** | `${{ steps.sign.outputs.rekor-uuid || 'N/A' }}` |
| **Certificate Identity** | `${{ steps.sign.outputs.certificate-identity }}` |
| **Signed At** | `${{ steps.sign.outputs.signed-at }}` |
| **Signing Mode** | Keyless (Fulcio) |
### Verification Command
```bash
stella attest verify \
--artifact "${{ inputs.artifact-digest }}" \
--certificate-identity "${{ steps.sign.outputs.certificate-identity }}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
```
EOF