- 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.
217 lines
7.4 KiB
YAML
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
|