430 lines
17 KiB
Markdown
430 lines
17 KiB
Markdown
# SBOM Generation Flow
|
|
|
|
## Overview
|
|
|
|
The SBOM Generation Flow describes how StellaOps creates Software Bills of Materials for container images using its 11 language-specific analyzers. The flow covers layer extraction, dependency detection, transitive resolution, and final SBOM assembly in SPDX 3.0.1 or CycloneDX 1.6 format.
|
|
|
|
**Business Value**: Complete, accurate SBOMs enable precise vulnerability matching and regulatory compliance (Executive Order 14028, EU CRA).
|
|
|
|
## Actors
|
|
|
|
| Actor | Type | Role |
|
|
|-------|------|------|
|
|
| Scanner | Service | Orchestrates SBOM generation |
|
|
| Layer Extractor | Component | Unpacks OCI layers |
|
|
| OS Detector | Component | Identifies base operating system |
|
|
| Language Analyzers (11) | Components | Detect ecosystem-specific dependencies |
|
|
| SBOM Assembler | Component | Merges results into final SBOM |
|
|
| Attestor | Service | Signs SBOM with DSSE envelope |
|
|
|
|
## Prerequisites
|
|
|
|
- Container image accessible (registry or local)
|
|
- Registry credentials if private registry
|
|
- Analyzer plugins enabled for target ecosystems
|
|
|
|
## Language Analyzers
|
|
|
|
StellaOps includes 11 specialized analyzers:
|
|
|
|
| Analyzer | Ecosystems | Lock Files | Manifests |
|
|
|----------|------------|------------|-----------|
|
|
| **DotNet** | NuGet, .NET | `packages.lock.json`, `*.deps.json` | `*.csproj`, `*.fsproj`, `Directory.Packages.props` |
|
|
| **Java** | Maven, Gradle | `pom.xml`, `build.gradle.kts` | `gradle.lockfile`, `maven-dependency-tree.txt` |
|
|
| **Node** | npm, yarn, pnpm | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` | `package.json` |
|
|
| **Python** | pip, Poetry, Pipenv | `requirements.txt`, `poetry.lock`, `Pipfile.lock` | `pyproject.toml`, `setup.py` |
|
|
| **Go** | Go Modules | `go.sum` | `go.mod` |
|
|
| **Rust** | Cargo | `Cargo.lock` | `Cargo.toml` |
|
|
| **PHP** | Composer | `composer.lock` | `composer.json` |
|
|
| **Ruby** | Bundler | `Gemfile.lock` | `Gemfile` |
|
|
| **Deno** | Deno, JSR | `deno.lock` | `deno.json`, `import_map.json` |
|
|
| **Bun** | Bun | `bun.lockb` | `package.json` |
|
|
| **Binary** | Native ELF/PE/Mach-O | N/A | Symbol tables, build IDs |
|
|
|
|
## Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
|
│ SBOM Generation Flow │
|
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────┐ ┌───────────┐ ┌────────────┐ ┌───────────────────────────────┐
|
|
│ Scanner │ │ Layer │ │ OS │ │ Language Analyzers │
|
|
│ │ │ Extractor │ │ Detector │ │ (DotNet, Java, Node, etc.) │
|
|
└────┬────┘ └─────┬─────┘ └──────┬─────┘ └───────────────┬───────────────┘
|
|
│ │ │ │
|
|
│ Fetch image │ │ │
|
|
│ manifest │ │ │
|
|
│──────┐ │ │ │
|
|
│ │ │ │ │
|
|
│<─────┘ │ │ │
|
|
│ │ │ │
|
|
│ Extract │ │ │
|
|
│ layers │ │ │
|
|
│─────────────>│ │ │
|
|
│ │ │ │
|
|
│ │ Unpack to │ │
|
|
│ │ work dir │ │
|
|
│ │───────┐ │ │
|
|
│ │ │ │ │
|
|
│ │<──────┘ │ │
|
|
│ │ │ │
|
|
│ │ Layers ready │ │
|
|
│<─────────────│ │ │
|
|
│ │ │ │
|
|
│ Detect OS │ │ │
|
|
│──────────────────────────────>│ │
|
|
│ │ │ │
|
|
│ │ │ Parse /etc/os-release │
|
|
│ │ │ Check package manager │
|
|
│ │ │──────────┐ │
|
|
│ │ │ │ │
|
|
│ │ │<─────────┘ │
|
|
│ │ │ │
|
|
│ OS: Alpine 3.19 │ │
|
|
│<──────────────────────────────│ │
|
|
│ │ │ │
|
|
│ Fan-out to │ │ │
|
|
│ all analyzers│ │ │
|
|
│────────────────────────────────────────────────────────>│
|
|
│ │ │ │
|
|
│ │ │ │ ┌─────────┐
|
|
│ │ │ │ │ DotNet │
|
|
│ │ │ │ └────┬────┘
|
|
│ │ │ │ │
|
|
│ │ │ Scan *.csproj
|
|
│ │ │ Parse deps.json
|
|
│ │ │ │<─────┘
|
|
│ │ │ │
|
|
│ │ │ │ ┌─────────┐
|
|
│ │ │ │ │ Java │
|
|
│ │ │ │ └────┬────┘
|
|
│ │ │ │ │
|
|
│ │ │ Parse pom.xml
|
|
│ │ │ Run gradle deps
|
|
│ │ │ │<─────┘
|
|
│ │ │ │
|
|
│ │ │ │ ┌─────────┐
|
|
│ │ │ │ │ Node │
|
|
│ │ │ │ └────┬────┘
|
|
│ │ │ │ │
|
|
│ │ │ Parse lockfiles
|
|
│ │ │ Build dep tree
|
|
│ │ │ │<─────┘
|
|
│ │ │ │
|
|
│ │ │ ... (8 more analyzers)
|
|
│ │ │ │
|
|
│ Analyzer │ │ │
|
|
│ results │ │ │
|
|
│<────────────────────────────────────────────────────────│
|
|
│ │ │ │
|
|
│ ┌────────────────┐ │ │
|
|
│ │ SBOM Assembler │ │ │
|
|
│ └───────┬────────┘ │ │
|
|
│ │ │ │
|
|
│ Merge & │ │ │
|
|
│ dedupe │ │ │
|
|
│──────────> │ │
|
|
│ │ │ │
|
|
│ SBOM │ │ │
|
|
│ document │ │ │
|
|
│<─────────│ │ │
|
|
│ │ │ │
|
|
│ ┌─────────┐ │ │
|
|
│ │Attestor │ │ │
|
|
│ └────┬────┘ │ │
|
|
│ │ │ │
|
|
│ Sign │ │ │
|
|
│───────> │ │
|
|
│ │ │ │
|
|
│ DSSE │ │ │
|
|
│<──────│ │ │
|
|
│ │ │ │
|
|
```
|
|
|
|
## Step-by-Step
|
|
|
|
### 1. Image Manifest Fetch
|
|
|
|
Scanner retrieves OCI image manifest:
|
|
|
|
```http
|
|
GET /v2/library/nginx/manifests/1.25 HTTP/1.1
|
|
Host: registry-1.docker.io
|
|
Accept: application/vnd.oci.image.manifest.v1+json
|
|
```
|
|
|
|
Response contains layer digests:
|
|
```json
|
|
{
|
|
"schemaVersion": 2,
|
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
|
"config": {
|
|
"digest": "sha256:config123..."
|
|
},
|
|
"layers": [
|
|
{"digest": "sha256:layer1...", "size": 31456789},
|
|
{"digest": "sha256:layer2...", "size": 1234567}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 2. Layer Extraction
|
|
|
|
Layer Extractor downloads and unpacks each layer:
|
|
|
|
```
|
|
work/
|
|
├── layer-0/ # Base OS layer (Alpine, Debian, etc.)
|
|
│ ├── etc/
|
|
│ ├── lib/
|
|
│ └── usr/
|
|
├── layer-1/ # Application layer
|
|
│ ├── app/
|
|
│ │ ├── node_modules/
|
|
│ │ ├── package.json
|
|
│ │ └── package-lock.json
|
|
│ └── ...
|
|
└── merged/ # Union filesystem view
|
|
```
|
|
|
|
### 3. OS Detection
|
|
|
|
OS Detector identifies the base operating system:
|
|
|
|
| Detection Method | Files Checked |
|
|
|-----------------|---------------|
|
|
| `/etc/os-release` | `ID`, `VERSION_ID` |
|
|
| `/etc/alpine-release` | Alpine version |
|
|
| `/etc/debian_version` | Debian version |
|
|
| Package manager | `apk`, `dpkg`, `rpm` |
|
|
|
|
Result:
|
|
```json
|
|
{
|
|
"os": "alpine",
|
|
"version": "3.19",
|
|
"package_manager": "apk",
|
|
"architecture": "amd64"
|
|
}
|
|
```
|
|
|
|
### 4. Parallel Analyzer Execution
|
|
|
|
All 11 analyzers run in parallel on the merged filesystem:
|
|
|
|
#### DotNet Analyzer
|
|
```
|
|
Scanning for:
|
|
- *.csproj, *.fsproj, *.vbproj
|
|
- *.deps.json (runtime dependencies)
|
|
- packages.lock.json (NuGet lock)
|
|
- Directory.Packages.props (central management)
|
|
|
|
Output:
|
|
{
|
|
"packages": [
|
|
{"purl": "pkg:nuget/Newtonsoft.Json@13.0.3", "scope": "runtime"},
|
|
{"purl": "pkg:nuget/Microsoft.Extensions.Logging@8.0.0", "scope": "runtime"}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Java Analyzer
|
|
```
|
|
Scanning for:
|
|
- pom.xml (Maven)
|
|
- build.gradle, build.gradle.kts (Gradle)
|
|
- *.jar in lib/ directories
|
|
- MANIFEST.MF inside JARs
|
|
|
|
Output:
|
|
{
|
|
"packages": [
|
|
{"purl": "pkg:maven/com.google.guava/guava@32.1.2-jre", "scope": "compile"},
|
|
{"purl": "pkg:maven/org.slf4j/slf4j-api@2.0.9", "scope": "runtime"}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Node Analyzer
|
|
```
|
|
Scanning for:
|
|
- package.json + package-lock.json (npm)
|
|
- yarn.lock (Yarn)
|
|
- pnpm-lock.yaml (pnpm)
|
|
- node_modules/.package-lock.json
|
|
|
|
Output:
|
|
{
|
|
"packages": [
|
|
{"purl": "pkg:npm/express@4.18.2", "scope": "runtime"},
|
|
{"purl": "pkg:npm/lodash@4.17.21", "scope": "runtime"}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 5. Transitive Resolution
|
|
|
|
Each analyzer resolves transitive dependencies:
|
|
|
|
```
|
|
express@4.18.2
|
|
├── accepts@1.3.8
|
|
│ ├── mime-types@2.1.35
|
|
│ │ └── mime-db@1.52.0
|
|
│ └── negotiator@0.6.3
|
|
├── body-parser@1.20.1
|
|
│ ├── bytes@3.1.2
|
|
│ └── ...
|
|
└── ...
|
|
```
|
|
|
|
### 6. SBOM Assembly
|
|
|
|
SBOM Assembler merges all analyzer results:
|
|
|
|
1. **Deduplication**: Remove duplicate PURLs across analyzers
|
|
2. **Relationship mapping**: Build component dependency graph
|
|
3. **Metadata enrichment**: Add licenses, hashes, supplier info
|
|
4. **Format conversion**: Output as SPDX 3.0.1 or CycloneDX 1.6
|
|
|
|
```json
|
|
{
|
|
"$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
|
|
"bomFormat": "CycloneDX",
|
|
"specVersion": "1.6",
|
|
"version": 1,
|
|
"metadata": {
|
|
"timestamp": "2024-12-29T10:30:00Z",
|
|
"tools": [{"name": "stellaops-scanner", "version": "2.1.0"}],
|
|
"component": {
|
|
"type": "container",
|
|
"name": "docker.io/library/nginx",
|
|
"version": "1.25"
|
|
}
|
|
},
|
|
"components": [
|
|
{
|
|
"type": "library",
|
|
"bom-ref": "pkg:npm/express@4.18.2",
|
|
"name": "express",
|
|
"version": "4.18.2",
|
|
"purl": "pkg:npm/express@4.18.2",
|
|
"hashes": [{"alg": "SHA-256", "content": "abc123..."}]
|
|
}
|
|
],
|
|
"dependencies": [
|
|
{
|
|
"ref": "pkg:npm/express@4.18.2",
|
|
"dependsOn": ["pkg:npm/accepts@1.3.8", "pkg:npm/body-parser@1.20.1"]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### 7. SBOM Attestation
|
|
|
|
Attestor creates DSSE envelope for the SBOM:
|
|
|
|
```json
|
|
{
|
|
"_type": "https://in-toto.io/Statement/v1",
|
|
"subject": [
|
|
{
|
|
"name": "docker.io/library/nginx",
|
|
"digest": {"sha256": "abc123..."}
|
|
}
|
|
],
|
|
"predicateType": "https://spdx.dev/Document",
|
|
"predicate": {
|
|
"sbom": "base64-encoded-sbom...",
|
|
"generator": "stellaops-scanner@2.1.0",
|
|
"timestamp": "2024-12-29T10:30:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
Signed with DSSE:
|
|
```json
|
|
{
|
|
"payloadType": "application/vnd.in-toto+json",
|
|
"payload": "base64-encoded-statement...",
|
|
"signatures": [
|
|
{
|
|
"keyid": "sha256:signer-key-fingerprint",
|
|
"sig": "base64-encoded-signature..."
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Data Contracts
|
|
|
|
### Analyzer Output Schema
|
|
|
|
```typescript
|
|
interface AnalyzerOutput {
|
|
analyzer: string;
|
|
ecosystem: string;
|
|
packages: Array<{
|
|
purl: string;
|
|
name: string;
|
|
version: string;
|
|
scope: 'runtime' | 'dev' | 'optional';
|
|
locations: string[];
|
|
hashes?: Record<string, string>;
|
|
licenses?: string[];
|
|
}>;
|
|
relationships: Array<{
|
|
parent: string;
|
|
child: string;
|
|
type: 'depends-on' | 'dev-depends-on';
|
|
}>;
|
|
}
|
|
```
|
|
|
|
### SBOM Output Formats
|
|
|
|
| Format | Schema Version | Use Case |
|
|
|--------|---------------|----------|
|
|
| CycloneDX | 1.6 | Default, rich dependency graph |
|
|
| SPDX | 3.0.1 | Regulatory compliance, legal |
|
|
| SPDX | 2.3 | Legacy compatibility |
|
|
|
|
## Error Handling
|
|
|
|
| Error | Recovery |
|
|
|-------|----------|
|
|
| Layer download failed | Retry with exponential backoff |
|
|
| Analyzer timeout | Mark analyzer as partial, continue |
|
|
| Lock file parse error | Fall back to manifest parsing |
|
|
| Invalid PURL | Log warning, skip component |
|
|
| Attestation failed | Return SBOM without attestation |
|
|
|
|
## Observability
|
|
|
|
### Metrics
|
|
|
|
| Metric | Type | Labels |
|
|
|--------|------|--------|
|
|
| `sbom_generation_duration_seconds` | Histogram | `image_size`, `layer_count` |
|
|
| `sbom_analyzer_duration_seconds` | Histogram | `analyzer` |
|
|
| `sbom_components_total` | Counter | `analyzer`, `ecosystem` |
|
|
| `sbom_size_bytes` | Histogram | `format` |
|
|
|
|
### Key Log Events
|
|
|
|
| Event | Level | Fields |
|
|
|-------|-------|--------|
|
|
| `sbom.generation.start` | INFO | `image`, `digest` |
|
|
| `sbom.analyzer.complete` | DEBUG | `analyzer`, `package_count` |
|
|
| `sbom.assembly.complete` | INFO | `total_components`, `format` |
|
|
| `sbom.attestation.signed` | INFO | `digest`, `keyid` |
|
|
|
|
## Related Flows
|
|
|
|
- [Scan Submission Flow](02-scan-submission-flow.md) - Parent flow
|
|
- [Binary Delta Attestation Flow](15-binary-delta-attestation-flow.md) - Binary-level analysis
|
|
- [Evidence Bundle Export Flow](13-evidence-bundle-export-flow.md) - SBOM packaging
|