268 lines
9.2 KiB
YAML
268 lines
9.2 KiB
YAML
# =============================================================================
|
|
# replay-verify.yml
|
|
# Sprint: SPRINT_20251228_001_BE_replay_manifest_ci (T4)
|
|
# Description: CI workflow template for SBOM hash drift detection
|
|
# =============================================================================
|
|
#
|
|
# This workflow verifies that SBOM generation and verdict computation are
|
|
# deterministic by comparing replay manifest hashes across builds.
|
|
#
|
|
# Usage:
|
|
# 1. Copy this template to your project's .gitea/workflows/ directory
|
|
# 2. Adjust the image name and scan parameters as needed
|
|
# 3. Optionally enable the SBOM attestation step
|
|
#
|
|
# Exit codes:
|
|
# 0 - Verification passed, all hashes match
|
|
# 1 - Drift detected, hashes differ
|
|
# 2 - Verification error (missing inputs, invalid manifest)
|
|
#
|
|
# =============================================================================
|
|
|
|
name: SBOM Replay Verification
|
|
|
|
on:
|
|
push:
|
|
branches: [main, develop]
|
|
pull_request:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
fail_on_drift:
|
|
description: 'Fail build if hash drift detected'
|
|
required: false
|
|
default: 'true'
|
|
type: boolean
|
|
strict_mode:
|
|
description: 'Enable strict verification mode'
|
|
required: false
|
|
default: 'false'
|
|
type: boolean
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository }}
|
|
STELLAOPS_VERSION: '1.0.0'
|
|
|
|
jobs:
|
|
build-and-scan:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
id-token: write # For OIDC-based signing
|
|
|
|
outputs:
|
|
image_digest: ${{ steps.build.outputs.digest }}
|
|
sbom_digest: ${{ steps.scan.outputs.sbom_digest }}
|
|
verdict_digest: ${{ steps.scan.outputs.verdict_digest }}
|
|
replay_manifest: ${{ steps.scan.outputs.replay_manifest }}
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log in to container registry
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata for Docker
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
tags: |
|
|
type=sha,prefix=
|
|
type=ref,event=branch
|
|
type=ref,event=pr
|
|
|
|
- name: Build and push image
|
|
id: build
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
provenance: true
|
|
sbom: false # We generate our own SBOM
|
|
|
|
- name: Install StellaOps CLI
|
|
run: |
|
|
curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }}
|
|
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
|
|
|
- name: Scan image and generate replay manifest
|
|
id: scan
|
|
env:
|
|
IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
|
run: |
|
|
# Scan image with StellaOps
|
|
stella scan \
|
|
--image "${IMAGE_REF}" \
|
|
--output-sbom sbom.json \
|
|
--output-findings findings.json \
|
|
--output-verdict verdict.json \
|
|
--format cyclonedx-1.6
|
|
|
|
# Export replay manifest for CI verification
|
|
stella replay export \
|
|
--image "${IMAGE_REF}" \
|
|
--output replay.json \
|
|
--include-feeds \
|
|
--include-reachability \
|
|
--pretty
|
|
|
|
# Extract digests for outputs
|
|
SBOM_DIGEST=$(sha256sum sbom.json | cut -d' ' -f1)
|
|
VERDICT_DIGEST=$(sha256sum verdict.json | cut -d' ' -f1)
|
|
|
|
echo "sbom_digest=sha256:${SBOM_DIGEST}" >> $GITHUB_OUTPUT
|
|
echo "verdict_digest=sha256:${VERDICT_DIGEST}" >> $GITHUB_OUTPUT
|
|
echo "replay_manifest=replay.json" >> $GITHUB_OUTPUT
|
|
|
|
# Display summary
|
|
echo "### Scan Results" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Artifact | Digest |" >> $GITHUB_STEP_SUMMARY
|
|
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Image | \`${{ steps.build.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| SBOM | \`sha256:${SBOM_DIGEST}\` |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Verdict | \`sha256:${VERDICT_DIGEST}\` |" >> $GITHUB_STEP_SUMMARY
|
|
|
|
- name: Upload scan artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: scan-artifacts-${{ github.sha }}
|
|
path: |
|
|
sbom.json
|
|
findings.json
|
|
verdict.json
|
|
replay.json
|
|
retention-days: 30
|
|
|
|
verify-determinism:
|
|
runs-on: ubuntu-latest
|
|
needs: build-and-scan
|
|
|
|
steps:
|
|
- name: Download scan artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: scan-artifacts-${{ github.sha }}
|
|
|
|
- name: Install StellaOps CLI
|
|
run: |
|
|
curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }}
|
|
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
|
|
|
- name: Verify SBOM determinism
|
|
id: verify
|
|
env:
|
|
FAIL_ON_DRIFT: ${{ inputs.fail_on_drift || 'true' }}
|
|
STRICT_MODE: ${{ inputs.strict_mode || 'false' }}
|
|
run: |
|
|
# Build verification flags
|
|
VERIFY_FLAGS="--manifest replay.json"
|
|
if [ "${FAIL_ON_DRIFT}" = "true" ]; then
|
|
VERIFY_FLAGS="${VERIFY_FLAGS} --fail-on-drift"
|
|
fi
|
|
if [ "${STRICT_MODE}" = "true" ]; then
|
|
VERIFY_FLAGS="${VERIFY_FLAGS} --strict-mode"
|
|
fi
|
|
|
|
# Run verification
|
|
stella replay export verify ${VERIFY_FLAGS}
|
|
EXIT_CODE=$?
|
|
|
|
# Report results
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|
echo "✅ Verification passed - all hashes match" >> $GITHUB_STEP_SUMMARY
|
|
echo "status=success" >> $GITHUB_OUTPUT
|
|
elif [ $EXIT_CODE -eq 1 ]; then
|
|
echo "⚠️ Drift detected - hashes differ from expected" >> $GITHUB_STEP_SUMMARY
|
|
echo "status=drift" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "❌ Verification error" >> $GITHUB_STEP_SUMMARY
|
|
echo "status=error" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
exit $EXIT_CODE
|
|
|
|
- name: Comment on PR (on drift)
|
|
if: failure() && github.event_name == 'pull_request'
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
github.rest.issues.createComment({
|
|
issue_number: context.issue.number,
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
body: `## ⚠️ SBOM Determinism Check Failed
|
|
|
|
Hash drift detected between scan runs. This may indicate non-deterministic build or scan behavior.
|
|
|
|
**Expected digests:**
|
|
- SBOM: \`${{ needs.build-and-scan.outputs.sbom_digest }}\`
|
|
- Verdict: \`${{ needs.build-and-scan.outputs.verdict_digest }}\`
|
|
|
|
**Possible causes:**
|
|
- Non-deterministic build artifacts (timestamps, random values)
|
|
- Changed dependencies between runs
|
|
- Environment differences
|
|
|
|
**Next steps:**
|
|
1. Review the replay manifest in the artifacts
|
|
2. Check build logs for non-deterministic elements
|
|
3. Consider using \`--strict-mode\` for detailed drift analysis`
|
|
})
|
|
|
|
# Optional: Attest SBOM to OCI registry
|
|
attest-sbom:
|
|
runs-on: ubuntu-latest
|
|
needs: [build-and-scan, verify-determinism]
|
|
if: github.event_name != 'pull_request' && success()
|
|
permissions:
|
|
packages: write
|
|
id-token: write
|
|
|
|
steps:
|
|
- name: Download scan artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: scan-artifacts-${{ github.sha }}
|
|
|
|
- name: Install StellaOps CLI
|
|
run: |
|
|
curl -sSfL https://stellaops.io/install.sh | sh -s -- -v ${{ env.STELLAOPS_VERSION }}
|
|
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
|
|
|
|
- name: Log in to container registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Attach SBOM attestation
|
|
env:
|
|
IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-and-scan.outputs.image_digest }}
|
|
run: |
|
|
# Sign and attach SBOM as in-toto attestation
|
|
stella attest attach \
|
|
--image "${IMAGE_REF}" \
|
|
--sbom sbom.json \
|
|
--predicate-type https://cyclonedx.org/bom/v1.6 \
|
|
--sign keyless
|
|
|
|
echo "### SBOM Attestation" >> $GITHUB_STEP_SUMMARY
|
|
echo "SBOM attached to \`${IMAGE_REF}\`" >> $GITHUB_STEP_SUMMARY
|