178 lines
6.2 KiB
Markdown
178 lines
6.2 KiB
Markdown
# 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
|