Files
git.stella-ops.org/.github/workflows/examples/stellaops-verify.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

220 lines
7.1 KiB
YAML

# .github/workflows/examples/stellaops-verify.yml
# StellaOps Verification Gate Reusable Workflow
#
# This reusable workflow verifies attestations before deployment.
# Use it as a gate in your CI/CD pipeline to ensure only properly
# signed artifacts are deployed.
#
# Usage:
# jobs:
# verify:
# uses: stella-ops/templates/.github/workflows/stellaops-verify.yml@v1
# with:
# artifact-digest: sha256:abc123...
# certificate-identity: 'repo:myorg/myrepo:ref:refs/heads/main'
# certificate-oidc-issuer: 'https://token.actions.githubusercontent.com'
#
# See: docs/modules/signer/guides/keyless-signing.md
name: StellaOps Verify Gate
on:
workflow_call:
inputs:
artifact-digest:
description: 'SHA256 digest of artifact to verify'
required: true
type: string
stellaops-url:
description: 'StellaOps API URL'
required: false
type: string
default: 'https://api.stella-ops.org'
certificate-identity:
description: 'Expected OIDC identity pattern (supports regex)'
required: true
type: string
certificate-oidc-issuer:
description: 'Expected OIDC issuer URL'
required: true
type: string
require-rekor:
description: 'Require Rekor transparency log inclusion proof'
required: false
type: boolean
default: true
strict:
description: 'Fail workflow on any verification issue'
required: false
type: boolean
default: true
max-cert-age-hours:
description: 'Maximum age of signing certificate in hours (0 = no limit)'
required: false
type: number
default: 0
require-sbom:
description: 'Require SBOM attestation'
required: false
type: boolean
default: false
require-verdict:
description: 'Require passing policy verdict attestation'
required: false
type: boolean
default: false
cli-version:
description: 'StellaOps CLI version to use'
required: false
type: string
default: 'latest'
outputs:
verified:
description: 'Whether all verifications passed'
value: ${{ jobs.verify.outputs.verified }}
attestation-count:
description: 'Number of attestations found'
value: ${{ jobs.verify.outputs.attestation-count }}
verification-details:
description: 'JSON details of verification results'
value: ${{ jobs.verify.outputs.verification-details }}
jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
outputs:
verified: ${{ steps.verify.outputs.verified }}
attestation-count: ${{ steps.verify.outputs.attestation-count }}
verification-details: ${{ steps.verify.outputs.verification-details }}
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
if [[ -z "${{ inputs.certificate-identity }}" ]]; then
echo "::error::certificate-identity is required"
exit 1
fi
if [[ -z "${{ inputs.certificate-oidc-issuer }}" ]]; then
echo "::error::certificate-oidc-issuer is required"
exit 1
fi
- name: Install StellaOps CLI
uses: stella-ops/setup-cli@v1
with:
version: ${{ inputs.cli-version }}
- name: Verify Attestation
id: verify
env:
STELLAOPS_URL: ${{ inputs.stellaops-url }}
run: |
set +e # Don't exit on error - we handle it
VERIFY_ARGS=(
--artifact "${{ inputs.artifact-digest }}"
--certificate-identity "${{ inputs.certificate-identity }}"
--certificate-oidc-issuer "${{ inputs.certificate-oidc-issuer }}"
--output json
)
# Add optional flags
if [[ "${{ inputs.require-rekor }}" == "true" ]]; then
VERIFY_ARGS+=(--require-rekor)
fi
if [[ "${{ inputs.max-cert-age-hours }}" -gt 0 ]]; then
VERIFY_ARGS+=(--max-cert-age-hours "${{ inputs.max-cert-age-hours }}")
fi
if [[ "${{ inputs.require-sbom }}" == "true" ]]; then
VERIFY_ARGS+=(--require-sbom)
fi
if [[ "${{ inputs.require-verdict }}" == "true" ]]; then
VERIFY_ARGS+=(--require-verdict)
fi
echo "::group::Verifying attestations"
RESULT=$(stella attest verify "${VERIFY_ARGS[@]}" 2>&1)
EXIT_CODE=$?
echo "$RESULT" | jq . 2>/dev/null || echo "$RESULT"
echo "::endgroup::"
set -e
# Parse results
VERIFIED=$(echo "$RESULT" | jq -r '.valid // false')
ATTESTATION_COUNT=$(echo "$RESULT" | jq -r '.attestationCount // 0')
echo "verified=${VERIFIED}" >> $GITHUB_OUTPUT
echo "attestation-count=${ATTESTATION_COUNT}" >> $GITHUB_OUTPUT
echo "verification-details=$(echo "$RESULT" | jq -c '.')" >> $GITHUB_OUTPUT
# Handle verification failure
if [[ "$VERIFIED" != "true" ]]; then
echo "::warning::Verification failed"
# Extract and report issues
ISSUES=$(echo "$RESULT" | jq -r '.issues[]? | "\(.code): \(.message)"' 2>/dev/null)
if [[ -n "$ISSUES" ]]; then
while IFS= read -r issue; do
echo "::error::$issue"
done <<< "$ISSUES"
fi
if [[ "${{ inputs.strict }}" == "true" ]]; then
echo "::error::Verification failed in strict mode"
exit 1
fi
fi
- name: Generate Summary
if: always()
run: |
VERIFIED="${{ steps.verify.outputs.verified }}"
if [[ "$VERIFIED" == "true" ]]; then
ICON="white_check_mark"
STATUS="Passed"
else
ICON="x"
STATUS="Failed"
fi
cat >> $GITHUB_STEP_SUMMARY << EOF
## :${ICON}: Verification ${STATUS}
| Field | Value |
|-------|-------|
| **Artifact** | \`${{ inputs.artifact-digest }}\` |
| **Expected Identity** | \`${{ inputs.certificate-identity }}\` |
| **Expected Issuer** | \`${{ inputs.certificate-oidc-issuer }}\` |
| **Attestations Found** | ${{ steps.verify.outputs.attestation-count }} |
| **Rekor Required** | ${{ inputs.require-rekor }} |
| **Strict Mode** | ${{ inputs.strict }} |
EOF
# Add issues if any
DETAILS='${{ steps.verify.outputs.verification-details }}'
ISSUES=$(echo "$DETAILS" | jq -r '.issues[]? | "- **\(.code)**: \(.message)"' 2>/dev/null)
if [[ -n "$ISSUES" ]]; then
cat >> $GITHUB_STEP_SUMMARY << EOF
### Issues
$ISSUES
EOF
fi