Files
git.stella-ops.org/docs/guides/ci-sbom-attestation-pipeline.md
2026-02-19 22:07:11 +02:00

6.2 KiB

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:

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

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

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

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:
    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
  • 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