# CI SBOM Attestation Pipeline Guide ## Overview This guide explains how to integrate Stella Ops' SBOM canonicalization, DSSE attestation, and VEX mapping checks into your CI/CD pipeline using Gitea Actions workflow templates. The pipeline consists of three stages: 1. **SBOM Canonicalization** - Validate schema and compute deterministic canonical_id 2. **DSSE Attest + Verify** - Sign attestation and verify transparency log inclusion 3. **VEX Mapping** - Validate VEX documents and verify artifact targeting ## Prerequisites - Gitea Actions runner with the Stella Ops CI image (`devops/docker/Dockerfile.ci`) - Tools: `sbom-utility`, `cosign`, `rekor-cli`, `python3`, `ajv-cli` (all included in CI image) - For keyless signing: OIDC token provider (Gitea Actions built-in) - For keyed signing: `COSIGN_PRIVATE_KEY_B64` secret configured ## Quick Start ### Stage 1: SBOM Canonicalization Check Add to your workflow: ```yaml jobs: sbom-check: uses: ./.gitea/workflows/templates/sbom-canonicalization-check.yml with: bom_path: sbom.json # Optional: pin to a known canonical_id for regression testing # expected_canonical_id: 'sha256:abc123...' ``` **What it does:** - Validates the SBOM against the CycloneDX 1.7 JSON schema - Computes `canonical_id := sha256(JCS(sbom.json))` per RFC 8785 - Verifies canonicalization is deterministic (computes twice, asserts match) - Optionally checks against a known `expected_canonical_id` for regression **Outputs:** - `canonical_id` - The computed canonical identifier (e.g., `sha256:abc123...`) - `validation_result` - Schema validation result (`pass` or `fail`) ### Stage 2: DSSE Attestation + Verification ```yaml jobs: attest: needs: sbom-check uses: ./.gitea/workflows/templates/dsse-attest-verify-check.yml with: subject_ref: 'ghcr.io/org/repo@sha256:...' predicate_path: sbom.json signing_mode: keyless # or 'key' predicate_type: 'https://cyclonedx.org/bom' # skip_rekor: true # For air-gapped environments ``` **What it does:** - Signs the SBOM as a DSSE/in-toto attestation using cosign - Verifies the attestation signature - Validates Rekor transparency log inclusion proof **Signing Modes:** | Mode | Description | When to Use | |------|-------------|-------------| | `keyless` | Fulcio/OIDC ephemeral certificate | CI runners with OIDC (default) | | `key` | Cosign key pair (PEM) | Air-gapped, self-managed keys | ### Stage 3: VEX Mapping Check ```yaml jobs: vex-check: needs: sbom-check uses: ./.gitea/workflows/templates/vex-mapping-check.yml with: vex_path: vex.json vex_format: openvex # or 'cyclonedx' canonical_id: ${{ needs.sbom-check.outputs.canonical_id }} ``` **What it does:** - Validates the VEX document against its schema (OpenVEX or CycloneDX VEX) - Asserts required fields: `status`, `vulnerability`, `product` - Verifies the VEX targets match the expected `canonical_id` ## Complete Pipeline Example ```yaml name: SBOM Evidence Pipeline on: push: branches: [main] pull_request: jobs: build-and-scan: runs-on: ubuntu-latest outputs: sbom_path: sbom.json image_ref: ${{ steps.build.outputs.image_ref }} steps: - uses: actions/checkout@v4 - name: Build image id: build run: | # Build your container image docker build -t myapp . echo "image_ref=ghcr.io/org/myapp@sha256:..." >> $GITHUB_OUTPUT - name: Generate SBOM run: stella scan --image myapp --output-sbom sbom.json --format cyclonedx-1.7 - uses: actions/upload-artifact@v4 with: name: evidence path: sbom.json canonicalize: needs: build-and-scan uses: ./.gitea/workflows/templates/sbom-canonicalization-check.yml with: bom_path: sbom.json attest: needs: [build-and-scan, canonicalize] if: github.event_name != 'pull_request' uses: ./.gitea/workflows/templates/dsse-attest-verify-check.yml with: subject_ref: ${{ needs.build-and-scan.outputs.image_ref }} predicate_path: sbom.json signing_mode: keyless vex-check: needs: canonicalize if: hashFiles('vex.json') != '' uses: ./.gitea/workflows/templates/vex-mapping-check.yml with: vex_path: vex.json vex_format: openvex canonical_id: ${{ needs.canonicalize.outputs.canonical_id }} ``` ## Air-Gap Mode For environments without internet access: 1. **Skip Rekor**: Set `skip_rekor: true` in the attestation workflow 2. **Use keyed signing**: Pre-distribute cosign keys, use `signing_mode: key` 3. **Bundle schemas locally**: Schemas are already in `docs/schemas/` (no network fetch) 4. **Verification**: Use bundled checkpoints for offline Rekor verification: ```bash stella attest verify --offline --checkpoint-bundle /path/to/checkpoints ``` ## Failure Modes | Assertion | Failure | What to Do | |-----------|---------|------------| | Schema validation | Invalid CycloneDX | Check SBOM generator version; validate against `docs/schemas/cyclonedx-bom-1.7.schema.json` | | Determinism check | Hash differs between runs | Non-deterministic SBOM generation; check for timestamps, random values, unstable ordering | | Regression check | canonical_id changed | SBOM content changed; update `expected_canonical_id` or investigate drift | | Attestation signing | Cosign error | Check signing key/OIDC token; verify registry access | | Attestation verification | Signature invalid | Key mismatch or tampered attestation; re-sign | | Rekor proof | Entry not found | May be pending; retry after 30s; check Rekor connectivity | | VEX schema | Invalid document | Check VEX format matches `vex_format` input; validate manually | | VEX field assertions | Missing status | VEX statements must have `status` field; check VEX generator | | Target mismatch | canonical_id not in VEX | VEX document targets different artifact; verify PURL/product matching | ## Related Documentation - Contract: `docs/contracts/canonical-sbom-id-v1.md` - Canonical ID computation rules - Contract: `docs/contracts/artifact-canonical-record-v1.md` - Unified evidence record - Module: `docs/modules/attestor/architecture.md` - Attestor DSSE pipeline - Module: `docs/modules/signer/architecture.md` - Signing modes and configuration