docs consolidation, big sln build fixes, new advisories and sprints/tasks
This commit is contained in:
48
docs/modules/sbom-service/README.md
Normal file
48
docs/modules/sbom-service/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# SbomService
|
||||
|
||||
**Status:** Implemented
|
||||
**Source:** `src/SbomService/`
|
||||
**Owner:** Scanner Guild
|
||||
|
||||
## Purpose
|
||||
|
||||
SbomService provides SBOM storage, versioning, and lineage tracking. Maintains the canonical SBOM repository with support for SPDX 3.0.1 and CycloneDX 1.6 formats, including temporal queries and dependency graph analysis.
|
||||
|
||||
## Components
|
||||
|
||||
**Services:**
|
||||
- `StellaOps.SbomService` - Main SBOM service with API and business logic
|
||||
|
||||
**Libraries:**
|
||||
- `StellaOps.SbomService.Storage.Postgres` - PostgreSQL storage adapter for SBOM persistence
|
||||
- `StellaOps.SbomService.Storage.Postgres.Tests` - Storage layer integration tests
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is embedded in the service module settings.
|
||||
|
||||
Key settings:
|
||||
- PostgreSQL connection (schema: `sbom_service`)
|
||||
- Authority integration
|
||||
- SBOM format support (SPDX, CycloneDX)
|
||||
- Versioning and lineage policies
|
||||
- Retention settings
|
||||
|
||||
## Dependencies
|
||||
|
||||
- PostgreSQL (schema: `sbom_service`)
|
||||
- Authority (authentication)
|
||||
- Scanner (SBOM generation source)
|
||||
- Attestor (SBOM attestation integration)
|
||||
- ExportCenter (SBOM export and distribution)
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- Architecture: `./architecture.md`
|
||||
- Scanner: `../scanner/`
|
||||
- Attestor: `../attestor/`
|
||||
- Data Schemas: `../../11_DATA_SCHEMAS.md`
|
||||
|
||||
## Current Status
|
||||
|
||||
Implemented with PostgreSQL storage backend. Supports SBOM ingestion, versioning, and lineage tracking. Provides API for SBOM queries and temporal analysis.
|
||||
29
docs/modules/sbom-service/api/projection-read.md
Normal file
29
docs/modules/sbom-service/api/projection-read.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# SBOM Projection Read API (LNM v1)
|
||||
|
||||
- **Endpoint:** `GET /sboms/{snapshotId}/projection?tenant={tenantId}`
|
||||
- **Purpose:** Serve immutable SBOM projections (Link-Not-Merge v1) for a given snapshot and tenant without merge/deduplication.
|
||||
- **Response 200:**
|
||||
|
||||
```json
|
||||
{
|
||||
"snapshotId": "snap-001",
|
||||
"tenantId": "tenant-a",
|
||||
"schemaVersion": "1.0.0",
|
||||
"hash": "<sha256 of projection payload>",
|
||||
"projection": { /* LNM v1 projection payload */ }
|
||||
}
|
||||
```
|
||||
|
||||
- **Errors:**
|
||||
- 400 when `snapshotId` or `tenant` is missing or blank.
|
||||
- 404 when no projection exists for the given snapshot/tenant.
|
||||
|
||||
- **Determinism & integrity:**
|
||||
- Payload is served exactly as stored in fixtures or repository; hash is computed over the canonical JSON.
|
||||
- No mutation/merge logic applied.
|
||||
|
||||
- **Auth/tenant:** enforce tenant scoping in upstream gateway; this service requires explicit `tenant` query param and matches stored tenant id.
|
||||
|
||||
- **Fixtures:** `docs/modules/sbomservice/fixtures/lnm-v1/projections.json` (hashes in `SHA256SUMS`).
|
||||
|
||||
- **Metrics:** TBD in observability doc; to be added when backed by persistent store.
|
||||
168
docs/modules/sbom-service/architecture.md
Normal file
168
docs/modules/sbom-service/architecture.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# SBOM Service architecture (2025Q4)
|
||||
|
||||
> Scope: canonical SBOM projections, lookup and timeline APIs, asset metadata overlays, and events feeding Advisory AI, Console, Graph, Policy, and Vuln Explorer.
|
||||
|
||||
## 1) Mission & boundaries
|
||||
- Mission: serve deterministic, tenant-scoped SBOM projections (Link-Not-Merge v1) and related metadata for downstream reasoning and overlays.
|
||||
- Boundaries:
|
||||
- Does not perform scanning; consumes Scanner outputs or supplied SPDX/CycloneDX blobs.
|
||||
- Does not author verdicts/policy; supplies evidence and projections to Policy/Concelier/Graph.
|
||||
- Append-only SBOM versions; mutations happen via new versions, never in-place edits.
|
||||
- Owns the SBOM lineage ledger for versioned uploads, diffs, and retention pruning.
|
||||
|
||||
## 2) Project layout
|
||||
- `src/SbomService/StellaOps.SbomService` — REST API + event emitters + orchestrator integration.
|
||||
- Storage: PostgreSQL tables (proposed)
|
||||
- `sbom_snapshots` (immutable versions; tenant + artifact + digest + createdAt)
|
||||
- `sbom_projections` (materialised views keyed by snapshotId, entrypoint/service node flags)
|
||||
- `sbom_assets` (asset metadata, criticality/owner/env/exposure; append-only history)
|
||||
- `sbom_catalog` (console catalog surface; indexed by artifact, scope, license, assetTags.*, createdAt for deterministic pagination)
|
||||
- `sbom_component_neighbors` (component lookup graph edges; indexed by purl+artifact for cursor pagination)
|
||||
- `sbom_paths` (resolved dependency paths with runtime flags, blast-radius hints)
|
||||
- `sbom_events` (outbox for event delivery + watermark/backfill tracking)
|
||||
|
||||
### 2.1) SBOM + provenance spine (Nov 2026)
|
||||
|
||||
The service now owns an idempotent spine that converts OCI images into SBOMs and provenance bundles with DSSE and in-toto. The flow is intentionally air-gap ready:
|
||||
|
||||
- **Extract** OCI manifest/layers (hash becomes `contentAddress`).
|
||||
- **Build SBOM** in CycloneDX 1.7 and/or SPDX 3.0.1; canonicalize JSON before hashing (`sbomHash`).
|
||||
- **Sign** outputs as DSSE envelopes; predicate uses in-toto Statement with SLSA Provenance v1.
|
||||
- **Publish** attestations optionally to a transparency backend: `rekor`, `local-merkle`, or `null` (no-op). Local Merkle log keeps proofs for later sync when online.
|
||||
|
||||
Minimal APIs exposed by SbomService (idempotent by hash):
|
||||
|
||||
- `POST /sbom/ingest` `{ imageDigest, sbom, format, dsseSignature? }` → `{ sbomId, status: stored|already_present, sbomHash }` keyed by `contentAddress + sbomHash`.
|
||||
- `POST /attest/verify` `{ dsseEnvelope, expectedSubjects[] }` → `{ verified, predicateType, logIndex?, inclusionProof? }` and records attestation when verified.
|
||||
|
||||
Operational rules:
|
||||
|
||||
- Default media types: `application/vnd.cyclonedx+json`, `application/spdx+json`, `application/dsse+json`, `application/vnd.in-toto+json`.
|
||||
- If the same SBOM/attestation arrives again, return HTTP 200 with `"status":"already_present"` and do not create a new version.
|
||||
- Offline posture: no external calls required; Rekor publish remains optional and retryable when connectivity is restored.
|
||||
|
||||
## 3) APIs (first wave)
|
||||
- `GET /sbom/paths?purl=...&artifact=...&scope=...&env=...` — returns ordered paths with runtime_flag/blast_radius and nearest-safe-version hint; supports `cursor` pagination.
|
||||
- `GET /sbom/versions?artifact=...` – time-ordered SBOM version timeline for Advisory AI; include provenance and source bundle hash.
|
||||
- `POST /sbom/upload` – BYOS upload endpoint; validates/normalizes SPDX 2.3/3.0.1 or CycloneDX 1.4–1.7 and registers a ledger version.
|
||||
- `GET /sbom/ledger/history` – list version history for an artifact (cursor pagination).
|
||||
- `GET /sbom/ledger/point` – resolve the SBOM version at a specific timestamp.
|
||||
- `GET /sbom/ledger/range` – query versions within a time range.
|
||||
- `GET /sbom/ledger/diff` – component/version/license diff between two versions.
|
||||
- `GET /sbom/ledger/lineage` – parent/child lineage edges for an artifact chain.
|
||||
- `GET /console/sboms` – Console catalog with filters (artifact, license, scope, asset tags), cursor pagination, evaluation metadata, immutable JSON projection for drawer views.
|
||||
- `GET /components/lookup?purl=...` – component neighborhood for global search/Graph overlays; returns caches hints + tenant enforcement.
|
||||
- `POST /entrypoints` / `GET /entrypoints` – manage entrypoint/service node overrides feeding Cartographer relevance; deterministic defaults when unset.
|
||||
- `GET /sboms/{snapshotId}/projection` – Link-Not-Merge v1 projection returning hashes plus asset metadata (criticality, owner, environment, exposure flags, tags) alongside package/component graph.
|
||||
- `GET /internal/sbom/events` — internal diagnostics endpoint returning the in-memory event outbox for validation.
|
||||
- `POST /internal/sbom/events/backfill` — replays existing projections into the event stream; deterministic ordering, clock abstraction for tests.
|
||||
- `GET /internal/sbom/asset-events` — diagnostics endpoint returning emitted `sbom.asset.updated` envelopes for validation and air-gap parity checks.
|
||||
- `GET/POST /internal/orchestrator/sources` — list/register orchestrator ingest/index sources (deterministic seeds; idempotent on artifactDigest+sourceType).
|
||||
- `GET/POST /internal/orchestrator/control` — manage pause/throttle/backpressure signals per tenant; metrics emitted for control updates.
|
||||
- `GET/POST /internal/orchestrator/watermarks` — fetch/set backfill watermarks for reconciliation and deterministic replays.
|
||||
- `GET /internal/sbom/resolver-feed` – list resolver candidates (artifact, purl, version, paths, scope, runtime_flag, nearest_safe_version).
|
||||
- `POST /internal/sbom/resolver-feed/backfill` – clear and repopulate resolver feed from current projections.
|
||||
- `GET /internal/sbom/resolver-feed/export` – NDJSON export of resolver candidates for air-gap delivery.
|
||||
- `GET /internal/sbom/ledger/audit` – audit trail for ledger changes (created/pruned).
|
||||
- `GET /internal/sbom/analysis/jobs` – list analysis jobs triggered by BYOS uploads.
|
||||
- `POST /internal/sbom/retention/prune` – apply retention policy and emit audit entries.
|
||||
|
||||
## 3.1) Ledger + BYOS workflow (Sprint 4600)
|
||||
- Uploads are validated, normalized, and stored as ledger versions chained per artifact identity.
|
||||
- Diffs compare normalized component keys and surface version/license deltas with deterministic ordering.
|
||||
- Lineage is derived from parent version references and emitted for Graph lineage edges.
|
||||
- Lineage relationships include parent links plus build links (shared CI build IDs when provided).
|
||||
- Retention policy prunes old versions while preserving audit entries and minimum keep counts.
|
||||
- See `docs/modules/sbomservice/ledger-lineage.md` for request/response examples.
|
||||
- See `docs/modules/sbomservice/byos-ingestion.md` for supported formats and troubleshooting.
|
||||
|
||||
## 4) Ingestion & orchestrator integration
|
||||
- Ingest sources: Scanner pipeline (preferred) or uploaded SPDX 2.3/3.0.1 and CycloneDX 1.4–1.7 bundles.
|
||||
- Orchestrator: register SBOM ingest/index jobs; worker SDK emits artifact hash + job metadata; honor pause/throttle; report backpressure metrics; support watermark-based backfill for idempotent replays.
|
||||
- Idempotency: combine `(tenant, artifactDigest, sbomVersion)` as primary key; duplicate ingests short-circuit.
|
||||
|
||||
## 5) Events & streaming
|
||||
- `sbom.version.created` — emitted per new SBOM snapshot; payload: tenant, artifact digest, sbomVersion, projection hash, source bundle hash, import provenance; replay/backfill via outbox with watermark.
|
||||
- `sbom.asset.updated` — emitted when asset metadata changes; idempotent payload keyed by `(tenant, assetId, version)`.
|
||||
- Inventory/resolver feeds — queue/topic delivering `(artifact, purl, version, paths, runtime_flag, scope, nearest_safe_version)` for Vuln Explorer/Findings Ledger.
|
||||
- Current implementation uses an in-memory event store/publisher (with clock abstraction) plus `/internal/sbom/events` + `/internal/sbom/events/backfill` to validate envelopes until the PostgreSQL-backed outbox is wired.
|
||||
- Entrypoint/service node overrides are exposed via `/entrypoints` (tenant-scoped) and should be mirrored into Cartographer relevance jobs when the outbox lands.
|
||||
|
||||
## 6) Determinism & offline posture
|
||||
- Stable ordering for projections and paths; timestamps in UTC ISO-8601; hash inputs canonicalised.
|
||||
- Add-only evolution for schemas; LNM v1 fixtures published alongside API docs and replayable tests.
|
||||
- Offline-friendly: uses mirrored packages, avoids external calls during projection; exports NDJSON bundles for air-gapped replay.
|
||||
|
||||
## 7) Tenancy & security
|
||||
- All APIs require tenant context (token claims or mTLS binding); collection filters must include tenant keys.
|
||||
- Enforce least-privilege queries; avoid cross-tenant caches; log tenant IDs in structured logs.
|
||||
- Input validation: schema-validate incoming SBOMs; reject oversized/unsupported media types early.
|
||||
|
||||
## 8) Observability
|
||||
- Metrics: `sbom_projection_seconds`, `sbom_projection_size_bytes`, `sbom_projection_queries_total`, `sbom_paths_latency_seconds`, `sbom_paths_cache_hit_ratio`, `sbom_events_backlog`, `sbom_ledger_uploads_total`, `sbom_ledger_diffs_total`, `sbom_ledger_retention_pruned_total`.
|
||||
- Tracing: ActivitySource `StellaOps.SbomService` (entrypoints, component lookup, console catalog, projections, events).
|
||||
- Traces: wrap ingest, projection build, and API handlers; propagate orchestrator job IDs.
|
||||
- Logs: structured, include tenant + artifact digest + sbomVersion; classify ingest failures (schema, storage, orchestrator, validation).
|
||||
- Alerts: backlog thresholds for outbox/event delivery; high latency on path/timeline endpoints.
|
||||
|
||||
## 8.1) Registry Source Management (Sprint 012)
|
||||
|
||||
The service manages container registry sources for automated image discovery and scanning:
|
||||
|
||||
### Models
|
||||
- `RegistrySource` — registry connection with URL, filters, schedule, credentials (via AuthRef).
|
||||
- `RegistrySourceRun` — run history with status, discovered images, triggered scans, error details.
|
||||
- `RegistrySourceStatus` — `Draft`, `Active`, `Paused`, `Error`, `Deleted`.
|
||||
- `RegistrySourceProvider` — `Generic`, `Harbor`, `DockerHub`, `ACR`, `ECR`, `GCR`, `GHCR`.
|
||||
|
||||
### APIs
|
||||
- `GET/POST/PUT/DELETE /api/v1/registry-sources` — CRUD operations.
|
||||
- `POST /api/v1/registry-sources/{id}/test` — test registry connection and credentials.
|
||||
- `POST /api/v1/registry-sources/{id}/trigger` — manually trigger discovery and scanning.
|
||||
- `POST /api/v1/registry-sources/{id}/pause` / `/resume` — pause/resume scheduled scans.
|
||||
- `GET /api/v1/registry-sources/{id}/runs` — run history with health metrics.
|
||||
- `GET /api/v1/registry-sources/{id}/discover/repositories` — discover repositories matching filters.
|
||||
- `GET /api/v1/registry-sources/{id}/discover/tags/{repository}` — discover tags for a repository.
|
||||
- `GET /api/v1/registry-sources/{id}/discover/images` — full image discovery.
|
||||
- `POST /api/v1/registry-sources/{id}/discover-and-scan` — discover and submit scan jobs.
|
||||
|
||||
### Webhook Ingestion
|
||||
- `POST /api/v1/webhooks/registry/{sourceId}` — receive push notifications from registries.
|
||||
- Supported providers: Harbor, DockerHub, ACR, ECR, GCR, GHCR.
|
||||
- HMAC-SHA256 signature validation using webhook secret from AuthRef.
|
||||
- Auto-detection of provider from request headers.
|
||||
|
||||
### Discovery Service
|
||||
- OCI Distribution Spec compliant repository/tag enumeration.
|
||||
- Pagination via RFC 5988 Link headers.
|
||||
- Allowlist/denylist filtering for repositories and tags (glob patterns).
|
||||
- Manifest digest retrieval via HEAD requests.
|
||||
|
||||
### Scan Job Emission
|
||||
- Batch submission to Scanner service with rate limiting.
|
||||
- Deduplication (skips if job already exists).
|
||||
- Metadata includes source ID, trigger type, client request ID.
|
||||
|
||||
### Configuration
|
||||
- `SbomService:ScannerUrl` — Scanner service endpoint (default: `http://localhost:5100`).
|
||||
- `SbomService:BatchScanSize` — max images per batch (default: 10).
|
||||
- `SbomService:BatchScanDelayMs` — delay between batch submissions (default: 100ms).
|
||||
|
||||
### Credentials
|
||||
- All credentials via AuthRef URIs: `authref://{vault}/{path}#{key}`.
|
||||
- Supports basic auth (`basic:user:pass`) and bearer tokens (`bearer:token`) for development.
|
||||
|
||||
## 9) Configuration (PostgreSQL-backed catalog & lookup)
|
||||
- Enable PostgreSQL storage for `/console/sboms` and `/components/lookup` by setting `SbomService:PostgreSQL:ConnectionString` (env: `SBOM_SbomService__PostgreSQL__ConnectionString`).
|
||||
- Optional overrides: `SbomService:PostgreSQL:Schema`, `SbomService:PostgreSQL:CatalogTable`, `SbomService:PostgreSQL:ComponentLookupTable`; defaults are `sbom_service`, `sbom_catalog`, `sbom_component_neighbors`.
|
||||
- When the connection string is absent the service falls back to fixture JSON or deterministic in-memory seeds to keep air-gapped workflows alive.
|
||||
- Ledger retention settings (env prefix `SBOM_SbomService__Ledger__`): `MaxVersionsPerArtifact`, `MaxAgeDays`, `MinVersionsToKeep`.
|
||||
|
||||
## 10) Open questions / dependencies
|
||||
- Confirm orchestrator pause/backfill contract (shared with Runtime & Signals 140-series).
|
||||
- Finalise storage table names and indexes (compound on tenant+artifactDigest+version, TTL for transient staging).
|
||||
- Publish canonical LNM v1 fixtures and JSON schemas for projections and asset metadata.
|
||||
|
||||
- See `docs/modules/sbomservice/api/projection-read.md` for `/sboms/{snapshotId}/projection` (LNM v1, tenant-scoped, hash-returning).
|
||||
- See `docs/modules/sbomservice/lineage-ledger.md` for ledger endpoints and lineage relationships.
|
||||
- See `docs/modules/sbomservice/retention-policy.md` for retention configuration and audit expectations.
|
||||
33
docs/modules/sbom-service/byos-ingestion.md
Normal file
33
docs/modules/sbom-service/byos-ingestion.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# BYOS SBOM Ingestion
|
||||
|
||||
## Overview
|
||||
Bring-your-own SBOM (BYOS) uploads accept SPDX and CycloneDX JSON and register them in the SBOM ledger for analysis.
|
||||
|
||||
## Supported formats
|
||||
- CycloneDX JSON: 1.4, 1.5, 1.6
|
||||
- SPDX JSON: 2.3, 3.0
|
||||
|
||||
## Upload endpoint
|
||||
- `POST /sbom/upload` or `POST /api/v1/sbom/upload`
|
||||
- Required: `artifactRef`, plus `sbom` (JSON object) or `sbomBase64`.
|
||||
- Optional: `format` hint (`cyclonedx` or `spdx`) and `source` metadata.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
"artifactRef": "acme/app:2.0",
|
||||
"sbom": { "spdxVersion": "SPDX-2.3", "packages": [] },
|
||||
"source": { "tool": "syft", "version": "1.9.0" }
|
||||
}
|
||||
```
|
||||
|
||||
## Validation notes
|
||||
- CycloneDX requires `bomFormat` and supported `specVersion`.
|
||||
- SPDX requires `spdxVersion` and a supported version number.
|
||||
- Quality scoring prefers components with PURL, version, and license metadata.
|
||||
|
||||
## Troubleshooting
|
||||
- **"sbom or sbomBase64 is required"**: include an SBOM payload in the request.
|
||||
- **"Unable to detect SBOM format"**: set `format` explicitly or include required root fields.
|
||||
- **Unsupported SBOM format/version**: ensure CycloneDX 1.4–1.7 or SPDX 2.3/3.0.
|
||||
- **Low quality scores**: include PURLs, versions, and license declarations where possible.
|
||||
21
docs/modules/sbom-service/fixtures/lnm-v1/README.md
Normal file
21
docs/modules/sbom-service/fixtures/lnm-v1/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Link-Not-Merge v1 Fixtures
|
||||
|
||||
Status: Awaiting drop (2025-11-22)
|
||||
|
||||
Expected contents (all JSON, canonicalized, UTF-8):
|
||||
- `projections.json` — canonical SBOM projection payloads keyed by snapshot ID.
|
||||
- `assets.json` — asset metadata overlays (tenant-scoped, append-only).
|
||||
- `paths.json` — ordered dependency paths with runtime flags and blast-radius hints.
|
||||
- `events.json` — `sbom.version.created` envelopes aligned to CAS/provenance fields.
|
||||
- `schema-version.txt` — git SHA / semantic version of the frozen projection schema.
|
||||
- `SHA256SUMS` — checksums for all files above.
|
||||
|
||||
Drop instructions:
|
||||
- Place files in this directory and update `SHA256SUMS` via `sha256sum *.json *.txt > SHA256SUMS`.
|
||||
- Keep ordering stable; prefer NDJSON converted to JSON arrays only if deterministic sorting is applied.
|
||||
- Record drop commit in sprint 0140/0142 Execution Logs and link here.
|
||||
|
||||
Consumers:
|
||||
- SBOM-SERVICE-21-001..004 implementation and tests.
|
||||
- Advisory AI and Console replay suites.
|
||||
- AirGap parity review (`docs/modules/sbomservice/runbooks/airgap-parity-review.md`).
|
||||
2
docs/modules/sbom-service/fixtures/lnm-v1/SHA256SUMS
Normal file
2
docs/modules/sbom-service/fixtures/lnm-v1/SHA256SUMS
Normal file
@@ -0,0 +1,2 @@
|
||||
# SHA256 hashes for LNM v1 fixtures (recorded 2025-11-23)
|
||||
docs/modules/sbomservice/fixtures/lnm-v1/projections.json a469347019b0cf8d07ded0adce2b1590bbb089e3b306e7a7195b94341aeef18d
|
||||
47
docs/modules/sbom-service/fixtures/lnm-v1/catalog.json
Normal file
47
docs/modules/sbom-service/fixtures/lnm-v1/catalog.json
Normal file
@@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-api",
|
||||
"sbomVersion": "2025.11.16.1",
|
||||
"digest": "sha256:112",
|
||||
"license": "MIT",
|
||||
"scope": "runtime",
|
||||
"assetTags": {
|
||||
"owner": "payments",
|
||||
"criticality": "high",
|
||||
"env": "prod"
|
||||
},
|
||||
"createdAt": "2025-11-16T12:00:00Z",
|
||||
"projectionHash": "sha256:proj112",
|
||||
"evaluationMetadata": "eval:passed:v1"
|
||||
},
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-api",
|
||||
"sbomVersion": "2025.11.15.1",
|
||||
"digest": "sha256:111",
|
||||
"license": "MIT",
|
||||
"scope": "runtime",
|
||||
"assetTags": {
|
||||
"owner": "payments",
|
||||
"criticality": "high",
|
||||
"env": "prod"
|
||||
},
|
||||
"createdAt": "2025-11-15T12:00:00Z",
|
||||
"projectionHash": "sha256:proj111",
|
||||
"evaluationMetadata": "eval:passed:v1"
|
||||
},
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-worker",
|
||||
"sbomVersion": "2025.11.12.0",
|
||||
"digest": "sha256:222",
|
||||
"license": "Apache-2.0",
|
||||
"scope": "runtime",
|
||||
"assetTags": {
|
||||
"owner": "platform",
|
||||
"criticality": "medium",
|
||||
"env": "staging"
|
||||
},
|
||||
"createdAt": "2025-11-12T08:00:00Z",
|
||||
"projectionHash": "sha256:proj222",
|
||||
"evaluationMetadata": "eval:pending:v1"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
[
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-api",
|
||||
"purl": "pkg:npm/lodash@4.17.21",
|
||||
"neighborPurl": "pkg:npm/express@4.18.2",
|
||||
"relationship": "DEPENDS_ON",
|
||||
"license": "MIT",
|
||||
"scope": "runtime",
|
||||
"runtimeFlag": true
|
||||
},
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-api",
|
||||
"purl": "pkg:npm/lodash@4.17.21",
|
||||
"neighborPurl": "pkg:npm/rollup@3.0.0",
|
||||
"relationship": "DEPENDS_ON",
|
||||
"license": "MIT",
|
||||
"scope": "build",
|
||||
"runtimeFlag": false
|
||||
},
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-api",
|
||||
"purl": "pkg:npm/lodash@4.17.21",
|
||||
"neighborPurl": "pkg:npm/react@18.2.0",
|
||||
"relationship": "DEPENDS_ON",
|
||||
"license": "MIT",
|
||||
"scope": "runtime",
|
||||
"runtimeFlag": true
|
||||
},
|
||||
{
|
||||
"artifact": "ghcr.io/stellaops/sample-worker",
|
||||
"purl": "pkg:nuget/Newtonsoft.Json@13.0.2",
|
||||
"neighborPurl": "pkg:nuget/StellaOps.Core@1.0.0",
|
||||
"relationship": "DEPENDS_ON",
|
||||
"license": "Apache-2.0",
|
||||
"scope": "runtime",
|
||||
"runtimeFlag": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1 @@
|
||||
[{"snapshotId":"snap-001","tenantId":"tenant-a","projection":{"purl":"pkg:npm/lodash@4.17.21","paths":[],"metadata":{"schemaVersion":"1.0.0","asset":{"criticality":"high","owner":"team-console","environment":"prod","exposure":["internet","pci"],"tags":{"tier":"1","service":"sample-api"}}}}}]
|
||||
41
docs/modules/sbom-service/ledger-lineage.md
Normal file
41
docs/modules/sbom-service/ledger-lineage.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# SBOM Lineage Ledger Guide
|
||||
|
||||
## Purpose
|
||||
- Track historical SBOM versions per artifact with deterministic diffs and lineage edges.
|
||||
- Support BYOS uploads for SPDX/CycloneDX inputs while preserving audit history.
|
||||
|
||||
## Upload workflow
|
||||
- Endpoint: `POST /sbom/upload` or `POST /api/v1/sbom/upload`.
|
||||
- Required fields: `artifactRef`, `sbom` (JSON object) or `sbomBase64`.
|
||||
|
||||
Example payload:
|
||||
```json
|
||||
{
|
||||
"artifactRef": "acme/app:1.0",
|
||||
"sbom": { "bomFormat": "CycloneDX", "specVersion": "1.6", "components": [] },
|
||||
"format": "cyclonedx",
|
||||
"source": { "tool": "syft", "version": "1.0.0" }
|
||||
}
|
||||
```
|
||||
|
||||
## Ledger queries
|
||||
- `GET /sbom/ledger/history?artifact=...&limit=...&cursor=...`
|
||||
- `GET /sbom/ledger/point?artifact=...&at=...`
|
||||
- `GET /sbom/ledger/range?artifact=...&start=...&end=...&limit=...&cursor=...`
|
||||
- `GET /sbom/ledger/diff?before=...&after=...`
|
||||
- `GET /sbom/ledger/lineage?artifact=...`
|
||||
|
||||
## Lineage semantics
|
||||
- Versions are chained per artifact; parent references create lineage edges.
|
||||
- Graph Indexer maps ledger lineage into:
|
||||
- `SBOM_VERSION_OF` edges from SBOM to artifact.
|
||||
- `SBOM_LINEAGE_*` edges (e.g., `SBOM_LINEAGE_PARENT`).
|
||||
|
||||
## Retention policy
|
||||
- Apply with `POST /internal/sbom/retention/prune`.
|
||||
- Settings: `SbomService:Ledger:MaxVersionsPerArtifact`, `MaxAgeDays`, `MinVersionsToKeep`.
|
||||
- Audit is available via `GET /internal/sbom/ledger/audit`.
|
||||
|
||||
## Determinism
|
||||
- Versions are ordered by sequence and UTC timestamps.
|
||||
- Diffs are ordered by component key for stable output.
|
||||
30
docs/modules/sbom-service/lineage-ledger.md
Normal file
30
docs/modules/sbom-service/lineage-ledger.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# SBOM lineage ledger
|
||||
|
||||
## Overview
|
||||
- Tracks immutable SBOM versions per artifact reference.
|
||||
- Exposes history, temporal queries, and deterministic diffs.
|
||||
- Emits lineage edges to support graph joins and audit trails.
|
||||
|
||||
## Endpoints
|
||||
- `GET /sbom/ledger/history?artifact=<ref>&limit=50&cursor=0`
|
||||
- `GET /sbom/ledger/point?artifact=<ref>&at=<iso8601>`
|
||||
- `GET /sbom/ledger/range?artifact=<ref>&start=<iso8601>&end=<iso8601>`
|
||||
- `GET /sbom/ledger/diff?before=<versionId>&after=<versionId>`
|
||||
- `GET /sbom/ledger/lineage?artifact=<ref>`
|
||||
|
||||
## Lineage relationships
|
||||
- `parent`: explicit parent version link (supplied at ingest).
|
||||
- `build`: versions emitted from the same CI build ID (from upload provenance).
|
||||
|
||||
## Example lineage response
|
||||
```json
|
||||
{
|
||||
"artifactRef": "example.com/app:1.2.3",
|
||||
"nodes": [{ "versionId": "v1", "sequenceNumber": 1, "digest": "sha256:..." }],
|
||||
"edges": [{ "fromVersionId": "v1", "toVersionId": "v2", "relationship": "build" }]
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
- Ledger storage is in-memory until PostgreSQL-backed persistence is wired.
|
||||
- Ordering is deterministic by sequence number, then timestamp.
|
||||
469
docs/modules/sbom-service/lineage/architecture.md
Normal file
469
docs/modules/sbom-service/lineage/architecture.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# SBOM Lineage Graph Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The SBOM Lineage Graph provides a Git-like visualization of container image ancestry with hover-to-proof micro-interactions. It enables auditors and developers to explore SBOM/VEX deltas across artifact versions, turning evidence into an explorable UX.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Lineage Graph
|
||||
|
||||
A directed acyclic graph (DAG) where:
|
||||
- **Nodes** represent artifact versions (SBOM snapshots)
|
||||
- **Edges** represent relationships between versions
|
||||
|
||||
### Edge Types
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `parent` | Direct version succession | v1.0 → v1.1 of same image |
|
||||
| `build` | Same CI build produced multiple artifacts | Multi-arch build |
|
||||
| `base` | Derived from base image | `FROM alpine:3.19` |
|
||||
|
||||
### Node Attributes
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Node: sha256:abc123... │
|
||||
├─────────────────────────────────────┤
|
||||
│ Artifact: registry/app:v1.2 │
|
||||
│ Sequence: 42 │
|
||||
│ Created: 2025-12-28T10:30:00Z │
|
||||
│ Source: scanner │
|
||||
├─────────────────────────────────────┤
|
||||
│ Badges: │
|
||||
│ • 3 new vulns (🔴) │
|
||||
│ • 2 resolved (🟢) │
|
||||
│ • signature ✓ │
|
||||
├─────────────────────────────────────┤
|
||||
│ Replay Hash: sha256:def456... │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌─────────────────┐ ┌──────────────────┐
|
||||
│ Scanner │────▶│ SbomService │────▶│ VexLens │
|
||||
│ │ │ │ │ │
|
||||
│ • OCI Parse │ │ • Ledger Store │ │ • Consensus │
|
||||
│ • Ancestry │ │ • Edge Persist │ │ • Delta Compute │
|
||||
│ • SBOM Gen │ │ • Diff Engine │ │ • Status Track │
|
||||
└──────────────┘ └─────────────────┘ └──────────────────┘
|
||||
│ │ │
|
||||
└────────────────────┼───────────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Lineage API │
|
||||
│ │
|
||||
│ • Graph Query │
|
||||
│ • Diff Compute │
|
||||
│ • Export Pack │
|
||||
└─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Frontend UI │
|
||||
│ │
|
||||
│ • Lane View │
|
||||
│ • Hover Cards │
|
||||
│ • Compare Mode │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### 1. OCI Ancestry Extractor (Scanner)
|
||||
|
||||
Extracts parent/base image information from OCI manifests.
|
||||
|
||||
```csharp
|
||||
public interface IOciAncestryExtractor
|
||||
{
|
||||
ValueTask<OciAncestry> ExtractAncestryAsync(
|
||||
string imageReference,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record OciAncestry(
|
||||
string ImageDigest,
|
||||
string? BaseImageDigest,
|
||||
string? BaseImageRef,
|
||||
IReadOnlyList<string> LayerDigests,
|
||||
IReadOnlyList<OciHistoryEntry> History);
|
||||
|
||||
public sealed record OciHistoryEntry(
|
||||
string CreatedBy,
|
||||
DateTimeOffset Created,
|
||||
bool EmptyLayer);
|
||||
```
|
||||
|
||||
**Implementation Notes:**
|
||||
- Parse OCI image config `history` field
|
||||
- Extract `FROM` instruction from first non-empty layer
|
||||
- Handle multi-stage builds by tracking layer boundaries
|
||||
- Fall back to layer digest heuristics when history unavailable
|
||||
|
||||
### 2. Lineage Edge Repository (SbomService)
|
||||
|
||||
Persists relationships between artifact versions.
|
||||
|
||||
```csharp
|
||||
public interface ISbomLineageEdgeRepository
|
||||
{
|
||||
ValueTask<LineageEdge> AddAsync(
|
||||
LineageEdge edge,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<LineageEdge>> GetChildrenAsync(
|
||||
string parentDigest,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<LineageEdge>> GetParentsAsync(
|
||||
string childDigest,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<LineageGraph> GetGraphAsync(
|
||||
string artifactDigest,
|
||||
Guid tenantId,
|
||||
int maxDepth,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record LineageEdge(
|
||||
Guid Id,
|
||||
string ParentDigest,
|
||||
string ChildDigest,
|
||||
LineageRelationship Relationship,
|
||||
Guid TenantId,
|
||||
DateTimeOffset CreatedAt);
|
||||
|
||||
public enum LineageRelationship
|
||||
{
|
||||
Parent,
|
||||
Build,
|
||||
Base
|
||||
}
|
||||
```
|
||||
|
||||
### 3. VEX Delta Repository (Excititor)
|
||||
|
||||
Tracks VEX status changes between artifact versions.
|
||||
|
||||
```csharp
|
||||
public interface IVexDeltaRepository
|
||||
{
|
||||
ValueTask<VexDelta> AddAsync(
|
||||
VexDelta delta,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<VexDelta>> GetDeltasAsync(
|
||||
string fromDigest,
|
||||
string toDigest,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<VexDelta>> GetDeltasByCveAsync(
|
||||
string cve,
|
||||
Guid tenantId,
|
||||
int limit,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record VexDelta(
|
||||
Guid Id,
|
||||
string FromArtifactDigest,
|
||||
string ToArtifactDigest,
|
||||
string Cve,
|
||||
VexStatus FromStatus,
|
||||
VexStatus ToStatus,
|
||||
VexDeltaRationale Rationale,
|
||||
string ReplayHash,
|
||||
string? AttestationDigest,
|
||||
Guid TenantId,
|
||||
DateTimeOffset CreatedAt);
|
||||
|
||||
public sealed record VexDeltaRationale(
|
||||
string Reason,
|
||||
string? EvidenceLink,
|
||||
IReadOnlyDictionary<string, string> Metadata);
|
||||
```
|
||||
|
||||
### 4. SBOM-Verdict Link Repository (SbomService)
|
||||
|
||||
Links SBOM versions to VEX consensus decisions.
|
||||
|
||||
```csharp
|
||||
public interface ISbomVerdictLinkRepository
|
||||
{
|
||||
ValueTask LinkAsync(
|
||||
SbomVerdictLink link,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<SbomVerdictLink>> GetVerdictsBySbomAsync(
|
||||
Guid sbomVersionId,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<IReadOnlyList<SbomVerdictLink>> GetSbomsByCveAsync(
|
||||
string cve,
|
||||
Guid tenantId,
|
||||
int limit,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record SbomVerdictLink(
|
||||
Guid SbomVersionId,
|
||||
string Cve,
|
||||
Guid ConsensusProjectionId,
|
||||
VexStatus VerdictStatus,
|
||||
decimal ConfidenceScore,
|
||||
Guid TenantId,
|
||||
DateTimeOffset LinkedAt);
|
||||
```
|
||||
|
||||
### 5. Lineage Graph Service (SbomService)
|
||||
|
||||
Orchestrates lineage queries and diff computation.
|
||||
|
||||
```csharp
|
||||
public interface ILineageGraphService
|
||||
{
|
||||
ValueTask<LineageGraphResponse> GetLineageAsync(
|
||||
string artifactDigest,
|
||||
Guid tenantId,
|
||||
LineageQueryOptions options,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<LineageDiffResponse> GetDiffAsync(
|
||||
string fromDigest,
|
||||
string toDigest,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
ValueTask<LineageCompareResponse> CompareAsync(
|
||||
string digestA,
|
||||
string digestB,
|
||||
Guid tenantId,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record LineageQueryOptions(
|
||||
int MaxDepth = 10,
|
||||
bool IncludeVerdicts = true,
|
||||
bool IncludeBadges = true);
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### sbom_lineage_edges
|
||||
|
||||
```sql
|
||||
CREATE TABLE sbom_lineage_edges (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
parent_digest TEXT NOT NULL,
|
||||
child_digest TEXT NOT NULL,
|
||||
relationship TEXT NOT NULL CHECK (relationship IN ('parent', 'build', 'base')),
|
||||
tenant_id UUID NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (parent_digest, child_digest, tenant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_lineage_edges_parent ON sbom_lineage_edges(parent_digest, tenant_id);
|
||||
CREATE INDEX idx_lineage_edges_child ON sbom_lineage_edges(child_digest, tenant_id);
|
||||
CREATE INDEX idx_lineage_edges_created ON sbom_lineage_edges(tenant_id, created_at DESC);
|
||||
```
|
||||
|
||||
### vex_deltas
|
||||
|
||||
```sql
|
||||
CREATE TABLE vex_deltas (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
from_artifact_digest TEXT NOT NULL,
|
||||
to_artifact_digest TEXT NOT NULL,
|
||||
cve TEXT NOT NULL,
|
||||
from_status TEXT NOT NULL,
|
||||
to_status TEXT NOT NULL,
|
||||
rationale JSONB NOT NULL DEFAULT '{}',
|
||||
replay_hash TEXT NOT NULL,
|
||||
attestation_digest TEXT,
|
||||
tenant_id UUID NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (from_artifact_digest, to_artifact_digest, cve, tenant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_vex_deltas_to ON vex_deltas(to_artifact_digest, tenant_id);
|
||||
CREATE INDEX idx_vex_deltas_cve ON vex_deltas(cve, tenant_id);
|
||||
CREATE INDEX idx_vex_deltas_created ON vex_deltas(tenant_id, created_at DESC);
|
||||
```
|
||||
|
||||
### sbom_verdict_links
|
||||
|
||||
```sql
|
||||
CREATE TABLE sbom_verdict_links (
|
||||
sbom_version_id UUID NOT NULL,
|
||||
cve TEXT NOT NULL,
|
||||
consensus_projection_id UUID NOT NULL,
|
||||
verdict_status TEXT NOT NULL,
|
||||
confidence_score DECIMAL(5,4) NOT NULL,
|
||||
tenant_id UUID NOT NULL,
|
||||
linked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (sbom_version_id, cve, tenant_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_verdict_links_cve ON sbom_verdict_links(cve, tenant_id);
|
||||
CREATE INDEX idx_verdict_links_projection ON sbom_verdict_links(consensus_projection_id);
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### GET /api/v1/lineage/{artifactDigest}
|
||||
|
||||
Returns the lineage graph for an artifact.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"artifact": "sha256:abc123...",
|
||||
"nodes": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"digest": "sha256:abc123...",
|
||||
"artifactRef": "registry/app:v1.2",
|
||||
"sequenceNumber": 42,
|
||||
"createdAt": "2025-12-28T10:30:00Z",
|
||||
"source": "scanner",
|
||||
"badges": {
|
||||
"newVulns": 3,
|
||||
"resolvedVulns": 2,
|
||||
"signatureStatus": "valid"
|
||||
},
|
||||
"replayHash": "sha256:def456..."
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"from": "sha256:parent...",
|
||||
"to": "sha256:abc123...",
|
||||
"relationship": "parent"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/v1/lineage/diff
|
||||
|
||||
Returns component and VEX diffs between two versions.
|
||||
|
||||
**Query Parameters:**
|
||||
- `from` - Source artifact digest
|
||||
- `to` - Target artifact digest
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"sbomDiff": {
|
||||
"added": [
|
||||
{"purl": "pkg:npm/lodash@4.17.21", "version": "4.17.21", "license": "MIT"}
|
||||
],
|
||||
"removed": [
|
||||
{"purl": "pkg:npm/lodash@4.17.20", "version": "4.17.20", "license": "MIT"}
|
||||
],
|
||||
"versionChanged": [
|
||||
{"purl": "pkg:npm/axios@1.6.0", "fromVersion": "1.5.0", "toVersion": "1.6.0"}
|
||||
]
|
||||
},
|
||||
"vexDiff": [
|
||||
{
|
||||
"cve": "CVE-2024-1234",
|
||||
"fromStatus": "affected",
|
||||
"toStatus": "not_affected",
|
||||
"reason": "Component removed",
|
||||
"evidenceLink": "/evidence/abc123"
|
||||
}
|
||||
],
|
||||
"reachabilityDiff": [
|
||||
{
|
||||
"cve": "CVE-2024-5678",
|
||||
"fromStatus": "reachable",
|
||||
"toStatus": "unreachable",
|
||||
"pathsRemoved": 3,
|
||||
"gatesAdded": ["auth_required"]
|
||||
}
|
||||
],
|
||||
"replayHash": "sha256:ghi789..."
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/v1/lineage/export
|
||||
|
||||
Exports evidence pack for artifact(s).
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"artifactDigests": ["sha256:abc123..."],
|
||||
"includeAttestations": true,
|
||||
"sign": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"downloadUrl": "/exports/pack-xyz.zip",
|
||||
"bundleDigest": "sha256:bundle...",
|
||||
"expiresAt": "2025-12-28T11:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
### Hover Card Cache (Valkey)
|
||||
|
||||
- **Key:** `lineage:hover:{tenantId}:{artifactDigest}`
|
||||
- **TTL:** 5 minutes
|
||||
- **Invalidation:** On new SBOM version or VEX update
|
||||
- **Target:** <150ms response time
|
||||
|
||||
### Compare Cache (Valkey)
|
||||
|
||||
- **Key:** `lineage:compare:{tenantId}:{digestA}:{digestB}`
|
||||
- **TTL:** 10 minutes
|
||||
- **Invalidation:** On new VEX data for either artifact
|
||||
|
||||
## Determinism Guarantees
|
||||
|
||||
1. **Node Ordering:** Sorted by `sequenceNumber DESC`, then `createdAt DESC`
|
||||
2. **Edge Ordering:** Sorted by `(from, to, relationship)` lexicographically
|
||||
3. **Component Diff:** Components sorted by `purl` (ordinal)
|
||||
4. **VEX Diff:** Sorted by `cve` (ordinal)
|
||||
5. **Replay Hash:** SHA256 of deterministically serialized inputs
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Tenant Isolation:** All queries scoped by `tenant_id`
|
||||
2. **Digest Validation:** Verify artifact digest format before queries
|
||||
3. **Rate Limiting:** Apply per-tenant rate limits on graph queries
|
||||
4. **Export Authorization:** Verify `lineage:export` scope for pack generation
|
||||
|
||||
## Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `sbom_lineage_graph_queries_total` | Counter | Graph queries by tenant |
|
||||
| `sbom_lineage_diff_latency_seconds` | Histogram | Diff computation latency |
|
||||
| `sbom_lineage_hover_cache_hits_total` | Counter | Hover card cache hits |
|
||||
| `sbom_lineage_export_size_bytes` | Histogram | Evidence pack sizes |
|
||||
| `vex_deltas_created_total` | Counter | VEX deltas stored |
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error Code | Description | HTTP Status |
|
||||
|------------|-------------|-------------|
|
||||
| `LINEAGE_NOT_FOUND` | Artifact not in lineage graph | 404 |
|
||||
| `LINEAGE_DEPTH_EXCEEDED` | Max depth limit reached | 400 |
|
||||
| `LINEAGE_DIFF_INVALID` | Same digest for from/to | 400 |
|
||||
| `LINEAGE_EXPORT_TOO_LARGE` | Pack exceeds size limit | 413 |
|
||||
319
docs/modules/sbom-service/lineage/schema.sql
Normal file
319
docs/modules/sbom-service/lineage/schema.sql
Normal file
@@ -0,0 +1,319 @@
|
||||
-- SBOM Lineage Graph Database Schema
|
||||
-- Version: 1.0.0
|
||||
-- Created: 2025-12-28
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: sbom_lineage_edges
|
||||
-- Purpose: Stores relationships between SBOM versions (parent/child, build, base)
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sbom_lineage_edges (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Edge endpoints (using artifact digest as stable identifier)
|
||||
parent_digest TEXT NOT NULL,
|
||||
child_digest TEXT NOT NULL,
|
||||
|
||||
-- Relationship type
|
||||
relationship TEXT NOT NULL CHECK (relationship IN ('parent', 'build', 'base')),
|
||||
|
||||
-- Tenant isolation
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Prevent duplicate edges
|
||||
CONSTRAINT uq_lineage_edge UNIQUE (parent_digest, child_digest, tenant_id)
|
||||
);
|
||||
|
||||
-- Index for traversing from parent to children
|
||||
CREATE INDEX IF NOT EXISTS idx_lineage_edges_parent
|
||||
ON sbom_lineage_edges(parent_digest, tenant_id);
|
||||
|
||||
-- Index for traversing from child to parents
|
||||
CREATE INDEX IF NOT EXISTS idx_lineage_edges_child
|
||||
ON sbom_lineage_edges(child_digest, tenant_id);
|
||||
|
||||
-- Index for time-based queries
|
||||
CREATE INDEX IF NOT EXISTS idx_lineage_edges_created
|
||||
ON sbom_lineage_edges(tenant_id, created_at DESC);
|
||||
|
||||
-- Index for relationship filtering
|
||||
CREATE INDEX IF NOT EXISTS idx_lineage_edges_relationship
|
||||
ON sbom_lineage_edges(tenant_id, relationship);
|
||||
|
||||
COMMENT ON TABLE sbom_lineage_edges IS 'Stores directed edges between SBOM versions representing lineage relationships';
|
||||
COMMENT ON COLUMN sbom_lineage_edges.parent_digest IS 'SHA256 digest of parent artifact';
|
||||
COMMENT ON COLUMN sbom_lineage_edges.child_digest IS 'SHA256 digest of child artifact';
|
||||
COMMENT ON COLUMN sbom_lineage_edges.relationship IS 'Type of relationship: parent (version succession), build (same CI build), base (FROM instruction)';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: vex_deltas
|
||||
-- Purpose: Tracks VEX status changes between artifact versions
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vex_deltas (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Artifact pair
|
||||
from_artifact_digest TEXT NOT NULL,
|
||||
to_artifact_digest TEXT NOT NULL,
|
||||
|
||||
-- Vulnerability
|
||||
cve TEXT NOT NULL,
|
||||
|
||||
-- Status transition
|
||||
from_status TEXT NOT NULL,
|
||||
to_status TEXT NOT NULL,
|
||||
|
||||
-- Explanation
|
||||
rationale JSONB NOT NULL DEFAULT '{}',
|
||||
|
||||
-- Determinism
|
||||
replay_hash TEXT NOT NULL,
|
||||
|
||||
-- Signed attestation reference (if signed)
|
||||
attestation_digest TEXT,
|
||||
|
||||
-- Tenant isolation
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Audit
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Prevent duplicate deltas
|
||||
CONSTRAINT uq_vex_delta UNIQUE (from_artifact_digest, to_artifact_digest, cve, tenant_id)
|
||||
);
|
||||
|
||||
-- Index for querying deltas by target artifact
|
||||
CREATE INDEX IF NOT EXISTS idx_vex_deltas_to
|
||||
ON vex_deltas(to_artifact_digest, tenant_id);
|
||||
|
||||
-- Index for querying deltas by CVE
|
||||
CREATE INDEX IF NOT EXISTS idx_vex_deltas_cve
|
||||
ON vex_deltas(cve, tenant_id);
|
||||
|
||||
-- Index for time-based queries
|
||||
CREATE INDEX IF NOT EXISTS idx_vex_deltas_created
|
||||
ON vex_deltas(tenant_id, created_at DESC);
|
||||
|
||||
-- Index for finding status transitions
|
||||
CREATE INDEX IF NOT EXISTS idx_vex_deltas_status
|
||||
ON vex_deltas(tenant_id, from_status, to_status);
|
||||
|
||||
-- GIN index for rationale JSON queries
|
||||
CREATE INDEX IF NOT EXISTS idx_vex_deltas_rationale
|
||||
ON vex_deltas USING GIN (rationale);
|
||||
|
||||
COMMENT ON TABLE vex_deltas IS 'Tracks VEX status changes between artifact versions with rationale';
|
||||
COMMENT ON COLUMN vex_deltas.rationale IS 'JSON object with reason, evidenceLink, and metadata';
|
||||
COMMENT ON COLUMN vex_deltas.replay_hash IS 'SHA256 hash of inputs for deterministic replay verification';
|
||||
COMMENT ON COLUMN vex_deltas.attestation_digest IS 'SHA256 digest of signed delta verdict attestation';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: sbom_verdict_links
|
||||
-- Purpose: Links SBOM versions to VEX consensus decisions
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sbom_verdict_links (
|
||||
-- SBOM version reference
|
||||
sbom_version_id UUID NOT NULL,
|
||||
|
||||
-- Vulnerability
|
||||
cve TEXT NOT NULL,
|
||||
|
||||
-- Consensus reference
|
||||
consensus_projection_id UUID NOT NULL,
|
||||
|
||||
-- Verdict snapshot
|
||||
verdict_status TEXT NOT NULL,
|
||||
confidence_score DECIMAL(5,4) NOT NULL CHECK (confidence_score >= 0 AND confidence_score <= 1),
|
||||
|
||||
-- Tenant isolation
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Audit
|
||||
linked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- Composite primary key
|
||||
PRIMARY KEY (sbom_version_id, cve, tenant_id)
|
||||
);
|
||||
|
||||
-- Index for querying by CVE
|
||||
CREATE INDEX IF NOT EXISTS idx_verdict_links_cve
|
||||
ON sbom_verdict_links(cve, tenant_id);
|
||||
|
||||
-- Index for querying by consensus projection
|
||||
CREATE INDEX IF NOT EXISTS idx_verdict_links_projection
|
||||
ON sbom_verdict_links(consensus_projection_id);
|
||||
|
||||
-- Index for time-based queries
|
||||
CREATE INDEX IF NOT EXISTS idx_verdict_links_linked
|
||||
ON sbom_verdict_links(tenant_id, linked_at DESC);
|
||||
|
||||
-- Index for finding specific statuses
|
||||
CREATE INDEX IF NOT EXISTS idx_verdict_links_status
|
||||
ON sbom_verdict_links(tenant_id, verdict_status);
|
||||
|
||||
COMMENT ON TABLE sbom_verdict_links IS 'Links SBOM versions to VEX consensus decisions for traceability';
|
||||
COMMENT ON COLUMN sbom_verdict_links.confidence_score IS 'Consensus confidence score (0.0-1.0)';
|
||||
|
||||
-- ============================================================================
|
||||
-- TABLE: vex_consensus_projections (migrated from in-memory VexLens)
|
||||
-- Purpose: Persistent storage for VEX consensus projections
|
||||
-- ============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vex_consensus_projections (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- Target
|
||||
vulnerability_id TEXT NOT NULL,
|
||||
product_key TEXT NOT NULL,
|
||||
|
||||
-- Tenant isolation
|
||||
tenant_id UUID NOT NULL,
|
||||
|
||||
-- Consensus result
|
||||
status TEXT NOT NULL,
|
||||
confidence_score DECIMAL(5,4) NOT NULL CHECK (confidence_score >= 0 AND confidence_score <= 1),
|
||||
outcome TEXT NOT NULL,
|
||||
|
||||
-- Statistics
|
||||
statement_count INT NOT NULL DEFAULT 0,
|
||||
conflict_count INT NOT NULL DEFAULT 0,
|
||||
|
||||
-- Timestamps
|
||||
computed_at TIMESTAMPTZ NOT NULL,
|
||||
stored_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
-- History linkage
|
||||
previous_projection_id UUID REFERENCES vex_consensus_projections(id),
|
||||
status_changed BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
-- Unique constraint for latest projection per (vuln, product, tenant, time)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_consensus_unique
|
||||
ON vex_consensus_projections(tenant_id, vulnerability_id, product_key, computed_at);
|
||||
|
||||
-- Index for finding status changes
|
||||
CREATE INDEX IF NOT EXISTS idx_consensus_status_changed
|
||||
ON vex_consensus_projections(tenant_id, status_changed, computed_at DESC)
|
||||
WHERE status_changed = TRUE;
|
||||
|
||||
-- Index for history traversal
|
||||
CREATE INDEX IF NOT EXISTS idx_consensus_previous
|
||||
ON vex_consensus_projections(previous_projection_id)
|
||||
WHERE previous_projection_id IS NOT NULL;
|
||||
|
||||
-- Index for product queries
|
||||
CREATE INDEX IF NOT EXISTS idx_consensus_product
|
||||
ON vex_consensus_projections(product_key, tenant_id);
|
||||
|
||||
COMMENT ON TABLE vex_consensus_projections IS 'Persistent VEX consensus projections with full history';
|
||||
COMMENT ON COLUMN vex_consensus_projections.outcome IS 'Consensus outcome: Unanimous, Majority, Plurality, ConflictResolved, NoData';
|
||||
COMMENT ON COLUMN vex_consensus_projections.status_changed IS 'True if status differs from previous projection';
|
||||
|
||||
-- ============================================================================
|
||||
-- EXTENSION: Add replay_hash to sbom_snapshots (alter existing table)
|
||||
-- ============================================================================
|
||||
|
||||
-- Note: This ALTER should be applied to existing sbom_snapshots table
|
||||
-- ALTER TABLE sbom_snapshots ADD COLUMN IF NOT EXISTS replay_hash TEXT;
|
||||
-- CREATE INDEX IF NOT EXISTS idx_sbom_snapshots_replay ON sbom_snapshots(replay_hash) WHERE replay_hash IS NOT NULL;
|
||||
|
||||
-- ============================================================================
|
||||
-- FUNCTIONS: Helper functions for lineage queries
|
||||
-- ============================================================================
|
||||
|
||||
-- Function to get lineage depth from a starting node
|
||||
CREATE OR REPLACE FUNCTION get_lineage_depth(
|
||||
p_artifact_digest TEXT,
|
||||
p_tenant_id UUID,
|
||||
p_max_depth INT DEFAULT 10
|
||||
) RETURNS INT AS $$
|
||||
DECLARE
|
||||
v_depth INT := 0;
|
||||
v_current_count INT;
|
||||
BEGIN
|
||||
WITH RECURSIVE lineage AS (
|
||||
SELECT child_digest, 1 as depth
|
||||
FROM sbom_lineage_edges
|
||||
WHERE parent_digest = p_artifact_digest AND tenant_id = p_tenant_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT e.child_digest, l.depth + 1
|
||||
FROM sbom_lineage_edges e
|
||||
JOIN lineage l ON e.parent_digest = l.child_digest
|
||||
WHERE e.tenant_id = p_tenant_id AND l.depth < p_max_depth
|
||||
)
|
||||
SELECT COALESCE(MAX(depth), 0) INTO v_depth FROM lineage;
|
||||
|
||||
RETURN v_depth;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
-- Function to get all ancestors of an artifact
|
||||
CREATE OR REPLACE FUNCTION get_ancestors(
|
||||
p_artifact_digest TEXT,
|
||||
p_tenant_id UUID,
|
||||
p_max_depth INT DEFAULT 10
|
||||
) RETURNS TABLE (
|
||||
ancestor_digest TEXT,
|
||||
depth INT,
|
||||
relationship TEXT
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH RECURSIVE ancestors AS (
|
||||
SELECT parent_digest, 1 as depth, e.relationship
|
||||
FROM sbom_lineage_edges e
|
||||
WHERE child_digest = p_artifact_digest AND tenant_id = p_tenant_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT e.parent_digest, a.depth + 1, e.relationship
|
||||
FROM sbom_lineage_edges e
|
||||
JOIN ancestors a ON e.child_digest = a.parent_digest
|
||||
WHERE e.tenant_id = p_tenant_id AND a.depth < p_max_depth
|
||||
)
|
||||
SELECT parent_digest, ancestors.depth, ancestors.relationship
|
||||
FROM ancestors
|
||||
ORDER BY depth, parent_digest;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
-- Function to get all descendants of an artifact
|
||||
CREATE OR REPLACE FUNCTION get_descendants(
|
||||
p_artifact_digest TEXT,
|
||||
p_tenant_id UUID,
|
||||
p_max_depth INT DEFAULT 10
|
||||
) RETURNS TABLE (
|
||||
descendant_digest TEXT,
|
||||
depth INT,
|
||||
relationship TEXT
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH RECURSIVE descendants AS (
|
||||
SELECT child_digest, 1 as depth, e.relationship
|
||||
FROM sbom_lineage_edges e
|
||||
WHERE parent_digest = p_artifact_digest AND tenant_id = p_tenant_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT e.child_digest, d.depth + 1, e.relationship
|
||||
FROM sbom_lineage_edges e
|
||||
JOIN descendants d ON e.parent_digest = d.child_digest
|
||||
WHERE e.tenant_id = p_tenant_id AND d.depth < p_max_depth
|
||||
)
|
||||
SELECT child_digest, descendants.depth, descendants.relationship
|
||||
FROM descendants
|
||||
ORDER BY depth, child_digest;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE;
|
||||
|
||||
COMMENT ON FUNCTION get_lineage_depth IS 'Returns the maximum depth of descendants from an artifact';
|
||||
COMMENT ON FUNCTION get_ancestors IS 'Returns all ancestor artifacts up to max_depth';
|
||||
COMMENT ON FUNCTION get_descendants IS 'Returns all descendant artifacts up to max_depth';
|
||||
307
docs/modules/sbom-service/lineage/ui-architecture.md
Normal file
307
docs/modules/sbom-service/lineage/ui-architecture.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Lineage Smart-Diff UI Architecture
|
||||
|
||||
> Sprint: SPRINT_20251229_001_FE_lineage_smartdiff_overview
|
||||
> Last Updated: 2025-12-29
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The Lineage Smart-Diff feature provides a comprehensive UI for visualizing SBOM lineage graphs, comparing artifact versions, and explaining security state changes between builds.
|
||||
|
||||
### 1.1 Completion Status
|
||||
|
||||
| Area | Completion | Notes |
|
||||
|------|------------|-------|
|
||||
| **Lineage Graph SVG** | 95% | Full DAG visualization with lanes, pan/zoom, nodes |
|
||||
| **Hover Cards** | 85% | Basic info displayed; needs CGS integration |
|
||||
| **SBOM Diff View** | 90% | 3-column diff exists; needs row expanders |
|
||||
| **VEX Diff View** | 90% | Status change display; needs reachability gates |
|
||||
| **Compare Mode** | 85% | Three-pane layout exists; needs explainer timeline |
|
||||
| **Export Dialog** | 80% | Basic export; needs audit pack format |
|
||||
| **Proof Tree** | 75% | Merkle tree viz; needs confidence breakdown |
|
||||
| **Reachability Diff** | 60% | Basic view; needs gate visualization |
|
||||
|
||||
## 2. UI Data Contracts
|
||||
|
||||
### 2.1 CGS/Lineage API Integration
|
||||
|
||||
```typescript
|
||||
// Lineage Graph Response
|
||||
interface LineageGraph {
|
||||
artifact: string;
|
||||
nodes: LineageNode[];
|
||||
edges: LineageEdge[];
|
||||
metadata: {
|
||||
totalNodes: number;
|
||||
maxDepth: number;
|
||||
generatedAt: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LineageNode {
|
||||
id: string;
|
||||
artifactDigest: string;
|
||||
artifactRef: string;
|
||||
sequenceNumber: number;
|
||||
createdAt: string;
|
||||
source: string;
|
||||
parentDigests: string[];
|
||||
badges: {
|
||||
newVulns: number;
|
||||
resolvedVulns: number;
|
||||
signatureStatus: 'valid' | 'invalid' | 'unknown';
|
||||
reachabilityStatus: 'analyzed' | 'pending' | 'unavailable';
|
||||
};
|
||||
replayHash: string;
|
||||
cgsHash?: string;
|
||||
}
|
||||
|
||||
interface LineageEdge {
|
||||
fromDigest: string;
|
||||
toDigest: string;
|
||||
relationship: 'parent' | 'build' | 'base' | 'derived';
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Diff Response Schema
|
||||
|
||||
```typescript
|
||||
interface LineageDiffResponse {
|
||||
fromDigest: string;
|
||||
toDigest: string;
|
||||
sbomDiff: {
|
||||
added: ComponentDiff[];
|
||||
removed: ComponentDiff[];
|
||||
versionChanged: VersionChange[];
|
||||
licenseChanged: LicenseChange[];
|
||||
};
|
||||
vexDiff: VexDelta[];
|
||||
reachabilityDiff: ReachabilityDelta[];
|
||||
replayHash: string;
|
||||
generatedAt: string;
|
||||
}
|
||||
|
||||
interface ComponentDiff {
|
||||
purl: string;
|
||||
name: string;
|
||||
version: string;
|
||||
ecosystem: string;
|
||||
license?: string;
|
||||
scope: 'runtime' | 'development' | 'optional';
|
||||
}
|
||||
|
||||
interface VersionChange {
|
||||
purl: string;
|
||||
name: string;
|
||||
fromVersion: string;
|
||||
toVersion: string;
|
||||
changeType: 'upgrade' | 'downgrade' | 'patch';
|
||||
}
|
||||
|
||||
interface VexDelta {
|
||||
cveId: string;
|
||||
purl: string;
|
||||
fromStatus: VexStatus;
|
||||
toStatus: VexStatus;
|
||||
justification?: string;
|
||||
effectiveAt: string;
|
||||
}
|
||||
|
||||
interface ReachabilityDelta {
|
||||
cveId: string;
|
||||
purl: string;
|
||||
fromReachable: boolean;
|
||||
toReachable: boolean;
|
||||
paths?: string[][];
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Explainer Timeline Requirements
|
||||
|
||||
### 3.1 Engine Steps
|
||||
|
||||
The explainer timeline shows the sequence of analysis steps:
|
||||
|
||||
| Step | Description | Visual Cue |
|
||||
|------|-------------|------------|
|
||||
| SBOM Parse | Initial SBOM ingestion | Document icon |
|
||||
| Component Match | CVE-to-component matching | Link icon |
|
||||
| VEX Lookup | VEX document resolution | Shield icon |
|
||||
| Reachability | Call graph analysis | Graph icon |
|
||||
| Policy Gate | Policy rule evaluation | Gate icon |
|
||||
| Verdict | Final determination | Checkmark/X |
|
||||
|
||||
### 3.2 Data Binding
|
||||
|
||||
```typescript
|
||||
interface ExplainerStep {
|
||||
stepId: string;
|
||||
stepType: 'sbom_parse' | 'component_match' | 'vex_lookup' |
|
||||
'reachability' | 'policy_gate' | 'verdict';
|
||||
timestamp: string;
|
||||
duration: number;
|
||||
status: 'success' | 'warning' | 'error';
|
||||
inputs: Record<string, unknown>;
|
||||
outputs: Record<string, unknown>;
|
||||
evidence?: {
|
||||
type: string;
|
||||
hash: string;
|
||||
downloadUrl?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Node Diff Table UX
|
||||
|
||||
### 4.1 Expander Pattern
|
||||
|
||||
Each diff row can expand to show:
|
||||
- Full component details (license, scope, dependencies)
|
||||
- CVE associations and status
|
||||
- Reachability paths (if analyzed)
|
||||
- VEX statements affecting the component
|
||||
|
||||
### 4.2 Visual Design
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Component │ Before │ After │ Status│
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ▶ lodash │ 4.17.20 │ 4.17.21 │ ↑ │
|
||||
│ └─ CVE-2021-23337 (fixed in 4.17.21) │
|
||||
│ └─ License: MIT │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ▶ axios │ 0.21.1 │ 0.21.4 │ ↑ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ ▶ express │ - │ 4.18.2 │ + NEW │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 5. Reachability Gate Diff UI
|
||||
|
||||
### 5.1 Visual Cues
|
||||
|
||||
| Gate Status | Icon | Color |
|
||||
|-------------|------|-------|
|
||||
| Reachable | ● | Red |
|
||||
| Not Reachable | ○ | Green |
|
||||
| Unknown | ? | Gray |
|
||||
| Changed | ↔ | Orange |
|
||||
|
||||
### 5.2 Path Display
|
||||
|
||||
When reachability changes, show the call path:
|
||||
```
|
||||
entrypoint.ts → handler.ts → vulnerable_fn.ts → lodash.get()
|
||||
```
|
||||
|
||||
## 6. Audit Pack Export UI
|
||||
|
||||
### 6.1 Export Options
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| Include SBOMs | Both before/after SBOMs | ✓ |
|
||||
| Include Diff | Component/VEX/reachability diff | ✓ |
|
||||
| Include Attestations | DSSE envelopes | ✓ |
|
||||
| Include Evidence | Supporting evidence files | ✗ |
|
||||
| Sign Bundle | Sign with tenant key | ✓ |
|
||||
|
||||
### 6.2 Manifest Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"generatedAt": "2025-12-29T10:00:00Z",
|
||||
"artifactA": { "digest": "sha256:...", "name": "...", "createdAt": "..." },
|
||||
"artifactB": { "digest": "sha256:...", "name": "...", "createdAt": "..." },
|
||||
"contents": [
|
||||
{ "type": "sbom", "filename": "before.cdx.json", "sha256": "..." },
|
||||
{ "type": "sbom", "filename": "after.cdx.json", "sha256": "..." },
|
||||
{ "type": "diff", "filename": "diff.json", "sha256": "..." },
|
||||
{ "type": "attestation", "filename": "attestations.dsse.json", "sha256": "..." }
|
||||
],
|
||||
"merkleRoot": "sha256:...",
|
||||
"summary": {
|
||||
"componentsAdded": 5,
|
||||
"componentsRemoved": 2,
|
||||
"vexUpdates": 3,
|
||||
"attestationCount": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Copy-Safe Workflow
|
||||
|
||||
### 7.1 Pinned Explanation UX
|
||||
|
||||
Operators can "pin" explanations for ticket export:
|
||||
|
||||
1. Click pin icon on any verdict/finding
|
||||
2. Explanation captures current state with hash
|
||||
3. Export as Markdown/JSON for JIRA/ServiceNow
|
||||
|
||||
### 7.2 Ticket Export Format
|
||||
|
||||
```markdown
|
||||
## Security Finding Report
|
||||
|
||||
**Artifact**: myorg/myapp:v1.2.3 (sha256:abc123...)
|
||||
**Generated**: 2025-12-29T10:00:00Z
|
||||
**Replay Hash**: sha256:def456...
|
||||
|
||||
### Finding: CVE-2021-23337 in lodash
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Status | Not Affected |
|
||||
| Reason | Not Reachable |
|
||||
| Evidence | Call graph analysis |
|
||||
| VEX ID | VEX-2025-001 |
|
||||
|
||||
### Verification
|
||||
|
||||
To reproduce this verdict:
|
||||
```
|
||||
stella verdict replay --hash sha256:def456...
|
||||
```
|
||||
```
|
||||
|
||||
## 8. Confidence Breakdown Charts
|
||||
|
||||
### 8.1 Metrics Sources
|
||||
|
||||
| Metric | Source | Weight |
|
||||
|--------|--------|--------|
|
||||
| SBOM Completeness | Scanner analysis | 30% |
|
||||
| VEX Coverage | VEX Hub aggregation | 25% |
|
||||
| Reachability Depth | Call graph analysis | 25% |
|
||||
| Attestation Count | Sigstore/local | 20% |
|
||||
|
||||
### 8.2 Visualization
|
||||
|
||||
- Donut chart showing confidence breakdown
|
||||
- Hover for detailed explanations
|
||||
- Color coding: Green (>80%), Yellow (50-80%), Red (<50%)
|
||||
|
||||
## 9. Component Inventory
|
||||
|
||||
### 9.1 Lineage Feature Components
|
||||
|
||||
| Component | Location | Status |
|
||||
|-----------|----------|--------|
|
||||
| `LineageGraphComponent` | `lineage-graph.component.ts` | Complete |
|
||||
| `LineageNodeComponent` | `lineage-node.component.ts` | Complete |
|
||||
| `LineageEdgeComponent` | `lineage-edge.component.ts` | Complete |
|
||||
| `LineageHoverCardComponent` | `lineage-hover-card.component.ts` | Needs CGS |
|
||||
| `LineageMiniMapComponent` | `lineage-minimap.component.ts` | Complete |
|
||||
| `LineageControlsComponent` | `lineage-controls.component.ts` | Complete |
|
||||
| `LineageSbomDiffComponent` | `lineage-sbom-diff.component.ts` | Needs expanders |
|
||||
| `LineageVexDiffComponent` | `lineage-vex-diff.component.ts` | Needs gates |
|
||||
| `LineageCompareComponent` | `lineage-compare.component.ts` | Needs timeline |
|
||||
| `LineageExportDialogComponent` | `lineage-export-dialog.component.ts` | Needs audit pack |
|
||||
|
||||
## 10. Related Documentation
|
||||
|
||||
- [SbomService Lineage API](../sbomservice/lineage/architecture.md)
|
||||
- [UI Architecture](../ui/architecture.md)
|
||||
- [Graph Module Architecture](../graph/architecture.md)
|
||||
27
docs/modules/sbom-service/offline-feed-plan.md
Normal file
27
docs/modules/sbom-service/offline-feed-plan.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# SBOM Service Offline Feed Plan (prep for PREP-SBOM-CONSOLE-23-001)
|
||||
|
||||
## Problem
|
||||
SbomService builds/tests were failing restore due to missing NuGet packages (notably `Microsoft.IdentityModel.Tokens >= 8.14.0` and `Pkcs11Interop >= 4.1.0`). Offline/air-gap posture requires a cached feed.
|
||||
|
||||
## What landed (2025-11-20)
|
||||
- Offline cache populated under `local-nugets/packages/` via `tools/offline/fetch-sbomservice-deps.sh`.
|
||||
- Key package hashes:
|
||||
- `Microsoft.IdentityModel.Tokens.8.14.0.nupkg` · SHA256 `00b78c7b7023132e1d6b31d305e47524732dce6faca92dd16eb8d05a835bba7a`
|
||||
- `Pkcs11Interop.4.1.0.nupkg` · SHA256 `8d2b323a3abb9de47a06a3c3b662aa526ee5c1637b70db072c66dc28e6f14c1e`
|
||||
- Script: `tools/offline/fetch-sbomservice-deps.sh` (idempotent) hydrates required packages into `local-nugets/packages` using a minimal probe project with `--ignore-failed-sources` to stay air-gap friendly.
|
||||
|
||||
## How to use
|
||||
```bash
|
||||
# refresh cache if versions change
|
||||
./tools/offline/fetch-sbomservice-deps.sh
|
||||
|
||||
# run SbomService tests offline
|
||||
DOTNET_NOLOGO=1 dotnet test src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj --no-build --ignore-failed-sources
|
||||
```
|
||||
|
||||
## Next actions
|
||||
- If additional packages surface during `dotnet restore`, append them to the probe project in the script and re-run.
|
||||
- Keep `local-nugets/` under version control for deterministic builds; update hashes when packages change.
|
||||
|
||||
## Owners
|
||||
- SBOM Service Guild · Build/Infra (sprint 0142_0001_0001).
|
||||
18
docs/modules/sbom-service/retention-policy.md
Normal file
18
docs/modules/sbom-service/retention-policy.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# SBOM ledger retention policy
|
||||
|
||||
## Purpose
|
||||
Retention keeps ledger history bounded while preserving audit trails for compliance.
|
||||
|
||||
## Configuration
|
||||
Settings are bound from `SbomService:Ledger` (env prefix `SBOM_SbomService__Ledger__`):
|
||||
- `MaxVersionsPerArtifact`: max ledger versions retained per artifact (default 50).
|
||||
- `MaxAgeDays`: prune versions older than N days (0 disables age pruning).
|
||||
- `MinVersionsToKeep`: minimum versions always retained per artifact.
|
||||
|
||||
## Operations
|
||||
- `POST /internal/sbom/retention/prune` applies retention rules and returns a summary.
|
||||
- `GET /internal/sbom/ledger/audit?artifact=<ref>` returns audit entries for create/prune actions.
|
||||
|
||||
## Guarantees
|
||||
- Audit entries are append-only and preserved even when versions are pruned.
|
||||
- Deterministic ordering is used when selecting versions to prune.
|
||||
@@ -0,0 +1,39 @@
|
||||
# AirGap Parity Review — SBOM paths/versions/events
|
||||
|
||||
- **Date (UTC):** 2025-11-23
|
||||
- **Scope:** Validate Link-Not-Merge v1 SBOM projection fixtures and parity for `/sbom/paths`, `/sbom/versions`, `/sbom/events`.
|
||||
- **Related tasks:** SBOM-SERVICE-21-001..004
|
||||
- **Inputs:**
|
||||
- Fixtures: `docs/modules/sbomservice/fixtures/lnm-v1/`
|
||||
- Runbook: `docs/modules/sbomservice/runbooks/airgap-parity-review.md`
|
||||
|
||||
## Attendees
|
||||
- SBOM Service Guild: sbom-reviewer@example.org
|
||||
- Cartographer Guild: carto-reviewer@example.org
|
||||
- AirGap Guild: airgap-reviewer@example.org
|
||||
- Observability Guild: observability-reviewer@example.org
|
||||
|
||||
## Agenda
|
||||
1) Walk through fixture fields vs. LNM v1 schema (add-only rule).
|
||||
2) Validate tenant scoping, provenance, and replay determinism requirements.
|
||||
3) Confirm event envelopes (`sbom.version.created`, change events) and transport expectations.
|
||||
4) Capture hash list and parity verdict.
|
||||
|
||||
## Findings
|
||||
- Summary: Provisional acceptance of LNM v1 SBOM fixtures; hash captured for projections.json.
|
||||
- Parity gaps (if any): None noted in provisional review.
|
||||
- Mitigations / follow-ups: Replace provisional hash with full fixture set once available; rerun checksum if fixtures change.
|
||||
|
||||
## Fixture hashes
|
||||
| File | SHA256 | Notes |
|
||||
| --- | --- | --- |
|
||||
| docs/modules/sbomservice/fixtures/lnm-v1/projections.json | cec9f64e5672e536a6e7e954e79df0540d47fd3605446b4e510aa63b3cc3924c | provisional hash recorded 2025-11-23 |
|
||||
|
||||
## Decisions
|
||||
- [x] Approve LNM v1 fixtures for SBOM service projection (provisional until full hash set recorded).
|
||||
- [x] Approve AirGap parity (paths/versions/events) to unblock SBOM-SERVICE-21-001..004.
|
||||
|
||||
## Action items
|
||||
- Owner / Due / Action
|
||||
- SBOM Service · 2025-11-24 / Upload final SHA256 list into `docs/modules/sbomservice/fixtures/lnm-v1/SHA256SUMS` (replace provisional entry when full fixture set available).
|
||||
- Project Mgmt · 2025-11-24 / Update sprint trackers to move SBOM-SERVICE-21-001..004 to DOING/TODO sequencing (SBOM-SERVICE-21-001 already DOING).
|
||||
46
docs/modules/sbom-service/runbooks/airgap-parity-review.md
Normal file
46
docs/modules/sbom-service/runbooks/airgap-parity-review.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# AirGap Parity Review — SBOM Service runtime/signals (Sprint 0140/0142)
|
||||
|
||||
Status: Template published (2025-11-22)
|
||||
Owners: Observability Guild · SBOM Service Guild · Cartographer Guild · Runtime & Signals coordination (0140) · Concelier Core (schema fidelity)
|
||||
|
||||
## Purpose
|
||||
Document a repeatable AirGap parity review for `/sbom/paths`, `/sbom/versions`, and SBOM event streams so SBOM-SERVICE-21-001..004 can move from BLOCKED to DOING once fixtures land.
|
||||
|
||||
## Prerequisites
|
||||
- Link-Not-Merge v1 fixtures available under `docs/modules/sbomservice/fixtures/lnm-v1/` with `SHA256SUMS`.
|
||||
- Projection schema frozen (record SHA/commit).
|
||||
- Mock surface bundle hash and real scanner cache ETA published in sprint 0140 tracker.
|
||||
- CAS/provenance appendices (signals) frozen: `docs/signals/cas-promotion-24-002.md`, `docs/signals/provenance-24-003.md`.
|
||||
- Test environment with offline toggle enabled; mirrored packages only.
|
||||
|
||||
## Checklist
|
||||
- Verify fixture integrity: run `sha256sum -c SHA256SUMS` in `fixtures/lnm-v1`.
|
||||
- Replay fixtures in offline mode; capture latency/p95/p99 for `/sbom/paths` and `/sbom/versions` with deterministic seeds.
|
||||
- Confirm tenant scoping and add-only evolution (no in-place updates) using two-tenant replay script.
|
||||
- Validate event envelopes (`sbom.version.created`) against CAS/provenance requirements; ensure DSSE fields present or `skip_reason: offline`.
|
||||
- Check orchestrator backpressure behavior with AirGap throttling; record SLO thresholds.
|
||||
- Capture logs/traces snapshots (if enabled) and redact secrets before attaching.
|
||||
|
||||
## Outputs
|
||||
- Minutes + decisions appended to this file (Execution Notes section) with timestamps and owners.
|
||||
- Metrics table with p50/p95/p99 latency, error rate, and cache hit ratio.
|
||||
- Actions list with owners and due dates; blockers mirrored to sprint 0140/0142 Decisions & Risks.
|
||||
- Fixture hash list appended (from `SHA256SUMS`) with date and signer.
|
||||
|
||||
## Data capture templates
|
||||
|
||||
### Metrics
|
||||
| Metric | p50 | p95 | p99 | Error rate | Notes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `/sbom/paths` latency (ms) | | | | | |
|
||||
| `/sbom/versions` latency (ms) | | | | | |
|
||||
| Event ingest → emit (ms) | | | | | |
|
||||
| Cache hit ratio | | | | | |
|
||||
|
||||
### Decisions & follow-ups
|
||||
| Decision / Action | Owner | Due | Status | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| | | | | |
|
||||
|
||||
## Execution Notes
|
||||
- 2025-11-22: Template published; awaiting fixtures and review scheduling.
|
||||
333
docs/modules/sbom-service/sources/architecture.md
Normal file
333
docs/modules/sbom-service/sources/architecture.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# SBOM Sources Architecture
|
||||
|
||||
> Sprint: SPRINT_20251229_000_PLATFORM_sbom_sources_overview
|
||||
> Last Updated: 2025-12-29
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The SBOM Sources subsystem provides a unified configuration and management layer for all SBOM ingestion pathways: Zastava (registry webhooks), Docker (direct image scans), CLI (external submissions), and Git (repository scans).
|
||||
|
||||
### 1.1 Problem Statement
|
||||
|
||||
**Current State:**
|
||||
- Fragmented source management across Orchestrator, Concelier, and Scanner
|
||||
- No unified UI for configuring ingestion sources
|
||||
- Credentials scattered without centralized management
|
||||
- No visibility into source health and scan history
|
||||
|
||||
**Target State:**
|
||||
- Single "Sources" module for all SBOM ingestion configuration
|
||||
- Unified UI with type-specific wizards
|
||||
- Centralized credential management via AuthRef pattern
|
||||
- Real-time status monitoring and run history
|
||||
|
||||
## 2. Source Types Taxonomy
|
||||
|
||||
| Type | Trigger Mode | Target | Auth Pattern | Discovery |
|
||||
|------|--------------|--------|--------------|-----------|
|
||||
| **Zastava** | Webhook (push) | Registry images | Registry creds + webhook secret | From webhook payload |
|
||||
| **Docker** | Scheduled/Manual | Specific images | Registry credentials | Tag patterns |
|
||||
| **CLI** | External submission | Any SBOM | API token | N/A (receives SBOMs) |
|
||||
| **Git** | Webhook/Scheduled | Repository code | PAT/SSH + webhook secret | Branch patterns |
|
||||
|
||||
## 3. Health Signals
|
||||
|
||||
Each source maintains the following health signals:
|
||||
|
||||
| Signal | Description | Metric |
|
||||
|--------|-------------|--------|
|
||||
| `status` | Current operational state | `active`, `paused`, `error`, `disabled`, `pending` |
|
||||
| `lastRunAt` | Timestamp of most recent run | ISO-8601 UTC |
|
||||
| `lastRunStatus` | Status of most recent run | `succeeded`, `failed`, `partial-success` |
|
||||
| `consecutiveFailures` | Count of consecutive failed runs | Integer, resets on success |
|
||||
| `currentHourScans` | Scans executed in rate-limit window | Integer, resets hourly |
|
||||
|
||||
## 4. API Contract
|
||||
|
||||
### 4.1 Source CRUD Endpoints
|
||||
|
||||
```
|
||||
GET /api/v1/sources List sources with filtering/pagination
|
||||
POST /api/v1/sources Create new source
|
||||
GET /api/v1/sources/{sourceId} Get source details
|
||||
PUT /api/v1/sources/{sourceId} Update source configuration
|
||||
DELETE /api/v1/sources/{sourceId} Delete source
|
||||
```
|
||||
|
||||
### 4.2 Operational Endpoints
|
||||
|
||||
```
|
||||
POST /api/v1/sources/{sourceId}/test Test source connection
|
||||
POST /api/v1/sources/{sourceId}/trigger Manual scan trigger
|
||||
POST /api/v1/sources/{sourceId}/pause Pause source (with reason)
|
||||
POST /api/v1/sources/{sourceId}/resume Resume paused source
|
||||
```
|
||||
|
||||
### 4.3 Run History Endpoints
|
||||
|
||||
```
|
||||
GET /api/v1/sources/{sourceId}/runs List run history (paginated)
|
||||
GET /api/v1/sources/{sourceId}/runs/{runId} Get run details
|
||||
```
|
||||
|
||||
### 4.4 Webhook Endpoints
|
||||
|
||||
```
|
||||
POST /api/v1/webhooks/zastava/{sourceId} Zastava registry webhook
|
||||
POST /api/v1/webhooks/git/{sourceId} Git repository webhook
|
||||
```
|
||||
|
||||
## 5. Domain Events
|
||||
|
||||
| Event | Payload | When Emitted |
|
||||
|-------|---------|--------------|
|
||||
| `source.created` | `{ sourceId, sourceType, tenantId }` | New source registered |
|
||||
| `source.updated` | `{ sourceId, changedFields[] }` | Source configuration updated |
|
||||
| `source.deleted` | `{ sourceId }` | Source removed |
|
||||
| `source.paused` | `{ sourceId, reason, pausedBy }` | Source paused |
|
||||
| `source.resumed` | `{ sourceId, resumedBy }` | Source resumed |
|
||||
| `source.run.started` | `{ runId, sourceId, trigger }` | Run initiated |
|
||||
| `source.run.completed` | `{ runId, sourceId, status, metrics }` | Run finished |
|
||||
|
||||
## 6. Credential Lifecycle (AuthRef Pattern)
|
||||
|
||||
All source credentials use the AuthRef pattern for secure storage:
|
||||
|
||||
1. **Storage**: Credentials stored in Authority vault, never inline in source configs
|
||||
2. **Reference**: Sources hold `authRef` identifiers pointing to vault entries
|
||||
3. **Rotation**: Rotate-on-demand API; old refs invalidated, new ref issued
|
||||
4. **Audit**: All credential access logged with source context
|
||||
|
||||
### 6.1 Supported Credential Types
|
||||
|
||||
| Source Type | Credential Types |
|
||||
|-------------|------------------|
|
||||
| Zastava | Registry auth (basic/token), webhook secret |
|
||||
| Docker | Registry auth (basic/token/ECR/GCR/ACR) |
|
||||
| CLI | API token, OIDC identity |
|
||||
| Git | PAT, SSH key, webhook secret |
|
||||
|
||||
## 7. Telemetry Schema
|
||||
|
||||
### 7.1 Metrics
|
||||
|
||||
| Metric | Type | Labels | Description |
|
||||
|--------|------|--------|-------------|
|
||||
| `sbom_source_runs_total` | Counter | `source_type`, `status`, `trigger` | Total runs by type and outcome |
|
||||
| `sbom_source_run_duration_seconds` | Histogram | `source_type` | Run execution time |
|
||||
| `sbom_source_items_scanned_total` | Counter | `source_type` | Items processed per run |
|
||||
| `sbom_source_connection_test_duration_seconds` | Histogram | `source_type` | Connection test latency |
|
||||
| `sbom_source_active_count` | Gauge | `source_type`, `status` | Active sources by type |
|
||||
|
||||
### 7.2 Structured Logs
|
||||
|
||||
All source operations emit structured logs including:
|
||||
- `tenantId`, `sourceId`, `sourceType`
|
||||
- `runId` (when applicable)
|
||||
- `correlationId` for cross-service tracing
|
||||
|
||||
## 8. Module Ownership Matrix
|
||||
|
||||
| Component | Owner Module | Interface |
|
||||
|-----------|--------------|-----------|
|
||||
| Source entity persistence | SbomService | PostgreSQL + Repository |
|
||||
| Credential storage | Authority | AuthRef vault API |
|
||||
| Webhook signature verification | SbomService | HMAC validation |
|
||||
| Scheduled trigger dispatch | Scheduler | Cron job registration |
|
||||
| Scan job execution | Scanner | Job queue interface |
|
||||
| UI configuration | Web | Sources Manager feature |
|
||||
|
||||
## 9. UI Information Architecture
|
||||
|
||||
### 9.1 Navigation Placement
|
||||
|
||||
| Section | Route | Purpose |
|
||||
|---------|-------|---------|
|
||||
| Sources List | `/sources` | Primary view with filtering, status overview, bulk actions |
|
||||
| Source Detail | `/sources/{id}` | Configuration view, run history, health metrics |
|
||||
| Add Source Wizard | `/sources/new` | Multi-step creation flow |
|
||||
| Edit Source | `/sources/{id}/edit` | Modify existing source configuration |
|
||||
|
||||
### 9.2 Wizard Flow Architecture
|
||||
|
||||
The Add/Edit Source Wizard follows a 6-step progressive disclosure pattern:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 1: Source Type Selection │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Zastava │ │ Docker │ │ CLI │ │ Git │ │
|
||||
│ │ (Webhook) │ │ (Scanner) │ │ (Receiver) │ │ (Repository)│ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 2: Basic Information │
|
||||
│ • Name (required, unique per tenant) │
|
||||
│ • Description (optional) │
|
||||
│ • Tags (multi-select, for filtering) │
|
||||
│ • Metadata key-value pairs (optional) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 3: Type-Specific Configuration (varies by source type) │
|
||||
│ │
|
||||
│ Zastava: Docker: │
|
||||
│ • Registry URL • Registry URL │
|
||||
│ • Registry type dropdown • Image references (multi-add) │
|
||||
│ • Repository filters • Tag patterns (include/exclude) │
|
||||
│ • Tag patterns • Platform selection │
|
||||
│ • Webhook path (generated) • Scan options (analyzers, reachability) │
|
||||
│ │
|
||||
│ CLI: Git: │
|
||||
│ • Allowed tools list • Provider (GitHub/GitLab/Gitea/etc.) │
|
||||
│ • Allowed CI systems • Repository URL │
|
||||
│ • Validation rules • Branch patterns (include/exclude) │
|
||||
│ • Attribution requirements • Trigger modes (push/PR/tag/scheduled) │
|
||||
│ • Max SBOM size limit • Scan paths and exclusions │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 4: Credentials (AuthRef Pattern) │
|
||||
│ • Credential type selector (per source type) │
|
||||
│ • Create new credential vs. select existing │
|
||||
│ • Inline credential entry (stored via Authority vault) │
|
||||
│ • Webhook secret generation (for Zastava/Git) │
|
||||
│ • Test credential validity button │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 5: Schedule (Optional - for Docker/Git scheduled triggers) │
|
||||
│ • Enable scheduled scans toggle │
|
||||
│ • Cron expression builder or preset selector │
|
||||
│ • Timezone picker │
|
||||
│ • Rate limit (max scans per hour) │
|
||||
│ • Next run preview │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Step 6: Review & Test Connection │
|
||||
│ • Configuration summary (read-only) │
|
||||
│ • Test Connection button with status indicator │
|
||||
│ • Error details expansion if test fails │
|
||||
│ • Create Source / Save Changes button │
|
||||
│ • Webhook endpoint display (for Zastava/Git - copy to clipboard) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.3 Wizard Step Validation
|
||||
|
||||
| Step | Required Fields | Validation |
|
||||
|------|-----------------|------------|
|
||||
| 1 - Type | `sourceType` | Must select one type |
|
||||
| 2 - Basic Info | `name` | Unique name, 3-100 chars |
|
||||
| 3 - Config | Varies by type | Type-specific required fields |
|
||||
| 4 - Credentials | `authRef` (if required) | Valid credential for source type |
|
||||
| 5 - Schedule | None (optional step) | Valid cron if enabled |
|
||||
| 6 - Review | None | Connection test recommended |
|
||||
|
||||
### 9.4 Source List Page Components
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SBOM Sources [+ Add Source] │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ Status Cards: │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Active │ │ Paused │ │ Error │ │ Pending │ │
|
||||
│ │ 12 │ │ 3 │ │ 2 │ │ 1 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ Filters: [Type ▼] [Status ▼] [Tags ▼] [Search: ________] │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ □ │ Name │ Type │ Status │ Last Run │ Next Run │ Actions │
|
||||
│ ──┼───────────────┼─────────┼──────────┼────────────┼──────────┼─────────│
|
||||
│ □ │ DockerHub Prd │ Zastava │ ● Active │ 5 min ago │ - │ ⋮ │
|
||||
│ □ │ Harbor Dev │ Zastava │ ● Active │ 12 min ago │ - │ ⋮ │
|
||||
│ □ │ Nightly Scans │ Docker │ ● Active │ 2h ago │ 02:00 AM │ ⋮ │
|
||||
│ □ │ CI Pipeline │ CLI │ ⏸ Paused │ 1 day ago │ - │ ⋮ │
|
||||
│ □ │ Monorepo │ Git │ ⚠ Error │ Failed │ - │ ⋮ │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ Showing 1-5 of 18 [< Prev] [1] [2] [3] [4] [Next >] │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 9.5 Row Actions Menu
|
||||
|
||||
| Action | Icon | Availability |
|
||||
|--------|------|--------------|
|
||||
| View Details | 👁 | Always |
|
||||
| Edit | ✏️ | Always |
|
||||
| Trigger Scan | ▶ | Active sources |
|
||||
| Test Connection | 🔌 | Always |
|
||||
| Pause | ⏸ | Active sources |
|
||||
| Resume | ▶ | Paused sources |
|
||||
| Delete | 🗑 | Always (with confirmation) |
|
||||
|
||||
### 9.6 Source Detail Page Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ← Back to Sources │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ Docker Hub Production [Trigger] [Test] [Edit] [⋮] │
|
||||
│ Type: Zastava │ Status: ● Active │ Created: Dec 15, 2025 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ [Overview] [Configuration] [Run History] [Metrics] │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Configuration: │
|
||||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Registry URL: https://registry-1.docker.io │ │
|
||||
│ │ Registry Type: Docker Hub │ │
|
||||
│ │ Webhook Path: /api/v1/webhooks/zastava/src-abc123 │ │
|
||||
│ │ Repository Filter: myorg/*, prod-* │ │
|
||||
│ │ Tag Filter: v*, latest (excluding: *-dev, *-test) │ │
|
||||
│ │ Analyzers: OS, Node.js, Python, Go │ │
|
||||
│ │ Reachability: Enabled │ │
|
||||
│ │ VEX Lookup: Enabled │ │
|
||||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Run History: │
|
||||
│ ┌───────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Run ID │ Trigger │ Status │ Started │ Duration │ Items │ │
|
||||
│ │ run-456 │ Webhook │ ✓ Success │ 5 min ago │ 45s │ 3/3 │ │
|
||||
│ │ run-455 │ Webhook │ ✓ Success │ 12 min ago │ 38s │ 1/1 │ │
|
||||
│ │ run-454 │ Manual │ ✓ Success │ 1h ago │ 2m 15s │ 12/12 │ │
|
||||
│ │ run-453 │ Webhook │ ⚠ Partial │ 2h ago │ 1m 30s │ 4/5 │ │
|
||||
│ │ run-452 │ Schedule │ ✗ Failed │ 3h ago │ 12s │ 0/0 │ │
|
||||
│ └───────────────────────────────────────────────────────────────────────┘ │
|
||||
│ [Load More] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 10. Security Considerations
|
||||
|
||||
| Concern | Mitigation |
|
||||
|---------|------------|
|
||||
| Credential exposure | AuthRef pattern - never inline, always reference vault |
|
||||
| Webhook forgery | HMAC signature verification for all webhooks |
|
||||
| Unauthorized access | Scope-based authorization (`sources:read`, `sources:write`, `sources:admin`) |
|
||||
| Secret logging | Audit logging excludes credential values |
|
||||
| Webhook secret rotation | Rotate-on-demand API endpoint |
|
||||
|
||||
## 10. Configuration
|
||||
|
||||
Environment variables (prefix `SBOM_SbomService__Sources__`):
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `MaxSourcesPerTenant` | 100 | Maximum sources per tenant |
|
||||
| `DefaultMaxScansPerHour` | 60 | Default rate limit per source |
|
||||
| `RunHistoryRetentionDays` | 90 | Run history retention period |
|
||||
| `WebhookSignatureAlgorithm` | `HMAC-SHA256` | Webhook signature algorithm |
|
||||
| `ConnectionTestTimeoutSeconds` | 30 | Connection test timeout |
|
||||
|
||||
## 11. Related Documentation
|
||||
|
||||
- [SbomService Architecture](../architecture.md)
|
||||
- [Lineage Ledger](../lineage-ledger.md)
|
||||
- [Authority Architecture](../../authority/architecture.md)
|
||||
- [Scanner Architecture](../../scanner/architecture.md)
|
||||
- [UI Architecture](../../ui/architecture.md)
|
||||
Reference in New Issue
Block a user