save progress
This commit is contained in:
429
docs/flows/03-sbom-generation-flow.md
Normal file
429
docs/flows/03-sbom-generation-flow.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user