up
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Test Deploy / build-test (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / authority-container (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / docs (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / deploy (push) Has been cancelled
				
			
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Test Deploy / build-test (push) Has been cancelled
				
			Build Test Deploy / authority-container (push) Has been cancelled
				
			Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -26,3 +26,9 @@ injecting environment-specific Mongo credentials and telemetry endpoints. Upcomi | |||||||
| releases will add Microsoft OAuth (Entra ID) authentication support—track the quickstart | releases will add Microsoft OAuth (Entra ID) authentication support—track the quickstart | ||||||
| for integration steps once available. | for integration steps once available. | ||||||
|  |  | ||||||
|  | ## Documentation | ||||||
|  |  | ||||||
|  | - `docs/README.md` now consolidates the platform index and points to the updated high-level architecture. | ||||||
|  | - Module architecture dossiers live under `docs/ARCHITECTURE_*.md`; the most relevant here are `docs/ARCHITECTURE_FEEDSER.md` (service layout, merge engine, exports) and `docs/ARCHITECTURE_CLI.md` (command surface, AOT packaging, auth flows). Related services such as the Signer, Attestor, Authority, Scanner, UI, Vexer, Zastava, and DevOps pipeline each have their own dossier. | ||||||
|  | - Offline operation guidance moved to `docs/24_OFFLINE_KIT.md`, which details bundle composition, verification, and delta workflows. Feedser-specific connector operations stay in `docs/ops/feedser-certbund-operations.md` and companion runbooks under `docs/ops/`. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,388 +1,430 @@ | |||||||
| # 7 · High‑Level Architecture — **Stella Ops** | Below is the **revised, consolidated** `high_level_architecture.md`. | ||||||
|  | It **absorbs** all content from `components.md` so you have a single, authoritative file. No separate components doc is required. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 0 Purpose & Scope   | # High‑Level Architecture — **Stella Ops** (Consolidated • 2025Q4) | ||||||
|  |  | ||||||
| Give contributors, DevOps engineers and auditors a **complete yet readable map** of the Core: | > **Purpose.** A complete, implementation‑ready map of Stella Ops: product vision, all runtime components, trust boundaries, tokens/licensing, control/data flows, storage, APIs, security, scale, DevOps, and verification logic. | ||||||
|  | > **Scope.** This file **replaces** the separate `components.md`; all component details now live here. | ||||||
| * Major runtime components and message paths.   |  | ||||||
| * Where plug‑ins, CLI helpers and runtime agents attach.   |  | ||||||
| * Technology choices that enable the sub‑5 second SBOM goal.   |  | ||||||
| * Typical operational scenarios (pipeline scan, mute, nightly re‑scan, etc.).   |  | ||||||
|  |  | ||||||
| Anything enterprise‑only (signed PDF, custom/regulated TLS, LDAP, enforcement) **must arrive as a plug‑in**; the Core never hard‑codes those concerns. |  | ||||||
| --- |  | ||||||
| ## 1 Component Overview   |  | ||||||
|  |  | ||||||
| | # | Component | Responsibility | |  | ||||||
| |---|-----------|---------------| |  | ||||||
| | 1 | **API Gateway** | REST endpoints (`/scan`, `/quota`, **`/token/offline`**); token auth; quota enforcement | |  | ||||||
| | 2 | **Scan Service** | SBOM parsing, Delta‑SBOM cache, vulnerability lookup | |  | ||||||
| | 3 | **Policy Engine** | YAML / (optional) Rego rule evaluation; verdict assembly | |  | ||||||
| | 4 | **Quota Service** | Per‑token counters; **333 scans/day**; waits & HTTP 429 | |  | ||||||
| | 5 | **Client‑JWT Issuer** | Issues 30‑day offline tokens; bundles them into OUK | |  | ||||||
| | 5 | **Registry** | Anonymous internal Docker registry for agents, SBOM uploads | |  | ||||||
| | 6 | **Web UI** | React/Blazor SPA; dashboards, policy editor, quota banner | |  | ||||||
| | 7 | **Data Stores** | **Redis** (cache, quota) & **MongoDB** (SBOMs, findings, audit) | |  | ||||||
| | 8 | **Plugin Host** | Hot‑load .NET DLLs; isolates community plug‑ins | |  | ||||||
| | 9 | **Agents** | `sbom‑builder`, `Stella CLI` scanner CLI, future `StellaOpsAttestor` | |  | ||||||
|  |  | ||||||
|  |  | ||||||
| ```mermaid |  | ||||||
| flowchart TD |  | ||||||
|     subgraph "External Actors" |  | ||||||
|         DEV["Developer / DevSecOps / Manager"] |  | ||||||
|         CI["CI/CD Pipeline (e.g., Stella CLI)"] |  | ||||||
|         K8S["Kubernetes Cluster (e.g., Zastava Agent)"] |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     subgraph "Stella Ops Runtime" |  | ||||||
|         subgraph "Core Services" |  | ||||||
|             CORE["Stella Core<br>(REST + gRPC APIs, Orchestration)"] |  | ||||||
|             REDIS[("Redis<br>(Cache, Queues, Trivy DB Mirror)")] |  | ||||||
|             MONGO[("MongoDB<br>(Optional: Long-term Storage)")] |  | ||||||
|             POL["Mute Policies<br>(OPA & YAML Evaluator)"] |  | ||||||
|             REG["StellaOps Registry<br>(Docker Registry v2)"] |  | ||||||
|             ATT["StellaOps Attestor<br>(SLSA + Rekor)"] |  | ||||||
|         end |  | ||||||
|  |  | ||||||
|         subgraph "Agents & Builders" |  | ||||||
|             SB["SBOM Builder<br>(Go Binary: Extracts Layers, Generates SBOMs)"] |  | ||||||
|             SA["Stella CLI<br>(Pipeline Helper: Invokes Builder, Triggers Scans)"] |  | ||||||
|             ZA["Zastava Agent<br>(K8s Webhook: Enforces Policies, Inventories Containers)"] |  | ||||||
|         end |  | ||||||
|  |  | ||||||
|         subgraph "Scanners & UI" |  | ||||||
|             TRIVY["Trivy Scanner<br>(Plugin Container: Vulnerability Scanning)"] |  | ||||||
|             UI["Web UI<br>(Vue3 + Tailwind: Dashboards, Policy Editor)"] |  | ||||||
|             CLI["Stella CLI<br>(CLI Helper: Triggers Scans, Mutes)"] |  | ||||||
|         end |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     DEV -->|Browses Findings, Mutes CVEs| UI |  | ||||||
|     DEV -->|Triggers Scans| CLI |  | ||||||
|     CI -->|Generates SBOM, Calls /scan| SA |  | ||||||
|     K8S -->|Inventories Containers, Enforces Gates| ZA |  | ||||||
|  |  | ||||||
|     UI -- "REST" --> CORE |  | ||||||
|     CLI -- "REST/gRPC" --> CORE |  | ||||||
|     SA -->|Scan Requests| CORE |  | ||||||
|     SB -->|Uploads SBOMs| CORE |  | ||||||
|     ZA -->|Policy Gates| CORE |  | ||||||
|  |  | ||||||
|     CORE -- "Queues, Caches" --> REDIS |  | ||||||
|     CORE -- "Persists Data" --> MONGO |  | ||||||
|     CORE -->|Evaluates Policies| POL |  | ||||||
|     CORE -->|Attests Provenance| ATT |  | ||||||
|     CORE -->|Scans Vulnerabilities| TRIVY |  | ||||||
|  |  | ||||||
|     SB -- "Pulls Images" --> REG |  | ||||||
|     SA -- "Pulls Images" --> REG |  | ||||||
|     ZA -- "Pulls Images" --> REG |  | ||||||
|  |  | ||||||
|     style DEV fill:#f9f,stroke:#333 |  | ||||||
|     style CI fill:#f9f,stroke:#333 |  | ||||||
|     style K8S fill:#f9f,stroke:#333 |  | ||||||
|     style CORE fill:#ddf,stroke:#333 |  | ||||||
|     style REDIS fill:#fdd,stroke:#333 |  | ||||||
|     style MONGO fill:#fdd,stroke:#333 |  | ||||||
|     style POL fill:#dfd,stroke:#333 |  | ||||||
|     style REG fill:#dfd,stroke:#333 |  | ||||||
|     style ATT fill:#dfd,stroke:#333 |  | ||||||
|     style SB fill:#fdf,stroke:#333 |  | ||||||
|     style SA fill:#fdf,stroke:#333 |  | ||||||
|     style ZA fill:#fdf,stroke:#333 |  | ||||||
|     style TRIVY fill:#ffd,stroke:#333 |  | ||||||
|     style UI fill:#ffd,stroke:#333 |  | ||||||
|     style CLI fill:#ffd,stroke:#333 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| * **Developer / DevSecOps / Manager** – browses findings, mutes CVEs, triggers scans.   |  | ||||||
| * **Stella CLI** – generates SBOMs and calls `/scan` during CI.   |  | ||||||
| * **Zastava Agent** – inventories live containers; Core ships it in *passive* mode only (no kill).   |  | ||||||
|  |  | ||||||
| ### 1.1 Client‑JWT Lifecycle (offline aware) |  | ||||||
|  |  | ||||||
| 1. **Online instance** – user signs in → `/connect/token` issues JWT valid 12 h.   |  | ||||||
| 2. **Offline instance** – JWT with `exp ≈ 30 days` ships in OUK; backend |  | ||||||
|    **re‑signs** and stores it during import.   |  | ||||||
| 3. Tokens embed a `tier` claim (“Free”) and `maxScansPerDay: 333`.   |  | ||||||
| 4. On expiry the UI surfaces a red toast **7 days** in advance. |  | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 2 · Component Responsibilities (runtime view) | ## 0) Product vision & principles | ||||||
|  |  | ||||||
| | Component                  | Core Responsibility                                                                                        | Implementation Highlights                                 | | **Vision.** Stella Ops is a **deterministic SBOM + VEX platform** for CI/CD and runtime, tuned for **speed** (per‑layer deltas), **quiet output** (usage‑scoped views), and **verifiability** (DSSE + Rekor v2). It is **self‑hostable**, **air‑gap capable**, and **commercially enforceable**: only licensed installations can produce **Stella Ops‑verified** attestations. | ||||||
| | -------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | |  | ||||||
| | **Stella Core**            | Orchestrates scans, persists SBOM blobs, serves REST/gRPC APIs, fans out jobs to scanners & policy engine. | .NET {{ dotnet }}, CQRS, Redis Streams; pluggable runner interfaces. | |  | ||||||
| | **SBOM Builder**           | Extracts image layers, queries Core for *missing* layers, generates SBOMs (multi‑format), uploads blobs.   | Go binary; wraps Trivy & Syft libs.                       | |  | ||||||
| | **Stella CLI**          | Pipeline‑side helper; invokes Builder, triggers scan, streams progress back to CI/CD.                      | Static musl build.                                        | |  | ||||||
| | **Zastava Agent**          | K8s admission webhook enforcing policy verdicts before Pod creation.                                       | Rust for sub‑10 ms latencies.                             | |  | ||||||
| | **UI**                     | Angular 17 SPA for dashboards, settings, policy editor.                                                    | Tailwind CSS; Webpack module federation (future).         | |  | ||||||
| | **Redis**                  | Cache, queue, Trivy‑DB mirror, layer diffing.                                                              | Single instance or Sentinel.                              | |  | ||||||
| | **MongoDB** (opt.)         | Long‑term SBOM & policy audit storage (> 180 days).                                                        | Optional; enabled via flag.                               | |  | ||||||
| | **StellaOps.Registry**     | Anonymous read‑only Docker v2 registry with optional Cosign verification.                                  | `registry :2` behind nginx reverse proxy.                 | |  | ||||||
| | **StellaOps.MutePolicies** | YAML/Rego evaluator, policy version store, `/policy/*` API.                                                | Embeds OPA‑WASM; falls back to `opa exec`.                | |  | ||||||
| | **StellaOpsAttestor**      | Generate SLSA provenance & Rekor signatures; verify on demand.                                             | Side‑car container; DSSE + Rekor CLI.                     | |  | ||||||
|  |  | ||||||
| All cross‑component calls use dependency‑injected interfaces—no | **Operating principles.** | ||||||
| intra‑component reach‑ins. |  | ||||||
|  | * **Scanner‑owned SBOMs.** We generate our own BOMs; we do not warehouse third‑party SBOM content (we can **link** to attested SBOMs). | ||||||
|  | * **Deterministic evidence.** Facts come from package DBs, installed metadata, linkers, and verified attestations; no fuzzy guessing in the core. | ||||||
|  | * **Per‑layer caching.** Cache fragments by **layer digest** and compose image SBOMs via **CycloneDX BOM‑Link** / **SPDX ExternalRef**. | ||||||
|  | * **Inventory vs Usage.** Always record the full **inventory** of what exists; separately present **usage** (entrypoint closure + loaded libs). | ||||||
|  | * **Backend decides.** PASS/FAIL is produced by **Policy** + **VEX** + **Advisories**. The scanner reports facts. | ||||||
|  | * **Attest or it didn’t happen.** Every export is signed as **in‑toto/DSSE** and logged in **Rekor v2**. | ||||||
|  | * **Sovereign‑ready.** Cloud is used only for licensing and optional endorsement; everything else is first‑party and self‑hostable. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 3 · Principal Backend Modules & Plug‑in Hooks   | ## 1) Service topology & trust boundaries | ||||||
|  |  | ||||||
| | Namespace       | Responsibility                                     | Built‑in Tech / Default | Plug‑in Contract                                  | | ### 1.1 Runtime inventory (first‑party) | ||||||
| | --------------- | -------------------------------------------------- | ----------------------- | ------------------------------------------------- | |  | ||||||
| | `configuration` | Parse env/JSON, health‑check endpoint              | .NET {{ dotnet }} Options | `IConfigValidator`                                | |  | ||||||
| | `identity`      | Embedded OAuth2/OIDC (OpenIddict 6)                | MIT OpenIddict          | `IIdentityProvider` for LDAP/SAML/JWT gateway     | |  | ||||||
| | `pluginloader`  | Discover DLLs, SemVer gate, optional Cosign verify | Reflection + Cosign     | `IPluginLifecycleHook` for telemetry              | |  | ||||||
| | `scanning`      | SBOM‑ & image‑flow orchestration; runner pool      | Trivy CLI (default)     | `IScannerRunner` – e.g., Grype, Copacetic, Clair  | |  | ||||||
| | `feedser` (vulnerability ingest/merge/export service) | Nightly NVD merge & feed enrichment                | Hangfire job            | drop-in `*.Schedule.dll` for OSV, GHSA, NVD 2.0, CNNVD, CNVD, ENISA, JVN and BDU feeds | |  | ||||||
| | `tls`           | TLS provider abstraction                           | OpenSSL                 | `ITlsProvider` for custom suites (incl. **SM2**, where law or security requires it) | |  | ||||||
| | `reporting`     | Render HTML/PDF reports                            | RazorLight              | `IReportRenderer`                                 | |  | ||||||
| | `ui`            | Angular SPA & i18n                                 | Angular {{ angular }}   | new locales via `/locales/{lang}.json`            | |  | ||||||
| | `scheduling`    | Cron + retries                                     | Hangfire                | any recurrent job via `*.Schedule.dll`            | |  | ||||||
|  |  | ||||||
| ```mermaid | | Service / Tool                  | Container image            | Core role                                                                                                                                   | Scale pattern                                      | | ||||||
| classDiagram | | ------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | | ||||||
|     class configuration | | **Scanner.WebService**          | `stellaops/scanner-web`    | Control plane for scans; catalog; SBOM composition (inventory & usage); diff; exports.                                                      | Stateless; N replicas behind LB.                   | | ||||||
|     class identity | | **Scanner.Worker**              | `stellaops/scanner-worker` | Runs analyzers (OS, Lang: Java/Node/Python/Go/.NET/Rust, Native ELF/PE/Mach‑O, EntryTrace); emits per‑layer SBOMs and composes image SBOMs. | Horizontal; queue‑driven; sharded by layer digest. | | ||||||
|     class pluginloader | | **Scanner.Sbomer.BuildXPlugin** | `stellaops/sbom-indexer`   | BuildKit **generator** for build‑time SBOMs as OCI **referrers**.                                                                           | CI‑side; ephemeral.                                | | ||||||
|     class scanning | | **Scanner.Sbomer.DockerImage**  | `stellaops/scanner-cli`    | CLI‑orchestrated scanner container for post‑build scans.                                                                                    | Local/CI; ephemeral.                               | | ||||||
|     class feedser | | **Feedser.WebService**          | `stellaops/feedser-web`    | Vulnerability ingest/normalize/merge/export (JSON + Trivy DB).                                                                              | HA via Mongo locks.                                | | ||||||
|     class tls | | **Vexer.WebService**            | `stellaops/vexer-web`      | VEX ingest/normalize/consensus; conflict retention; exports.                                                                                | HA via Mongo locks.                                | | ||||||
|     class reporting | | **Policy Engine**               | (in `scanner-web`)         | YAML DSL evaluator (waivers, vendor preferences, KEV/EPSS, license, usage‑gating); produces **policy digest**.                              | In‑process; cache per digest.                      | | ||||||
|     class ui | | **Signer**                      | `stellaops/signer`         | **Hard gate:** validates entitlement + release integrity; mints signing cert (Fulcio keyless) or uses KMS; signs DSSE.                      | Stateless; HPA by QPS.                             | | ||||||
|     class scheduling | | **Attestor**                    | `stellaops/attestor`       | Posts DSSE bundles to **Rekor v2**; verification endpoints.                                                                                 | Stateless; HPA by QPS.                             | | ||||||
|  | | **Authority**                   | `stellaops/authority`      | On‑prem OIDC issuing **short‑lived OpToks** with DPoP/mTLS sender constraint.                                                               | HA behind LB.                                      | | ||||||
|  | | **Zastava** (Runtime)           | `stellaops/zastava`        | Runtime inspector/enforcer (observer + optional Admission Webhook).                                                                         | DaemonSet + Webhook.                               | | ||||||
|  | | **Web UI**                      | `stellaops/ui`             | Angular app for scans, diffs, policy, VEX, runtime, reports.                                                                                | Stateless.                                         | | ||||||
|  | | **StellaOps.Cli**               | `stellaops/cli`            | CLI for init/scan/export/diff/policy/report/verify; Buildx helper.                                                                          | Local/CI.                                          | | ||||||
|  |  | ||||||
|     class AllModules | ### 1.2 Third‑party (self‑hosted) | ||||||
|  |  | ||||||
|     configuration ..> identity : Uses | * **Fulcio** (Sigstore CA) — issues short‑lived signing certs (keyless). | ||||||
|     identity ..> pluginloader : Authenticates Plugins | * **Rekor v2** (tile‑backed transparency log). | ||||||
|     pluginloader ..> scanning : Loads Scanner Runners | * **MinIO** — S3‑compatible object store with lifecycle & Object Lock. | ||||||
|     scanning ..> feedser : Triggers Feed Merges | * **MongoDB** — catalog, advisories, VEX. | ||||||
|     tls ..> AllModules : Provides TLS Abstraction | * **Queue** — Redis Streams / NATS / RabbitMQ (pluggable). | ||||||
|     reporting ..> ui : Renders Reports for UI | * **OCI Registry** — must support **Referrers API** (discover SBOMs/signatures). | ||||||
|     scheduling ..> feedser : Schedules Nightly Jobs |  | ||||||
|  |  | ||||||
|     note for scanning "Pluggable: ISScannerRunner<br>e.g., Trivy, Grype" | ### 1.3 Cloud licensing (Stella Ops) | ||||||
|     note for feedser "Pluggable: *.Schedule.dll<br>e.g., OSV, GHSA Feeds" |  | ||||||
|     note for identity "Pluggable: IIdentityProvider<br>e.g., LDAP, SAML" |  | ||||||
|     note for reporting "Pluggable: IReportRenderer<br>e.g., Custom PDF" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| **When remaining = 0:**   | * **Licensing Service** (`www.stella-ops.org`) — issues long‑lived **License Tokens (LT)**; exchanges LT → **Proof‑of‑Entitlement (PoE)** bound to an installation key; revoke/introspect PoE; optional cross‑log **endorsement**. | ||||||
| API returns `429 Too Many Requests`, `Retry‑After: <UTC‑midnight>` (sequence omitted for brevity). |  | ||||||
|  |  | ||||||
| --- | ### 1.4 Diagram (control/data planes & trust) | ||||||
|  |  | ||||||
| ## 4 · Data Flows   |  | ||||||
|  |  | ||||||
| ### 4.1 SBOM‑First (≤ 5 s P95)   |  | ||||||
|  |  | ||||||
| Builder produces SBOM locally, so Core never touches the Docker |  | ||||||
| socket. |  | ||||||
| Trivy path hits ≤ 5 s on alpine:3.19 with warmed DB. |  | ||||||
| Image‑unpack fallback stays ≤ 10 s for 200 MB images. |  | ||||||
|  |  | ||||||
| ```mermaid |  | ||||||
| sequenceDiagram |  | ||||||
|     participant CI as CI/CD Pipeline (Stella CLI) |  | ||||||
|     participant SB as SBOM Builder |  | ||||||
|     participant CORE as Stella Core |  | ||||||
|     participant REDIS as Redis Queue |  | ||||||
|     participant RUN as Scanner Runner (e.g., Trivy) |  | ||||||
|     participant POL as Policy Evaluator |  | ||||||
|  |  | ||||||
|     CI->>SB: Invoke SBOM Generation |  | ||||||
|     SB->>CORE: Check Missing Layers (/layers/missing) |  | ||||||
|     CORE->>REDIS: Query Layer Diff (SDIFF) |  | ||||||
|     REDIS-->>CORE: Missing Layers List |  | ||||||
|     CORE-->>SB: Return Missing Layers |  | ||||||
|     SB->>SB: Generate Delta SBOM |  | ||||||
|     SB->>CORE: Upload SBOM Blob (POST /scan(sbom)) |  | ||||||
|     CORE->>REDIS: Enqueue Scan Job |  | ||||||
|     REDIS->>RUN: Fan Out to Runner |  | ||||||
|     RUN->>RUN: Perform Vulnerability Scan |  | ||||||
|     RUN-->>CORE: Return Scan Results |  | ||||||
|     CORE->>POL: Evaluate Mute Policies |  | ||||||
|     POL-->>CORE: Policy Verdict |  | ||||||
|     CORE-->>CI: JSON Verdict & Progress Stream |  | ||||||
|     Note over CORE,CI: Achieves ≤5s P95 with Warmed DB |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 4.2 Delta SBOM |  | ||||||
|  |  | ||||||
| Builder collects layer digests. |  | ||||||
| `POST /layers/missing` → Redis SDIFF → missing layer list (< 20 ms). |  | ||||||
| SBOM generated only for those layers and uploaded. |  | ||||||
|  |  | ||||||
| ### 4.3 Feedser Harvest & Export |  | ||||||
|  |  | ||||||
| ```mermaid |  | ||||||
| sequenceDiagram |  | ||||||
|     participant SCHED as Feedser Scheduler |  | ||||||
|     participant CONN as Source Connector Plug-in |  | ||||||
|     participant FEEDSER as Feedser Core |  | ||||||
|     participant MONGO as MongoDB (Canonical Advisories) |  | ||||||
|     participant EXPORT as Exporter (JSON / Trivy DB) |  | ||||||
|     participant ART as Artifact Store / Offline Kit |  | ||||||
|  |  | ||||||
|     SCHED->>CONN: Trigger window (init/resume) |  | ||||||
|     CONN->>CONN: Fetch source documents + metadata |  | ||||||
|     CONN->>FEEDSER: Submit raw document for parsing |  | ||||||
|     FEEDSER->>FEEDSER: Parse & normalize to DTO |  | ||||||
|     FEEDSER->>FEEDSER: Merge & deduplicate canonical advisory |  | ||||||
|     FEEDSER->>MONGO: Write advisory, provenance, merge_event |  | ||||||
|     FEEDSER->>EXPORT: Queue export delta request |  | ||||||
|     EXPORT->>MONGO: Read canonical snapshot/deltas |  | ||||||
|     EXPORT->>EXPORT: Build deterministic JSON & Trivy DB artifacts |  | ||||||
|     EXPORT->>ART: Publish artifacts / Offline Kit bundle |  | ||||||
|     ART-->>FEEDSER: Record export state + digests |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 4.4 Identity & Auth Flow |  | ||||||
|  |  | ||||||
| OpenIddict issues JWTs via client‑credentials or password grant. |  | ||||||
| An IIdentityProvider plug‑in can delegate to LDAP, SAML or external OIDC |  | ||||||
| without Core changes. |  | ||||||
| --- |  | ||||||
| ## 5 · Runtime Helpers   |  | ||||||
|  |  | ||||||
| | Helper    | Form                                  | Purpose                                                            | Extensible Bits                            | |  | ||||||
| |-----------|---------------------------------------|--------------------------------------------------------------------|-------------------------------------------| |  | ||||||
| | **Stella CLI** | Distroless CLI                       | Generates SBOM, calls `/scan`, honours threshold flag              | `--engine`, `--pdf-out` piped to plug‑ins | |  | ||||||
| | **Zastava** | Static Go binary / DaemonSet         | Watches Docker/CRI‑O events; uploads SBOMs; can enforce gate       | Policy plug‑in could alter thresholds     | |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 6 · Persistence & Cache Strategy   |  | ||||||
|  |  | ||||||
| | Store          | Primary Use                                   | Why chosen                     | |  | ||||||
| |----------------|-----------------------------------------------|--------------------------------| |  | ||||||
| | **MongoDB**    | Feedser canonical advisories, merge events, export state | Deterministic canonical store with flexible schema | |  | ||||||
| | **Redis 7**    | CLI quotas, short-lived job scheduling, layer diff cache | Sub-1 ms P99 latency for hot-path coordination | |  | ||||||
| | **Local tmpfs**| Trivy layer cache (`/var/cache/trivy`)        | Keeps disk I/O off hot path    | |  | ||||||
|  |  | ||||||
| ```mermaid | ```mermaid | ||||||
| flowchart LR | flowchart LR | ||||||
|     subgraph "Persistence Layers" |   subgraph Cloud["www.stella-ops.org (Cloud)"] | ||||||
|         REDIS[(Redis: Quotas & Short-lived Queues<br>Sub-1ms P99)] |     LS[Licensing Service<br/>LT→PoE / revoke / introspect] | ||||||
|         MONGO[(MongoDB: Canonical Advisories<br>Merge Events & Export State)] |  | ||||||
|         TMPFS[(Local tmpfs: Trivy Layer Cache<br>Low I/O Overhead)] |  | ||||||
|   end |   end | ||||||
|  |  | ||||||
|     CORE["Stella Core"] -- Queues & SBOM Cache --> REDIS |   subgraph OnPrem["Customer Site (Self-hosted)"] | ||||||
|     CORE -- Long-term Storage --> MONGO |     Auth[Authority (OIDC)\nOpTok (DPoP/mTLS)] | ||||||
|     TRIVY["Trivy Scanner"] -- Layer Unpack Cache --> TMPFS |     SW[Scanner.WebService] | ||||||
|  |     WK[Scanner.Worker xN] | ||||||
|  |     FEED[Feedser] | ||||||
|  |     VEX[Vexer] | ||||||
|  |     POL[Policy Engine (in Scanner.Web)] | ||||||
|  |     SGN[Signer\n(entitlement + signing)] | ||||||
|  |     ATT[Attestor\n(Rekor v2 submit/verify)] | ||||||
|  |     UI[Web UI (Angular)] | ||||||
|  |     Z[Zastava\n(Runtime Inspector/Enforcer)] | ||||||
|  |     MIN[(MinIO S3)] | ||||||
|  |     MGO[(MongoDB)] | ||||||
|  |     QUE[(Queue/Streams)] | ||||||
|  |   end | ||||||
|  |  | ||||||
|     style REDIS fill:#fdd,stroke:#333 |   CLI[StellaOps.Cli / Buildx Plugin] | ||||||
|     style MONGO fill:#dfd,stroke:#333 |   REG[(OCI Registry with Referrers)] | ||||||
|     style TMPFS fill:#ffd,stroke:#333 |   FUL[ Fulcio ] | ||||||
|  |   REK[ Rekor v2 (tiles) ] | ||||||
|  |  | ||||||
|  |   CLI -->|scan/build| SW | ||||||
|  |   SW -->|jobs| QUE | ||||||
|  |   QUE --> WK | ||||||
|  |   WK --> MIN | ||||||
|  |   SW --> MGO | ||||||
|  |   FEED --> MGO | ||||||
|  |   VEX --> MGO | ||||||
|  |   UI --> SW | ||||||
|  |   Z --> SW | ||||||
|  |  | ||||||
|  |   SGN <--> Auth | ||||||
|  |   SGN --> FUL | ||||||
|  |   SGN -->|mTLS| ATT | ||||||
|  |   ATT --> REK | ||||||
|  |  | ||||||
|  |   SGN <-->|verify referrers| REG | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Trust boundaries.** Only **Signer** can sign; only **Attestor** can write to **Rekor v2**. Scanner/UI never sign. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Licensing & tokens (installation‑ready, theft‑resistant) | ||||||
|  |  | ||||||
|  | **Two‑token model.** | ||||||
|  |  | ||||||
|  | * **License Token (LT)** — long‑lived JWT from **Licensing Service**; used **once** to enroll the installation; never used in hot path. | ||||||
|  | * **Proof‑of‑Entitlement (PoE)** — bound to the installation key (mTLS client cert **or** DPoP‑bound JWT with `cnf`); medium‑lived; renewable; revocable. | ||||||
|  | * **Operational token (OpTok)** — 2–5 min OIDC token from **Authority**, **sender‑constrained** (DPoP or mTLS). Used to authenticate to **Signer**/**Scanner.WebService**. | ||||||
|  |  | ||||||
|  | **Signer enforces both:** PoE proves entitlement; OpTok proves “who is calling now”. It also **independently verifies** the **scanner image digest** is **Stella Ops‑signed** via **Referrers + cosign** before signing anything. | ||||||
|  |  | ||||||
|  | **Enrollment sequence (LT → PoE).** | ||||||
|  |  | ||||||
|  | ```plantuml | ||||||
|  | @startuml | ||||||
|  | actor Operator | ||||||
|  | participant "Install Agent" as IA | ||||||
|  | participant "Licensing Service" as LS | ||||||
|  | Operator -> IA: Provide LT | ||||||
|  | IA -> IA: Generate K_inst | ||||||
|  | IA -> LS: /license/enroll {LT, pub(K_inst)} | ||||||
|  | LS --> IA: PoE (mTLS client cert or JWT with cnf=K_inst), CRL/OCSP/introspect | ||||||
|  | @enduml | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 7 · Typical Scenarios   | ## 3) Scanner subsystem (facts engine) | ||||||
|  |  | ||||||
| | #       | Flow                       | Steps                                                                                           | | ### 3.1 Analyzers (deterministic only) | ||||||
| |---------|----------------------------|-------------------------------------------------------------------------------------------------| |  | ||||||
| | **S‑1** | Pipeline Scan & Alert     | Stella CLI → SBOM → `/scan` → policy verdict → CI exit code & link to *Scan Detail*                | | * **OS packages:** apk/dpkg/rpm (Linux); Windows MSI/SxS/GAC (M2). | ||||||
| | **S‑2** | Mute Noisy CVE            | Dev toggles **Mute** in UI → rule stored in Redis → next build passes                           | | * **Language (installed state):** | ||||||
| | **S‑3** | Nightly Re‑scan           | `SbomNightly.Schedule` re‑queues SBOMs (mask‑filter) → dashboard highlights new Criticals       | |  | ||||||
| | **S‑4** | Feed Update Cycle         | `Feedser (vulnerability ingest/merge/export service)` refreshes feeds → UI *Feed Age* tile turns green | |   * Java (pom.properties / MANIFEST) → `pkg:maven/...` | ||||||
| | **S‑5** | Custom Report Generation  | Plug‑in registers `IReportRenderer` → `/report/custom/{digest}` → CI downloads artifact         | |   * Node (`node_modules/*/package.json`) → `pkg:npm/...` | ||||||
|  |   * Python (`*.dist-info/METADATA`) → `pkg:pypi/...` | ||||||
|  |   * Go (buildinfo) → `pkg:golang/...` | ||||||
|  |   * .NET (`*.deps.json`) → `pkg:nuget/...` | ||||||
|  |   * **Rust:** deterministic **language markers** (symbol mangling) and crates only when present; otherwise `bin:{sha256}`. | ||||||
|  | * **Native:** ELF/PE/Mach‑O imports, DT_NEEDED, RPATH/RUNPATH, symbol versions, PE version info. | ||||||
|  | * **EntryTrace:** parse `ENTRYPOINT`/`CMD`; shell AST; resolve launchers (Java/Node/Python) to terminal program; record file:line chain. | ||||||
|  |  | ||||||
|  | ### 3.2 Caching & composition | ||||||
|  |  | ||||||
|  | * **Layer cache:** `{layerDigest → SBOM fragment + analyzer meta}`. | ||||||
|  | * **File CAS:** `{sha256(file) → parse result (ELF/JAR metadata/etc.)}`. | ||||||
|  | * **Composition:** build **image SBOMs** from fragments via **BOM‑Link/ExternalRef**; emit **two views**: | ||||||
|  |  | ||||||
|  |   * **Inventory** (complete filesystem inventory). | ||||||
|  |   * **Usage** (entrypoint closure + linked libs). | ||||||
|  | * **Transport:** JSON **and** **CycloneDX Protobuf** (compact, fast to parse). | ||||||
|  | * **Index:** BOM‑Index sidecar with purl table + roaring bitmap + `usedByEntrypoint` flag for fast joins. | ||||||
|  |  | ||||||
|  | ### 3.3 Diff (image → layer → package) | ||||||
|  |  | ||||||
|  | * Added / Removed / Version‑changed changes, **attributed** to the layer that caused them. | ||||||
|  | * Raw diffs preserved; backend view applies **VEX + Policy**. | ||||||
|  |  | ||||||
|  | ### 3.4 Build‑time SBOMs (fast CI path) | ||||||
|  |  | ||||||
|  | * Buildx **generator** runs analyzers during `docker buildx build --attest=type=sbom,generator=stellaops/sbom-indexer`, attaches SBOMs as **OCI referrers**. | ||||||
|  | * Scanner.WebService can trust these (policy‑configurable) and **skip** re‑scan; DSSE + Rekor v2 can be done either at build time or post‑push via Signer/Attestor. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Backend evaluation (decider) | ||||||
|  |  | ||||||
|  | ### 4.1 Feedser (advisories) | ||||||
|  |  | ||||||
|  | * Ingests vendor, distro, OSS feeds; normalizes & merges; persists canonical advisories in Mongo; exports **deterministic JSON** and **Trivy DB**. | ||||||
|  | * Offline kit bundles for air‑gapped sites. | ||||||
|  |  | ||||||
|  | ### 4.2 Vexer (VEX) | ||||||
|  |  | ||||||
|  | * Ingests **OpenVEX / CSAF VEX / CycloneDX VEX**; normalizes claims; retains conflicts; computes **consensus** with provider trust weights and justification gates. | ||||||
|  |  | ||||||
|  | ### 4.3 Policy Engine (YAML DSL) | ||||||
|  |  | ||||||
|  | * Matchers: `image/repo/env/purl/cve/vendor/source/path/layerDigest/usedByEntrypoint` | ||||||
|  | * Actions: `ignore(until, justification)`, `fail`, `warn`, `defer`, `requireVEX{vendors, justifications}`, `escalate {sev, KEV, EPSS}`, license constraints. | ||||||
|  | * Produces a **policy digest** (SHA‑256 of canonicalized policy). | ||||||
|  |  | ||||||
|  | ### 4.4 PASS/FAIL flow | ||||||
|  |  | ||||||
|  | 1. SBOM (Inventory / Usage) → join with **Feedser** advisories. | ||||||
|  | 2. Apply **Vexer** consensus (statuses & justifications). | ||||||
|  | 3. Apply **Policy**; compute PASS/FAIL with waiver TTLs. | ||||||
|  | 4. Sign the **final report** (DSSE via **Signer**) and log to **Rekor v2** via **Attestor**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Runtime enforcement (Zastava) | ||||||
|  |  | ||||||
|  | * **Observer:** inventories running containers, checks image signatures, SBOM presence (referrers), detects drift (entrypoint chain divergence), flags unapproved images. | ||||||
|  | * **Admission Webhook (optional):** blocks policy‑fail pods (dry‑run first). | ||||||
|  | * **Integration:** posts runtime events to Scanner.WebService; can request **delta scans** on changed layers. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Storage & catalogs (MinIO/Mongo) | ||||||
|  |  | ||||||
|  | **MinIO layout** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | s3://stellaops/ | ||||||
|  |   layers/<sha256>/sbom.cdx.json.zst | ||||||
|  |   layers/<sha256>/sbom.spdx.json.zst | ||||||
|  |   images/<imgDigest>/inventory.cdx.pb | ||||||
|  |   images/<imgDigest>/usage.cdx.pb | ||||||
|  |   indexes/<imgDigest>/bom-index.bin | ||||||
|  |   attest/<artifactSha256>.dsse.json | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Catalog (Mongo)** | ||||||
|  |  | ||||||
|  | * `artifacts` (type/format/sha/size/rekor/ttl/immutable/refCount/createdAt) | ||||||
|  | * `images`, `layers`, `links`, `lifecycleRules` | ||||||
|  |  | ||||||
|  | **Retention** | ||||||
|  |  | ||||||
|  | * MinIO **ILM** for coarse TTL; Scanner.WebService GC decrements `refCount` and deletes unreferenced metadata; **Object Lock** for immutable classes (auditable artifacts). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) APIs (consolidated surface) | ||||||
|  |  | ||||||
|  | ### 7.1 Scanner.WebService | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /api/scans                         { imageRef|digest, force? } → { scanId } | ||||||
|  | GET  /api/scans/{id}                    → { status, digests, artifacts[] } | ||||||
|  | GET  /api/sboms/{imageDigest}           ?format=cdx-json|cdx-pb|spdx-json&view=inventory|usage | ||||||
|  | GET  /api/diff?old=<digest>&new=<digest> → { added[], removed[], changed[], byLayer[] } | ||||||
|  | POST /api/exports                       { imageDigest, format, view } → { artifactId, rekorUrl } | ||||||
|  | POST /api/reports                       { imageDigest, policyRevision? } → { reportId, rekorUrl } | ||||||
|  | GET  /api/catalog/artifacts/{id}        → { size, ttl, immutable, rekor, refs } | ||||||
|  | GET  /healthz | /readyz | /metrics | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.2 Signer (mTLS; hard gate) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /sign/dsse    # body: {subjectHash, imageDigest, predicate}; headers: OpTok (DPoP/mTLS) + PoE | ||||||
|  | GET  /verify/referrers?imageDigest=sha256:...  # is this image StellaOps-signed? | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.3 Attestor (mTLS) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /rekor/entries      # DSSE bundle → {uuid, index, proof, logURL} | ||||||
|  | GET  /rekor/entries/{uuid} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.4 Authority (OIDC) | ||||||
|  |  | ||||||
|  | * `/.well-known/openid-configuration`, `/oauth/token` (DPoP/mTLS), `/oauth/introspect`, `/jwks` | ||||||
|  |  | ||||||
|  | ### 7.5 Licensing (cloud) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /license/enroll      { LT, pubKey }           → PoE + introspection endpoints | ||||||
|  | POST /license/revoke      { license_id }           → ok | ||||||
|  | POST /license/introspect  { poe }                  → { active, claims, exp } | ||||||
|  | POST /attest/endorse      { bundle }               → endorsement bundle (optional) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Security & verifiability | ||||||
|  |  | ||||||
|  | * **Sender‑constrained tokens.** All operational calls use **DPoP** (RFC 9449) or **mTLS‑bound** tokens (RFC 8705). | ||||||
|  | * **Entitlement.** **PoE** is mandatory; revocation honored online. | ||||||
|  | * **Release integrity.** **Signer** independently verifies **scanner image digest** via **Referrers + cosign** before signing. | ||||||
|  | * **Separation of duties.** Scanner/UI cannot sign; only **Signer** can sign; only **Attestor** can write to **Rekor v2**. | ||||||
|  | * **Verifiers.** Anyone can verify: DSSE signature → certificate chain to **Stella Ops Fulcio/KMS root** → **Rekor v2** inclusion. | ||||||
|  | * **Community vs Authorized.** Free/community runs throttled with no official attestations; authorized runs full speed and produce **Stella Ops‑verified** bundles. | ||||||
|  |  | ||||||
|  | **DSSE predicate (SBOM/report)** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "predicateType": "https://stella-ops.org/attestations/sbom/1", | ||||||
|  |   "subject": [{ "name": "s3://stellaops/images/<digest>/inventory.cdx.pb", "digest": { "sha256": "<sha256>" } }], | ||||||
|  |   "predicate": { | ||||||
|  |     "image_digest": "<sha256:...>", | ||||||
|  |     "stellaops_version": "2.3.1 (2027.04)", | ||||||
|  |     "license_id": "LIC-9F2A...", | ||||||
|  |     "customer_id": "CUST-ACME", | ||||||
|  |     "plan": "pro", | ||||||
|  |     "policy_digest": "sha256:...", | ||||||
|  |     "views": ["inventory","usage"], | ||||||
|  |     "created": "2025-10-17T12:34:56Z" | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **BOM‑Index sidecar** | ||||||
|  | Binary header + purl table + roaring bitmaps; optional `usedByEntrypoint` flags for fast policy joins. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Scale, performance & quotas | ||||||
|  |  | ||||||
|  | * **Workers:** horizontal; **distributed lock per layer digest**; global CAS in MinIO. | ||||||
|  | * **Queues:** Redis Streams / NATS / RabbitMQ. HPA by queue depth, CPU, memory. | ||||||
|  | * **Registry throttling:** per‑registry concurrency budgets. | ||||||
|  | * **Targets:** | ||||||
|  |  | ||||||
|  |   * Build‑time path P95 ≤ 3–5 s on warmed bases. | ||||||
|  |   * Post‑build delta scan P95 ≤ 10 s for 200 MB images. | ||||||
|  |   * Policy + VEX evaluation ≤ 500 ms for 5k components using BOM‑Index. | ||||||
|  | * **Quotas:** license plan enforces QPS/concurrency/size; **Signer** throttles and can deny DSSE. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) DevOps & distribution | ||||||
|  |  | ||||||
|  | * **Releases:** all first‑party images **cosign‑signed**; labels embed `org.stellaops.version` and `org.stellaops.release_date`. | ||||||
|  | * **Channels:** | ||||||
|  |  | ||||||
|  |   * **Community** (public registry): throttled, non‑attesting. | ||||||
|  |   * **Authorized** (private registry): full speed, DSSE enabled. | ||||||
|  | * **Client update flow:** containers self‑verify signatures at boot; report version; **Signer** enforces `valid_release_year` / `max_version` from PoE before signing. | ||||||
|  | * **Compose skeleton:** | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | services: | ||||||
|  |   authority:  { image: stellaops/authority } | ||||||
|  |   fulcio:     { image: sigstore/fulcio } | ||||||
|  |   rekor:      { image: sigstore/rekor-v2 } | ||||||
|  |   minio:      { image: minio/minio, command: server /data --console-address ":9001" } | ||||||
|  |   mongo:      { image: mongo:7 } | ||||||
|  |   signer:     { image: stellaops/signer, depends_on: [authority, fulcio] } | ||||||
|  |   attestor:   { image: stellaops/attestor, depends_on: [rekor, signer] } | ||||||
|  |   scanner-web:{ image: stellaops/scanner-web, depends_on: [mongo, minio, signer, attestor] } | ||||||
|  |   scanner-worker: | ||||||
|  |     image: stellaops/scanner-worker | ||||||
|  |     deploy: { replicas: 4 } | ||||||
|  |     depends_on: [scanner-web] | ||||||
|  |   feedser:    { image: stellaops/feedser-web, depends_on: [mongo] } | ||||||
|  |   vexer:      { image: stellaops/vexer-web, depends_on: [mongo] } | ||||||
|  |   ui:         { image: stellaops/ui, depends_on: [scanner-web, feedser, vexer] } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | * **Backups:** Mongo dumps; MinIO versioned buckets & replication; Rekor v2 DB snapshots; JWKS/Fulcio/KMS key rotation. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Observability & audit | ||||||
|  |  | ||||||
|  | * **Metrics:** scan latency, layer cache hit %, artifact bytes, DSSE/Rekor latency, policy evaluation time, queue depth, admission decisions (Zastava). | ||||||
|  | * **Tracing:** per‑stage spans; correlation IDs across Scanner→Signer→Attestor. | ||||||
|  | * **Audit logs:** every signing records `license_id`, `image_digest`, `policy_digest`, and Rekor UUID. | ||||||
|  | * **Compliance:** MinIO **Object Lock** for immutable artifacts; reproducible outputs via policy digest + SBOM digest in predicate. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Roadmap (anchored to this architecture) | ||||||
|  |  | ||||||
|  | * M2: Windows MSI/SxS/GAC analyzers; deeper Rust (DWARF enrichers). | ||||||
|  | * M2: Buildx generator certified flows; cross‑registry trust policies. | ||||||
|  | * M3: Patch‑Presence plugin (signature‑based backport detection), opt‑in. | ||||||
|  | * M3: Zastava Admission control GA with policy presets and dry‑run→enforce stages. | ||||||
|  | * Continuous: Policy UX (waiver TTLs, vendor rules), Vexer connectors expansion. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Canonical sequences (verification & signing) | ||||||
|  |  | ||||||
|  | **Sign & log (OpTok + PoE, image verify, DSSE, Rekor).** | ||||||
|  |  | ||||||
| ```mermaid | ```mermaid | ||||||
| sequenceDiagram | sequenceDiagram | ||||||
|     participant DEV as Developer |   autonumber | ||||||
|     participant UI as Web UI |   participant Scan as Scanner.WebService | ||||||
|     participant CORE as Stella Core |   participant Auth as Authority (OIDC) | ||||||
|     participant REDIS as Redis |   participant Sign as Signer | ||||||
|     participant RUN as Scanner Runner |   participant Reg as OCI Registry | ||||||
|  |   participant Ful as Fulcio/KMS | ||||||
|  |   participant Att as Attestor | ||||||
|  |   participant Rek as Rekor v2 | ||||||
|  |  | ||||||
|     DEV->>UI: Toggle Mute for CVE |   Scan->>Auth: Get OpTok (DPoP/mTLS) | ||||||
|     UI->>CORE: Update Mute Rule (POST /policy/mute) |   Scan->>Sign: sign(request) + OpTok + PoE + DPoP proof | ||||||
|     CORE->>REDIS: Store Mute Policy |   Sign->>Auth: Validate OpTok & sender-constraint | ||||||
|     Note over CORE,REDIS: YAML/Rego Evaluator Updates |   Sign->>Sign: Validate PoE (introspect/revocation) | ||||||
|  |   Sign->>Reg: Verify scanner image is StellaOps-signed (Referrers + cosign) | ||||||
|     alt Next Pipeline Build |   alt OK | ||||||
|         CI->>CORE: Trigger Scan (POST /scan) |     Sign->>Ful: Get signing cert (keyless) or use KMS key | ||||||
|         CORE->>RUN: Enqueue & Scan |     Sign-->>Scan: DSSE bundle (cert chain) | ||||||
|         RUN-->>CORE: Raw Findings |     Scan->>Att: Submit bundle | ||||||
|         CORE->>REDIS: Apply Mute Policies |     Att-->>Rek: Create entry | ||||||
|         REDIS-->>CORE: Filtered Verdict (Passes) |     Rek-->>Att: {uuid,index,proof} | ||||||
|         CORE-->>CI: Success Exit Code |     Att-->>Scan: Rekor URL | ||||||
|  |   else Deny | ||||||
|  |     Sign-->>Scan: 403 (no attestation) | ||||||
|   end |   end | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ```mermaid | **Verification (third party).** | ||||||
| sequenceDiagram |  | ||||||
|     participant CRON as SbomNightly.Schedule |  | ||||||
|     participant CORE as Stella Core |  | ||||||
|     participant REDIS as Redis Queue |  | ||||||
|     participant RUN as Scanner Runner |  | ||||||
|     participant UI as Dashboard |  | ||||||
|  |  | ||||||
|     CRON->>CORE: Re-queue SBOMs (Mask-Filter) | ```plantuml | ||||||
|     CORE->>REDIS: Enqueue Filtered Jobs | @startuml | ||||||
|     REDIS->>RUN: Fan Out to Runners | actor Verifier | ||||||
|     RUN-->>CORE: New Scan Results | participant "stellaops verify" as Tool | ||||||
|     CORE->>UI: Highlight New Criticals | database "Fulcio/KMS root" as Root | ||||||
|     Note over CORE,UI: Focus on Changes Since Last Scan | participant "Rekor v2" as R2 | ||||||
|  | Verifier -> Tool: bundle (URL/file) | ||||||
|  | Tool -> Tool: Verify DSSE signature | ||||||
|  | Tool -> Root: Verify cert chain to StellaOps root | ||||||
|  | Tool -> R2: Verify inclusion proof / query by UUID | ||||||
|  | Tool -> Verifier: OK + claims (license_id, policy_digest, version) | ||||||
|  | @enduml | ||||||
| ``` | ``` | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 8 · UI Fast Facts   |  | ||||||
|  |  | ||||||
| * **Stack** – Angular 17 + Vite dev server; Tailwind CSS.   |  | ||||||
| * **State** – Signals + RxJS for live scan progress.   |  | ||||||
| * **i18n / l10n** – JSON bundles served from `/locales/{lang}.json`.   |  | ||||||
| * **Module Structure** – Lazy‑loaded feature modules (`dashboard`, `scans`, `settings`); runtime route injection by UI plug‑ins (road‑map Q2‑2026).   |  | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 9 · Cross‑Cutting Concerns   | **End of `high_level_architecture.md` (Consolidated).** | ||||||
|  |  | ||||||
| * **Security** – containers run non‑root, `CAP_DROP:ALL`, read‑only FS, hardened seccomp profiles.   |  | ||||||
| * **Observability** – Serilog JSON, OpenTelemetry OTLP exporter, Prometheus `/metrics`.   |  | ||||||
| * **Upgrade Policy** – `/api/v1` endpoints & CLI flags stable across a minor; breaking changes bump major.   |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 10 · Performance & Scalability   |  | ||||||
|  |  | ||||||
| | Scenario        | P95 target | Bottleneck      | Mitigation                                      | |  | ||||||
| |-----------------|-----------:|-----------------|-------------------------------------------------| |  | ||||||
| | SBOM‑first      | ≤ 5 s      | Redis queue     | More CPU, increase `ScannerPool.Workers`        | |  | ||||||
| | Image‑unpack    | ≤ 10 s     | Layer unpack    | Prefer SBOM path, warm Docker cache             | |  | ||||||
| | High concurrency| 40 rps     | Runner CPU      | Scale Core replicas + side‑car scanner services | |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 11 · Future Architectural Anchors   |  | ||||||
|  |  | ||||||
| * **ScanService micro‑split (gRPC)** – isolate heavy runners for large clusters.   |  | ||||||
| * **UI route plug‑ins** – dynamic Angular module loader (road‑map Q2‑2026).   |  | ||||||
| * **Redis Cluster** – transparently sharded cache once sustained > 100 rps.   |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 12 · Assumptions & Trade‑offs   |  | ||||||
|  |  | ||||||
| Requires Docker/CRI‑O runtime; .NET 9 available on hosts; Windows containers are out‑of‑scope this cycle.   |  | ||||||
| Embedded auth simplifies deployment but may need plug‑ins for enterprise IdPs.   |  | ||||||
| Speed is prioritised over exhaustive feature parity with heavyweight commercial scanners. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 13 · References & Further Reading   |  | ||||||
|  |  | ||||||
| * **C4 Model** – <https://c4model.com>   |  | ||||||
| * **.NET Architecture Guides** – <https://learn.microsoft.com/dotnet/architecture>   |  | ||||||
| * **OSS Examples** – Kubernetes Architecture docs, Prometheus design papers, Backstage.   |  | ||||||
|  |  | ||||||
| *(End of High‑Level Architecture v2.2)* |  | ||||||
|   | |||||||
| @@ -1,208 +0,0 @@ | |||||||
| # 8 · Detailed Module Specifications — **Stella Ops Feedser** |  | ||||||
| _This document describes the Feedser service, its supporting libraries, connectors, exporters, and test assets that live in the OSS repository._ |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 0 Scope   |  | ||||||
|  |  | ||||||
| Feedser is the vulnerability ingest/merge/export subsystem of Stella Ops. It |  | ||||||
| fetches primary advisories, normalizes and deduplicates them into MongoDB, and |  | ||||||
| produces deterministic JSON and Trivy DB exports. This document lists the |  | ||||||
| projects that make up that workflow, the extension points they expose, and the |  | ||||||
| artefacts they ship. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 1 Repository layout (current)   |  | ||||||
|  |  | ||||||
| ```text |  | ||||||
| src/ |  | ||||||
|  ├─ Directory.Build.props / Directory.Build.targets |  | ||||||
|  ├─ StellaOps.Plugin/ |  | ||||||
|  ├─ StellaOps.Feedser.Core/ |  | ||||||
|  ├─ StellaOps.Feedser.Core.Tests/ |  | ||||||
|  ├─ StellaOps.Feedser.Models/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Normalization/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Merge/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Storage.Mongo/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Exporter.Json/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Exporter.TrivyDb/ (+ .Tests/) |  | ||||||
|  ├─ StellaOps.Feedser.Source.* / StellaOps.Feedser.Source.*.Tests/ |  | ||||||
|  ├─ StellaOps.Feedser.Testing/ |  | ||||||
|  ├─ StellaOps.Feedser.Tests.Shared/ |  | ||||||
|  ├─ StellaOps.Feedser.WebService/ (+ .Tests/) |  | ||||||
|  ├─ PluginBinaries/ |  | ||||||
|  └─ StellaOps.Feedser.sln |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Each folder is a .NET project (or set of projects) referenced by |  | ||||||
| `StellaOps.Feedser.sln`. Build assets are shared through the root |  | ||||||
| `Directory.Build.props/targets` so conventions stay consistent. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 2 Shared libraries   |  | ||||||
|  |  | ||||||
| | Project | Purpose | Key extension points | |  | ||||||
| |---------|---------|----------------------| |  | ||||||
| | `StellaOps.Plugin` | Base contracts for connectors, exporters, and DI routines plus Cosign validation helpers. | `IFeedConnector`, `IExporterPlugin`, `IDependencyInjectionRoutine` | |  | ||||||
| | `StellaOps.DependencyInjection` | Composable service registrations for Feedser and plug-ins. | `IDependencyInjectionRoutine` discovery | |  | ||||||
| | `StellaOps.Feedser.Testing` | Common fixtures, builders, and harnesses for integration/unit tests. | `FeedserMongoFixture`, test builders | |  | ||||||
| | `StellaOps.Feedser.Tests.Shared` | Shared assembly metadata and fixtures wired in via `Directory.Build.props`. | Test assembly references | |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 3 Core projects   |  | ||||||
|  |  | ||||||
| | Project | Responsibility | Extensibility | |  | ||||||
| |---------|----------------|---------------| |  | ||||||
| | `StellaOps.Feedser.WebService` | ASP.NET Core minimal API hosting Feedser jobs, status endpoints, and scheduler. | DI-based plug-in discovery; configuration binding |  |  | ||||||
| | `StellaOps.Feedser.Core` | Job orchestration, connector pipelines, merge workflows, export coordination. | `IFeedConnector`, `IExportJob`, deterministic merge policies | |  | ||||||
| | `StellaOps.Feedser.Models` | Canonical advisory DTOs and enums persisted in MongoDB and exported artefacts. | Partial classes for source-specific metadata | |  | ||||||
| | `StellaOps.Feedser.Normalization` | Version comparison, CVSS normalization, text utilities for canonicalization. | Helpers consumed by connectors/merge | |  | ||||||
| | `StellaOps.Feedser.Merge` | Precedence evaluation, alias graph maintenance, merge-event hashing. | Policy extensions via DI | |  | ||||||
| | `StellaOps.Feedser.Storage.Mongo` | Repository layer for documents, DTOs, advisories, merge events, export state. | Connection string/config via options | |  | ||||||
| | `StellaOps.Feedser.Exporter.Json` | Deterministic vuln-list JSON export pipeline. | Dependency injection for storage + plugin to host |  |  | ||||||
| | `StellaOps.Feedser.Exporter.TrivyDb` | Builds Trivy DB artefacts from canonical advisories. | Optional ORAS push routines | |  | ||||||
|  |  | ||||||
| ### 3.1 StellaOps.Feedser.WebService   |  | ||||||
|  |  | ||||||
| * Hosts minimal API endpoints (`/health`, `/status`, `/jobs`). |  | ||||||
| * Runs the scheduler that triggers connectors and exporters according to |  | ||||||
|   configured windows. |  | ||||||
| * Applies dependency-injection routines from `PluginBinaries/` at startup only |  | ||||||
|   (restart-time plug-ins). |  | ||||||
|  |  | ||||||
| ### 3.2 StellaOps.Feedser.Core   |  | ||||||
|  |  | ||||||
| * Defines job primitives (fetch, parse, map, merge, export) used by connectors. |  | ||||||
| * Coordinates deterministic merge flows and writes `merge_event` documents. |  | ||||||
| * Provides telemetry/log scopes consumed by WebService and exporters. |  | ||||||
|  |  | ||||||
| ### 3.3 StellaOps.Feedser.Storage.Mongo   |  | ||||||
|  |  | ||||||
| * Persists raw documents, DTO records, canonical advisories, aliases, affected |  | ||||||
|   packages, references, merge events, export state, and job leases. |  | ||||||
| * Exposes repository helpers for exporters to stream full/delta snapshots. |  | ||||||
|  |  | ||||||
| ### 3.4 StellaOps.Feedser.Exporter.*   |  | ||||||
|  |  | ||||||
| * `Exporter.Json` mirrors the Aqua vuln-list tree with canonical ordering. |  | ||||||
| * `Exporter.TrivyDb` builds Trivy DB Bolt archives and optional OCI bundles. |  | ||||||
| * Both exporters honour deterministic hashing and respect export cursors. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 4 Source connectors   |  | ||||||
|  |  | ||||||
| Connectors live under `StellaOps.Feedser.Source.*` and conform to the interfaces |  | ||||||
| in `StellaOps.Plugin`. |  | ||||||
|  |  | ||||||
| | Family | Project(s) | Notes | |  | ||||||
| |--------|------------|-------| |  | ||||||
| | Distro PSIRTs | `StellaOps.Feedser.Source.Distro.*` | Debian, Red Hat, SUSE, Ubuntu connectors with NEVRA/EVR helpers. | |  | ||||||
| | Vendor PSIRTs | `StellaOps.Feedser.Source.Vndr.*` | Adobe, Apple, Cisco, Chromium, Microsoft, Oracle, VMware. | |  | ||||||
| | Regional CERTs | `StellaOps.Feedser.Source.Cert*`, `Source.Ru.*`, `Source.Ics.*`, `Source.Kisa` | Provide enrichment metadata while preserving vendor precedence. | |  | ||||||
| | OSS ecosystems | `StellaOps.Feedser.Source.Ghsa`, `Source.Osv`, `Source.Cve`, `Source.Kev`, `Source.Acsc`, `Source.Cccs`, `Source.Jvn` | Emit SemVer/alias-rich advisories. | |  | ||||||
|  |  | ||||||
| Each connector ships fixtures/tests under the matching `*.Tests` project. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 5 · Module Details   |  | ||||||
|  |  | ||||||
| > _Focus on the Feedser-specific services that replace the legacy FeedMerge cron._ |  | ||||||
|  |  | ||||||
| ### 5.1 Feedser.Core   |  | ||||||
|  |  | ||||||
| * Owns the fetch → parse → merge → export job pipeline and enforces deterministic |  | ||||||
|   merge hashes (`merge_event`). |  | ||||||
| * Provides `JobSchedulerBuilder`, job coordinator, and telemetry scopes consumed |  | ||||||
|   by the WebService and exporters. |  | ||||||
|  |  | ||||||
| ### 5.2 Feedser.Storage.Mongo   |  | ||||||
|  |  | ||||||
| * Bootstrapper creates collections/indexes (documents, dto, advisory, alias, |  | ||||||
|   affected, merge_event, export_state, jobs, locks). |  | ||||||
| * Repository APIs surface full/delta advisory reads for exporters, plus |  | ||||||
|   SourceState and job lease persistence. |  | ||||||
|  |  | ||||||
| ### 5.3 Feedser.Exporter.Json / Feedser.Exporter.TrivyDb   |  | ||||||
|  |  | ||||||
| * JSON exporter mirrors vuln-list layout with per-file digests and manifest. |  | ||||||
| * Trivy DB exporter shells or native-builds Bolt archives, optionally pushes OCI |  | ||||||
|   layers, and records export cursors. Delta runs reuse unchanged blobs from the |  | ||||||
|   previous full baseline, annotating `metadata.json` with `mode`, `baseExportId`, |  | ||||||
|   `baseManifestDigest`, `resetBaseline`, and `delta.changedFiles[]`/`delta.removedPaths[]`. |  | ||||||
|   ORAS pushes honour `publishFull` / `publishDelta`, and offline bundles respect |  | ||||||
|   `includeFull` / `includeDelta` for air-gapped syncs. |  | ||||||
|  |  | ||||||
| ### 5.4 Feedser.WebService   |  | ||||||
|  |  | ||||||
| * Minimal API host exposing `/health`, `/ready`, `/jobs` and wiring telemetry. |  | ||||||
| * Loads restart-time plug-ins from `PluginBinaries/`, executes Mongo bootstrap, |  | ||||||
|   and registers built-in connectors/exporters with the scheduler. |  | ||||||
|  |  | ||||||
| ### 5.5 Plugin host & DI bridge   |  | ||||||
|  |  | ||||||
| * `StellaOps.Plugin` + `StellaOps.DependencyInjection` provide the contracts and |  | ||||||
|   helper routines for connectors/exporters to integrate with the WebService. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 6 · Plug-ins & Agents   |  | ||||||
|  |  | ||||||
| * **Plug-in discovery** – restart-only; the WebService enumerates |  | ||||||
|   `PluginBinaries/` (or configured directories) and executes the contained |  | ||||||
|   `IDependencyInjectionRoutine` implementations. |  | ||||||
| * **Connector/exporter packages** – each source/exporter can ship as a plug-in |  | ||||||
|   assembly with its own options and HttpClient configuration, keeping the core |  | ||||||
|   image minimal. |  | ||||||
| * **StellaOps CLI (agent)** – new `StellaOps.Cli` module that exposes |  | ||||||
|   `scanner`, `scan`, and `db` verbs (via System.CommandLine 2.0) to download |  | ||||||
|   scanner container bundles, install them locally, execute scans against target |  | ||||||
|   directories, automatically upload results, and trigger Feedser jobs (`db |  | ||||||
|   fetch/merge/export`) aligned with the SBOM-first workflow described in |  | ||||||
|   `AGENTS.md`. |  | ||||||
| * **Offline Kit** – bundles Feedser plug-ins, JSON tree, Trivy DB, and export |  | ||||||
|   manifests so air-gapped sites can load the latest vulnerability data without |  | ||||||
|   outbound connectivity. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 7 · Docker & Distribution Artefacts   |  | ||||||
|  |  | ||||||
| | Artefact | Path / Identifier | Notes | |  | ||||||
| |----------|-------------------|-------| |  | ||||||
| | Feedser WebService image | `containers/feedser/Dockerfile` (built via CI) | Self-contained ASP.NET runtime hosting scheduler/endpoints. | |  | ||||||
| | Plugin bundle | `PluginBinaries/` | Mounted or baked-in assemblies for connectors/exporters. | |  | ||||||
| | Offline Kit tarball | Produced by CI release pipeline | Contains JSON tree, Trivy DB OCI layout, export manifest, and plug-ins. | |  | ||||||
| | Local dev compose | `scripts/` + future compose overlays | Developers can run MongoDB, Redis (optional), and WebService locally. | |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 8 · Performance Budget   |  | ||||||
|  |  | ||||||
| | Scenario | Budget | Source | |  | ||||||
| |----------|--------|--------| |  | ||||||
| | Advisory upsert (large advisory) | ≤ 500 ms/advisory | `AdvisoryStorePerformanceTests` (Mongo) | |  | ||||||
| | Advisory fetch (`GetRecent`) | ≤ 200 ms/advisory | Same performance test harness | |  | ||||||
| | Advisory point lookup (`Find`) | ≤ 200 ms/advisory | Same performance test harness | |  | ||||||
| | Bulk upsert/fetch cycle | ≤ 28 s total for 30 large advisories | Same performance test harness | |  | ||||||
| | Feedser job scheduling | Deterministic cron execution via `JobSchedulerHostedService` | `StellaOps.Feedser.Core` tests | |  | ||||||
| | Trivy DB export | Deterministic digests across runs (ongoing TODO for end-to-end test) | `Exporter.TrivyDb` backlog | |  | ||||||
|  |  | ||||||
| Budgets are enforced in automated tests where available; outstanding TODO/DOING |  | ||||||
| items (see task boards) continue tracking gaps such as exporter determinism. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 9 Testing   |  | ||||||
|  |  | ||||||
| * Unit and integration tests live alongside each component (`*.Tests`). |  | ||||||
| * Shared fixtures come from `StellaOps.Feedser.Testing` and |  | ||||||
|   `StellaOps.Feedser.Tests.Shared` (linked via `Directory.Build.props`). |  | ||||||
| * Integration suites use ephemeral MongoDB and Redis via Testcontainers to |  | ||||||
|   validate end-to-end flow without external dependencies. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
| @@ -110,3 +110,4 @@ See the detailed rules in | |||||||
| * **Install guide:** `/install/#air-gapped` | * **Install guide:** `/install/#air-gapped` | ||||||
| * **Sovereign mode rationale:** `/sovereign/` | * **Sovereign mode rationale:** `/sovereign/` | ||||||
| * **Security policy:** `/security/#reporting-a-vulnerability` | * **Security policy:** `/security/#reporting-a-vulnerability` | ||||||
|  | * **CERT-Bund snapshots:** `python tools/certbund_offline_snapshot.py --help` (see `docs/ops/feedser-certbund-operations.md`) | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ Produce and maintain offline-friendly documentation for StellaOps modules, cover | |||||||
| ## Operating Principles | ## Operating Principles | ||||||
| - Keep guides deterministic and in sync with shipped configuration samples. | - Keep guides deterministic and in sync with shipped configuration samples. | ||||||
| - Prefer tables/checklists for operator steps; flag security-sensitive actions. | - Prefer tables/checklists for operator steps; flag security-sensitive actions. | ||||||
|  | - When work involves a specific `StellaOps.<Component>` project, consult both `docs/07_HIGH_LEVEL_ARCHITECTURE.md` and the matching dossier `docs/ARCHITECTURE_<COMPONENT>.md` before drafting or editing content. | ||||||
| - Update `docs/TASKS.md` whenever work items change status (TODO/DOING/REVIEW/DONE/BLOCKED). | - Update `docs/TASKS.md` whenever work items change status (TODO/DOING/REVIEW/DONE/BLOCKED). | ||||||
|  |  | ||||||
| ## Coordination | ## Coordination | ||||||
|   | |||||||
							
								
								
									
										384
									
								
								docs/ARCHITECTURE_ATTESTOR.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										384
									
								
								docs/ARCHITECTURE_ATTESTOR.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,384 @@ | |||||||
|  | # component_architecture_attestor.md — **Stella Ops Attestor** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for the **Attestor**: the service that **submits** DSSE envelopes to **Rekor v2**, retrieves/validates inclusion proofs, caches results, and exposes verification APIs. It accepts DSSE **only** from the **Signer** over mTLS, enforces chain‑of‑trust to Stella Ops roots, and returns `{uuid, index, proof, logURL}` to calling services (Scanner.WebService for SBOMs; backend for final reports; Vexer exports when configured). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Turn a signed DSSE envelope from the Signer into a **transparency‑logged, verifiable fact** with a durable, replayable proof (Merkle inclusion + (optional) checkpoint anchoring). Provide **fast verification** for downstream consumers and a stable retrieval interface for UI/CLI. | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * Attestor **does not sign**; it **must not** accept unsigned or third‑party‑signed bundles. | ||||||
|  | * Attestor **does not decide PASS/FAIL**; it logs attestations for SBOMs, reports, and export artifacts. | ||||||
|  | * Rekor v2 backends may be **local** (self‑hosted) or **remote**; Attestor handles both with retries, backoff, and idempotency. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Topology & dependencies | ||||||
|  |  | ||||||
|  | **Process shape:** single stateless service `stellaops/attestor` behind mTLS. | ||||||
|  |  | ||||||
|  | **Dependencies:** | ||||||
|  |  | ||||||
|  | * **Signer** (caller) — authenticated via **mTLS** and **Authority** OpToks. | ||||||
|  | * **Rekor v2** — tile‑backed transparency log endpoint(s). | ||||||
|  | * **MinIO (S3)** — optional archive store for DSSE envelopes & verification bundles. | ||||||
|  | * **MongoDB** — local cache of `{uuid, index, proof, artifactSha256, bundleSha256}`; job state; audit. | ||||||
|  | * **Redis** — dedupe/idempotency keys and short‑lived rate‑limit buckets. | ||||||
|  | * **Licensing Service (optional)** — “endorse” call for cross‑log publishing when customer opts‑in. | ||||||
|  |  | ||||||
|  | Trust boundary: **Only the Signer** is allowed to call submission endpoints; enforced by **mTLS peer cert allowlist** + `aud=attestor` OpTok. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Data model (Mongo) | ||||||
|  |  | ||||||
|  | Database: `attestor` | ||||||
|  |  | ||||||
|  | **Collections & schemas** | ||||||
|  |  | ||||||
|  | * `entries` | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   { _id: "<rekor-uuid>", | ||||||
|  |     artifact: { sha256: "<sha256>", kind: "sbom|report|vex-export", imageDigest?, subjectUri? }, | ||||||
|  |     bundleSha256: "<sha256>",                           // canonicalized DSSE | ||||||
|  |     index: <int>,                                       // log index/sequence if provided by backend | ||||||
|  |     proof: {                                            // inclusion proof | ||||||
|  |       checkpoint: { origin, size, rootHash, timestamp }, | ||||||
|  |       inclusion: { leafHash, path[] }                   // Merkle path (tiles) | ||||||
|  |     }, | ||||||
|  |     log: { url, logId? }, | ||||||
|  |     createdAt, status: "included|pending|failed", | ||||||
|  |     signerIdentity: { mode: "keyless|kms", issuer, san?, kid? } | ||||||
|  |   } | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | * `dedupe` | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   { key: "bundle:<sha256>", rekorUuid, createdAt, ttlAt }     // idempotency key | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | * `audit` | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   { _id, ts, caller: { cn, mTLSThumbprint, sub, aud },        // from mTLS + OpTok | ||||||
|  |     action: "submit|verify|fetch", | ||||||
|  |     artifactSha256, bundleSha256, rekorUuid?, index?, result, latencyMs, backend } | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | Indexes: | ||||||
|  |  | ||||||
|  | * `entries` on `artifact.sha256`, `bundleSha256`, `createdAt`, and `{status:1, createdAt:-1}`. | ||||||
|  | * `dedupe.key` unique (TTL 24–48h). | ||||||
|  | * `audit.ts` for time‑range queries. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Input contract (from Signer) | ||||||
|  |  | ||||||
|  | **Attestor accepts only** DSSE envelopes that satisfy all of: | ||||||
|  |  | ||||||
|  | 1. **mTLS** peer certificate maps to `signer` service (CA‑pinned). | ||||||
|  | 2. **Authority** OpTok with `aud=attestor`, `scope=attestor.write`, DPoP or mTLS bound. | ||||||
|  | 3. DSSE envelope is **signed by the Signer’s key** (or includes a **Fulcio‑issued** cert chain) and **chains to configured roots** (Fulcio/KMS). | ||||||
|  | 4. **Predicate type** is one of Stella Ops types (sbom/report/vex‑export) with valid schema. | ||||||
|  | 5. `subject[*].digest.sha256` is present and canonicalized. | ||||||
|  |  | ||||||
|  | **Wire shape (JSON):** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "bundle": { "dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "<b64>", "signatures": [ ... ] }, | ||||||
|  |               "certificateChain": [ "-----BEGIN CERTIFICATE-----..." ], | ||||||
|  |               "mode": "keyless" }, | ||||||
|  |   "meta": { | ||||||
|  |     "artifact": { "sha256": "<subject sha256>", "kind": "sbom|report|vex-export", "imageDigest": "sha256:..." }, | ||||||
|  |     "bundleSha256": "<sha256 of canonical dsse>", | ||||||
|  |     "logPreference": "primary",               // "primary" | "mirror" | "both" | ||||||
|  |     "archive": true                           // whether Attestor should archive bundle to S3 | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) APIs | ||||||
|  |  | ||||||
|  | ### 4.1 Submission | ||||||
|  |  | ||||||
|  | `POST /api/v1/rekor/entries`  *(mTLS + OpTok required)* | ||||||
|  |  | ||||||
|  | * **Body**: as above. | ||||||
|  | * **Behavior**: | ||||||
|  |  | ||||||
|  |   * Verify caller (mTLS + OpTok). | ||||||
|  |   * Validate DSSE bundle (signature, cert chain to Fulcio/KMS; DSSE structure; payloadType allowed). | ||||||
|  |   * Idempotency: compute `bundleSha256`; check `dedupe`. If present, return existing `rekorUuid`. | ||||||
|  |   * Submit canonicalized bundle to Rekor v2 (primary or mirror according to `logPreference`). | ||||||
|  |   * Retrieve **inclusion proof** (blocking until inclusion or up to `proofTimeoutMs`); if backend returns promise only, return `status=pending` and retry asynchronously. | ||||||
|  |   * Persist `entries` record; archive DSSE to S3 if `archive=true`. | ||||||
|  | * **Response 200**: | ||||||
|  |  | ||||||
|  |   ```json | ||||||
|  |   { | ||||||
|  |     "uuid": "…", | ||||||
|  |     "index": 123456, | ||||||
|  |     "proof": { | ||||||
|  |       "checkpoint": { "origin": "rekor@site", "size": 987654, "rootHash": "…", "timestamp": "…" }, | ||||||
|  |       "inclusion": { "leafHash": "…", "path": ["…","…"] } | ||||||
|  |     }, | ||||||
|  |     "logURL": "https://rekor…/api/v2/log/…/entries/…", | ||||||
|  |     "status": "included" | ||||||
|  |   } | ||||||
|  |   ``` | ||||||
|  | * **Errors**: `401 invalid_token`, `403 not_signer|chain_untrusted`, `409 duplicate_bundle` (with existing `uuid`), `502 rekor_unavailable`, `504 proof_timeout`. | ||||||
|  |  | ||||||
|  | ### 4.2 Proof retrieval | ||||||
|  |  | ||||||
|  | `GET /api/v1/rekor/entries/{uuid}` | ||||||
|  |  | ||||||
|  | * Returns `entries` row (refreshes proof from Rekor if stale/missing). | ||||||
|  | * Accepts `?refresh=true` to force backend query. | ||||||
|  |  | ||||||
|  | ### 4.3 Verification (third‑party or internal) | ||||||
|  |  | ||||||
|  | `POST /api/v1/rekor/verify` | ||||||
|  |  | ||||||
|  | * **Body** (one of): | ||||||
|  |  | ||||||
|  |   * `{ "uuid": "…" }` | ||||||
|  |   * `{ "bundle": { …DSSE… } }` | ||||||
|  |   * `{ "artifactSha256": "…" }`  *(looks up most recent entry)* | ||||||
|  |  | ||||||
|  | * **Checks**: | ||||||
|  |  | ||||||
|  |   1. **Bundle signature** → cert chain to Fulcio/KMS roots configured. | ||||||
|  |   2. **Inclusion proof** → recompute leaf hash; verify Merkle path against checkpoint root. | ||||||
|  |   3. Optionally verify **checkpoint** against local trust anchors (if Rekor signs checkpoints). | ||||||
|  |   4. Confirm **subject.digest** matches caller‑provided hash (when given). | ||||||
|  |  | ||||||
|  | * **Response**: | ||||||
|  |  | ||||||
|  |   ```json | ||||||
|  |   { "ok": true, "uuid": "…", "index": 123, "logURL": "…", "checkedAt": "…" } | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | ### 4.4 Batch submission (optional) | ||||||
|  |  | ||||||
|  | `POST /api/v1/rekor/batch` accepts an array of submission objects; processes with per‑item results. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Rekor v2 driver (backend) | ||||||
|  |  | ||||||
|  | * **Canonicalization**: DSSE envelopes are **normalized** (stable JSON ordering, no insignificant whitespace) before hashing and submission. | ||||||
|  | * **Transport**: HTTP/2 with retries (exponential backoff, jitter), budgeted timeouts. | ||||||
|  | * **Idempotency**: if backend returns “already exists,” map to existing `uuid`. | ||||||
|  | * **Proof acquisition**: | ||||||
|  |  | ||||||
|  |   * In synchronous mode, poll the log for inclusion up to `proofTimeoutMs`. | ||||||
|  |   * In asynchronous mode, return `pending` and schedule a **proof fetcher** job (Mongo job doc + backoff). | ||||||
|  | * **Mirrors/dual logs**: | ||||||
|  |  | ||||||
|  |   * When `logPreference="both"`, submit to primary and mirror; store **both** UUIDs (primary canonical). | ||||||
|  |   * Optional **cloud endorsement**: POST to the Stella Ops cloud `/attest/endorse` with `{uuid, artifactSha256}`; store returned endorsement id. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Security model | ||||||
|  |  | ||||||
|  | * **mTLS required** for submission from **Signer** (CA‑pinned). | ||||||
|  | * **Authority token** with `aud=attestor` and DPoP/mTLS binding must be presented; Attestor verifies both. | ||||||
|  | * **Bundle acceptance policy**: | ||||||
|  |  | ||||||
|  |   * DSSE signature must chain to the configured **Fulcio** (keyless) or **KMS/HSM** roots. | ||||||
|  |   * SAN (Subject Alternative Name) must match **Signer identity** policy (e.g., `urn:stellaops:signer` or pinned OIDC issuer). | ||||||
|  |   * Predicate `predicateType` must be on allowlist (sbom/report/vex-export). | ||||||
|  |   * `subject.digest.sha256` values must be present and well‑formed (hex). | ||||||
|  | * **No public submission** path. **Never** accept bundles from untrusted clients. | ||||||
|  | * **Rate limits**: per mTLS thumbprint/license (from Signer‑forwarded claims) to avoid flooding the log. | ||||||
|  | * **Redaction**: Attestor never logs secret material; DSSE payloads **should** be public by design (SBOMs/reports). If customers require redaction, enforce policy at Signer (predicate minimization) **before** Attestor. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Storage & archival | ||||||
|  |  | ||||||
|  | * **Entries** in Mongo provide a local ledger keyed by `rekorUuid` and **artifact sha256** for quick reverse lookups. | ||||||
|  | * **S3 archival** (if enabled): | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   s3://stellaops/attest/ | ||||||
|  |     dsse/<bundleSha256>.json | ||||||
|  |     proof/<rekorUuid>.json | ||||||
|  |     bundle/<artifactSha256>.zip               # optional verification bundle | ||||||
|  |   ``` | ||||||
|  | * **Verification bundles** (zip): | ||||||
|  |  | ||||||
|  |   * DSSE (`*.dsse.json`), proof (`*.proof.json`), `chain.pem` (certs), `README.txt` with verification steps & hashes. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Observability & audit | ||||||
|  |  | ||||||
|  | **Metrics** (Prometheus): | ||||||
|  |  | ||||||
|  | * `attestor.submit_total{result,backend}` | ||||||
|  | * `attestor.submit_latency_seconds{backend}` | ||||||
|  | * `attestor.proof_fetch_total{result}` | ||||||
|  | * `attestor.verify_total{result}` | ||||||
|  | * `attestor.dedupe_hits_total` | ||||||
|  | * `attestor.errors_total{type}` | ||||||
|  |  | ||||||
|  | **Tracing**: | ||||||
|  |  | ||||||
|  | * Spans: `validate`, `rekor.submit`, `rekor.poll`, `persist`, `archive`, `verify`. | ||||||
|  |  | ||||||
|  | **Audit**: | ||||||
|  |  | ||||||
|  | * Immutable `audit` rows (ts, caller, action, hashes, uuid, index, backend, result, latency). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | attestor: | ||||||
|  |   listen: "https://0.0.0.0:8444" | ||||||
|  |   security: | ||||||
|  |     mtls: | ||||||
|  |       caBundle: /etc/ssl/signer-ca.pem | ||||||
|  |       requireClientCert: true | ||||||
|  |     authority: | ||||||
|  |       issuer: "https://authority.internal" | ||||||
|  |       jwksUrl: "https://authority.internal/jwks" | ||||||
|  |       requireSenderConstraint: "dpop"   # or "mtls" | ||||||
|  |     signerIdentity: | ||||||
|  |       mode: ["keyless","kms"] | ||||||
|  |       fulcioRoots: ["/etc/fulcio/root.pem"] | ||||||
|  |       allowedSANs: ["urn:stellaops:signer"] | ||||||
|  |       kmsKeys: ["kms://cluster-kms/stellaops-signer"] | ||||||
|  |   rekor: | ||||||
|  |     primary: | ||||||
|  |       url: "https://rekor-v2.internal" | ||||||
|  |       proofTimeoutMs: 15000 | ||||||
|  |       pollIntervalMs: 250 | ||||||
|  |       maxAttempts: 60 | ||||||
|  |     mirror: | ||||||
|  |       enabled: false | ||||||
|  |       url: "https://rekor-v2.mirror" | ||||||
|  |   mongo: | ||||||
|  |     uri: "mongodb://mongo/attestor" | ||||||
|  |   s3: | ||||||
|  |     enabled: true | ||||||
|  |     endpoint: "http://minio:9000" | ||||||
|  |     bucket: "stellaops" | ||||||
|  |     prefix: "attest/" | ||||||
|  |     objectLock: "governance" | ||||||
|  |   redis: | ||||||
|  |     url: "redis://redis:6379/2" | ||||||
|  |   quotas: | ||||||
|  |     perCaller: | ||||||
|  |       qps: 50 | ||||||
|  |       burst: 100 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) End‑to‑end sequences | ||||||
|  |  | ||||||
|  | **A) Submit & include (happy path)** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant SW as Scanner.WebService | ||||||
|  |   participant SG as Signer | ||||||
|  |   participant AT as Attestor | ||||||
|  |   participant RK as Rekor v2 | ||||||
|  |  | ||||||
|  |   SW->>SG: POST /sign/dsse (OpTok+PoE) | ||||||
|  |   SG-->>SW: DSSE bundle (+certs) | ||||||
|  |   SW->>AT: POST /rekor/entries (mTLS + OpTok) | ||||||
|  |   AT->>AT: Validate DSSE (chain to Fulcio/KMS; signer identity) | ||||||
|  |   AT->>RK: submit(bundle) | ||||||
|  |   RK-->>AT: {uuid, index?} | ||||||
|  |   AT->>RK: poll inclusion until proof or timeout | ||||||
|  |   RK-->>AT: inclusion proof (checkpoint + path) | ||||||
|  |   AT-->>SW: {uuid, index, proof, logURL} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **B) Verify by artifact digest (CLI)** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant CLI as stellaops verify | ||||||
|  |   participant SW as Scanner.WebService | ||||||
|  |   participant AT as Attestor | ||||||
|  |  | ||||||
|  |   CLI->>SW: GET /catalog/artifacts/{id} | ||||||
|  |   SW-->>CLI: {artifactSha256, rekor: {uuid}} | ||||||
|  |   CLI->>AT: POST /rekor/verify { uuid } | ||||||
|  |   AT-->>CLI: { ok: true, index, logURL } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Failure modes & responses | ||||||
|  |  | ||||||
|  | | Condition                             | Return                  | Details                                                   |          |              | | ||||||
|  | | ------------------------------------- | ----------------------- | --------------------------------------------------------- | -------- | ------------ | | ||||||
|  | | mTLS/OpTok invalid                    | `401 invalid_token`     | Include `WWW-Authenticate` DPoP challenge when applicable |          |              | | ||||||
|  | | Bundle not signed by trusted identity | `403 chain_untrusted`   | DSSE accepted only from Signer identities                 |          |              | | ||||||
|  | | Duplicate bundle                      | `409 duplicate_bundle`  | Return existing `uuid` (idempotent)                       |          |              | | ||||||
|  | | Rekor unreachable/timeout             | `502 rekor_unavailable` | Retry with backoff; surface `Retry-After`                 |          |              | | ||||||
|  | | Inclusion proof timeout               | `202 accepted`          | `status=pending`, background job continues to fetch proof |          |              | | ||||||
|  | | Archive failure                       | `207 multi-status`      | Entry recorded; archive will retry asynchronously         |          |              | | ||||||
|  | | Verification mismatch                 | `400 verify_failed`     | Include reason: chain                                     | leafHash | rootMismatch | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Performance & scale | ||||||
|  |  | ||||||
|  | * Stateless; scale horizontally. | ||||||
|  | * **Targets**: | ||||||
|  |  | ||||||
|  |   * Submit+proof P95 ≤ **300 ms** (warm log; local Rekor). | ||||||
|  |   * Verify P95 ≤ **30 ms** from cache; ≤ **120 ms** with live proof fetch. | ||||||
|  |   * 1k submissions/minute per replica sustained. | ||||||
|  | * **Hot caches**: `dedupe` (bundle hash → uuid), recent `entries` by artifact sha256. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Testing matrix | ||||||
|  |  | ||||||
|  | * **Happy path**: valid DSSE, inclusion within timeout. | ||||||
|  | * **Idempotency**: resubmit same `bundleSha256` → same `uuid`. | ||||||
|  | * **Security**: reject non‑Signer mTLS, wrong `aud`, DPoP replay, untrusted cert chain, forbidden predicateType. | ||||||
|  | * **Rekor variants**: promise‑then‑proof, proof delayed, mirror dual‑submit, mirror failure. | ||||||
|  | * **Verification**: corrupt leaf path, wrong root, tampered bundle. | ||||||
|  | * **Throughput**: soak test with 10k submissions; latency SLOs, zero drops. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Implementation notes | ||||||
|  |  | ||||||
|  | * Language: **.NET 10** minimal API; `HttpClient` with **sockets handler** tuned for HTTP/2. | ||||||
|  | * JSON: **canonical writer** for DSSE payload hashing. | ||||||
|  | * Crypto: use **BouncyCastle**/**System.Security.Cryptography**; PEM parsing for cert chains. | ||||||
|  | * Rekor client: pluggable driver; treat backend errors as retryable/non‑retryable with granular mapping. | ||||||
|  | * Safety: size caps on bundles; decompress bombs guarded; strict UTF‑8. | ||||||
|  | * CLI integration: `stellaops verify attestation <uuid|bundle|artifact>` calls `/rekor/verify`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Optional features | ||||||
|  |  | ||||||
|  | * **Dual‑log** write (primary + mirror) and **cross‑log proof** packaging. | ||||||
|  | * **Cloud endorsement**: send `{uuid, artifactSha256}` to Stella Ops cloud; store returned endorsement id for marketing/chain‑of‑custody. | ||||||
|  | * **Checkpoint pinning**: periodically pin latest Rekor checkpoints to an external audit store for independent monitoring. | ||||||
|  |  | ||||||
							
								
								
									
										394
									
								
								docs/ARCHITECTURE_AUTHORITY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								docs/ARCHITECTURE_AUTHORITY.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,394 @@ | |||||||
|  | # component_architecture_authority.md — **Stella Ops Authority** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for **Stella Ops Authority**: the on‑prem **OIDC/OAuth2** service that issues **short‑lived, sender‑constrained operational tokens (OpToks)** to first‑party services and tools. Covers protocols (DPoP & mTLS binding), token shapes, endpoints, storage, rotation, HA, RBAC, audit, and testing. This component is the trust anchor for *who* is calling inside a Stella Ops installation. (Entitlement is proven separately by **PoE** from the cloud Licensing Service; Authority does not issue PoE.) | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Provide **fast, local, verifiable** authentication for Stella Ops microservices and tools by minting **very short‑lived** OAuth2/OIDC tokens that are **sender‑constrained** (DPoP or mTLS‑bound). Support RBAC scopes, multi‑tenant claims, and deterministic validation for APIs (Scanner, Signer, Attestor, Vexer, Feedser, UI, CLI, Zastava). | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * Authority **does not** validate entitlements/licensing. That’s enforced by **Signer** using **PoE** with the cloud Licensing Service. | ||||||
|  | * Authority tokens are **operational only** (2–5 min TTL) and must not be embedded in long‑lived artifacts or stored in SBOMs. | ||||||
|  | * Authority is **stateless for validation** (JWT) and **optional introspection** for services that prefer online checks. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Protocols & cryptography | ||||||
|  |  | ||||||
|  | * **OIDC Discovery**: `/.well-known/openid-configuration` | ||||||
|  | * **OAuth2** grant types: | ||||||
|  |  | ||||||
|  |   * **Client Credentials** (service↔service, with mTLS or private_key_jwt) | ||||||
|  |   * **Device Code** (CLI login on headless agents; optional) | ||||||
|  |   * **Authorization Code + PKCE** (browser login for UI; optional) | ||||||
|  | * **Sender constraint options** (choose per caller or per audience): | ||||||
|  |  | ||||||
|  |   * **DPoP** (Demonstration of Proof‑of‑Possession): proof JWT on each HTTP request, bound to the access token via `cnf.jkt`. | ||||||
|  |   * **OAuth 2.0 mTLS** (certificate‑bound tokens): token bound to client certificate thumbprint via `cnf.x5t#S256`. | ||||||
|  | * **Signing algorithms**: **EdDSA (Ed25519)** preferred; fallback **ES256 (P‑256)**. Rotation is supported via **kid** in JWKS. | ||||||
|  | * **Token format**: **JWT** access tokens (compact), optionally opaque reference tokens for services that insist on introspection. | ||||||
|  | * **Clock skew tolerance**: ±60 s; issue `nbf`, `iat`, `exp` accordingly. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Token model | ||||||
|  |  | ||||||
|  | ### 2.1 Access token (OpTok) — short‑lived (120–300 s) | ||||||
|  |  | ||||||
|  | **Registered claims** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | iss   = https://authority.<domain> | ||||||
|  | sub   = <client_id or user_id> | ||||||
|  | aud   = <service audience: signer|scanner|attestor|feedser|vexer|ui|zastava> | ||||||
|  | exp   = <unix ts>  (<= 300 s from iat) | ||||||
|  | iat   = <unix ts> | ||||||
|  | nbf   = iat - 30 | ||||||
|  | jti   = <uuid> | ||||||
|  | scope = "scanner.scan scanner.export signer.sign ..." | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Sender‑constraint (`cnf`)** | ||||||
|  |  | ||||||
|  | * **DPoP**: | ||||||
|  |  | ||||||
|  |   ```json | ||||||
|  |   "cnf": { "jkt": "<base64url(SHA-256(JWK))>" } | ||||||
|  |   ``` | ||||||
|  | * **mTLS**: | ||||||
|  |  | ||||||
|  |   ```json | ||||||
|  |   "cnf": { "x5t#S256": "<base64url(SHA-256(client_cert_der))>" } | ||||||
|  |   ``` | ||||||
|  |  | ||||||
|  | **Install/tenant context (custom claims)** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | tid          = <tenant id>               // multi-tenant | ||||||
|  | inst         = <installation id>        // unique installation | ||||||
|  | roles        = [ "svc.scanner", "svc.signer", "ui.admin", ... ] | ||||||
|  | plan?        = <plan name>              // optional hint for UIs; not used for enforcement | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > **Note**: Do **not** copy PoE claims into OpTok; OpTok ≠ entitlement. Only **Signer** checks PoE. | ||||||
|  |  | ||||||
|  | ### 2.2 Refresh tokens (optional) | ||||||
|  |  | ||||||
|  | * Default **disabled**. If enabled (for UI interactive logins), pair with **DPoP‑bound** refresh tokens or **mTLS** client sessions; short TTL (≤ 8 h), rotating on use (replay‑safe). | ||||||
|  |  | ||||||
|  | ### 2.3 ID tokens (optional) | ||||||
|  |  | ||||||
|  | * Issued for UI/browser OIDC flows (Authorization Code + PKCE); not used for service auth. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Endpoints & flows | ||||||
|  |  | ||||||
|  | ### 3.1 OIDC discovery & keys | ||||||
|  |  | ||||||
|  | * `GET /.well-known/openid-configuration` → endpoints, algs, jwks_uri | ||||||
|  | * `GET /jwks` → JSON Web Key Set (rotating, at least 2 active keys during transition) | ||||||
|  |  | ||||||
|  | ### 3.2 Token issuance | ||||||
|  |  | ||||||
|  | * `POST /oauth/token` | ||||||
|  |  | ||||||
|  |   * **Client Credentials** (service→service): | ||||||
|  |  | ||||||
|  |     * **mTLS**: mutual TLS + `client_id` → bound token (`cnf.x5t#S256`) | ||||||
|  |     * **private_key_jwt**: JWT‑based client auth + **DPoP** header (preferred for tools and CLI) | ||||||
|  |   * **Device Code** (CLI): `POST /oauth/device/code` + `POST /oauth/token` poll | ||||||
|  |   * **Authorization Code + PKCE** (UI): standard | ||||||
|  |  | ||||||
|  | **DPoP handshake (example)** | ||||||
|  |  | ||||||
|  | 1. Client prepares **JWK** (ephemeral keypair). | ||||||
|  | 2. Client sends **DPoP proof** header with fields: | ||||||
|  |  | ||||||
|  |    ``` | ||||||
|  |    htm=POST | ||||||
|  |    htu=https://authority.../oauth/token | ||||||
|  |    iat=<now> | ||||||
|  |    jti=<uuid> | ||||||
|  |    ``` | ||||||
|  |  | ||||||
|  |    signed with the DPoP private key; header carries JWK. | ||||||
|  | 3. Authority validates proof; issues access token with `cnf.jkt=<thumbprint(JWK)>`. | ||||||
|  | 4. Client uses the same DPoP key to sign **every subsequent API request** to services (Signer, Scanner, …). | ||||||
|  |  | ||||||
|  | **mTLS flow** | ||||||
|  |  | ||||||
|  | * Mutual TLS at the connection; Authority extracts client cert, validates chain; token carries `cnf.x5t#S256`. | ||||||
|  |  | ||||||
|  | ### 3.3 Introspection & revocation (optional) | ||||||
|  |  | ||||||
|  | * `POST /oauth/introspect` → `{ active, sub, scope, aud, exp, cnf, ... }` | ||||||
|  | * `POST /oauth/revoke` → revokes refresh tokens or opaque access tokens. | ||||||
|  | * **Replay prevention**: maintain **DPoP `jti` cache** (TTL ≤ 10 min) to reject duplicate proofs when services supply DPoP nonces (Signer requires nonce for high‑value operations). | ||||||
|  |  | ||||||
|  | ### 3.4 UserInfo (optional for UI) | ||||||
|  |  | ||||||
|  | * `GET /userinfo` (ID token context). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Audiences, scopes & RBAC | ||||||
|  |  | ||||||
|  | ### 4.1 Audiences | ||||||
|  |  | ||||||
|  | * `signer` — only the **Signer** service should accept tokens with `aud=signer`. | ||||||
|  | * `attestor`, `scanner`, `feedser`, `vexer`, `ui`, `zastava` similarly. | ||||||
|  |  | ||||||
|  | Services **must** verify `aud` and **sender constraint** (DPoP/mTLS) per their policy. | ||||||
|  |  | ||||||
|  | ### 4.2 Core scopes | ||||||
|  |  | ||||||
|  | | Scope                              | Service            | Operation                  | | ||||||
|  | | ---------------------------------- | ------------------ | -------------------------- | | ||||||
|  | | `signer.sign`                      | Signer             | Request DSSE signing       | | ||||||
|  | | `attestor.write`                   | Attestor           | Submit Rekor entries       | | ||||||
|  | | `scanner.scan`                     | Scanner.WebService | Submit scan jobs           | | ||||||
|  | | `scanner.export`                   | Scanner.WebService | Export SBOMs               | | ||||||
|  | | `scanner.read`                     | Scanner.WebService | Read catalog/SBOMs         | | ||||||
|  | | `vex.read` / `vex.admin`           | Vexer              | Query/operate              | | ||||||
|  | | `feedser.read` / `feedser.export`  | Feedser            | Query/exports              | | ||||||
|  | | `ui.read` / `ui.admin`             | UI                 | View/admin                 | | ||||||
|  | | `zastava.emit` / `zastava.enforce` | Scanner/Zastava    | Runtime events / admission | | ||||||
|  |  | ||||||
|  | **Roles → scopes mapping** is configured centrally (Authority policy) and pushed during token issuance. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Storage & state | ||||||
|  |  | ||||||
|  | * **Configuration DB** (PostgreSQL/MySQL): clients, audiences, role→scope maps, tenant/installation registry, device code grants, persistent consents (if any). | ||||||
|  | * **Cache** (Redis): | ||||||
|  |  | ||||||
|  |   * DPoP **jti** replay cache (short TTL) | ||||||
|  |   * **Nonce** store (per resource server, if they demand nonce) | ||||||
|  |   * Device code pollers, rate limiting buckets | ||||||
|  | * **JWKS**: key material in HSM/KMS or encrypted at rest; JWKS served from memory. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Key management & rotation | ||||||
|  |  | ||||||
|  | * Maintain **at least 2 signing keys** active during rotation; tokens carry `kid`. | ||||||
|  | * Prefer **Ed25519** for compact tokens; maintain **ES256** fallback for FIPS contexts. | ||||||
|  | * Rotation cadence: 30–90 days; emergency rotation supported. | ||||||
|  | * Publish new JWKS **before** issuing tokens with the new `kid` to avoid cold‑start validation misses. | ||||||
|  | * Keep **old keys** available **at least** for max token TTL + 5 minutes. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) HA & performance | ||||||
|  |  | ||||||
|  | * **Stateless issuance** (except device codes/refresh) → scale horizontally behind a load‑balancer. | ||||||
|  | * **DB** only for client metadata and optional flows; token checks are JWT‑local; introspection endpoints hit cache/DB minimally. | ||||||
|  | * **Targets**: | ||||||
|  |  | ||||||
|  |   * Token issuance P95 ≤ **20 ms** under warm cache. | ||||||
|  |   * DPoP proof validation ≤ **1 ms** extra per request at resource servers (Signer/Scanner). | ||||||
|  |   * 99.9% uptime; HPA on CPU/latency. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Security posture | ||||||
|  |  | ||||||
|  | * **Strict TLS** (1.3 preferred); HSTS; modern cipher suites. | ||||||
|  | * **mTLS** enabled where required (Signer/Attestor paths). | ||||||
|  | * **Replay protection**: DPoP `jti` cache, nonce support for **Signer** (add `DPoP-Nonce` header on 401; clients re‑sign). | ||||||
|  | * **Rate limits** per client & per IP; exponential backoff on failures. | ||||||
|  | * **Secrets**: clients use **private_key_jwt** or **mTLS**; never basic secrets over the wire. | ||||||
|  | * **CSP/CSRF** hardening on UI flows; `SameSite=Lax` cookies; PKCE enforced. | ||||||
|  | * **Logs** redact `Authorization` and DPoP proofs; store `sub`, `aud`, `scopes`, `inst`, `tid`, `cnf` thumbprints, not full keys. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Multi‑tenancy & installations | ||||||
|  |  | ||||||
|  | * **Tenant (`tid`)** and **Installation (`inst`)** registries define which audiences/scopes a client can request. | ||||||
|  | * Cross‑tenant isolation enforced at issuance (disallow rogue `aud`), and resource servers **must** check that `tid` matches their configured tenant. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Admin & operations APIs | ||||||
|  |  | ||||||
|  | All under `/admin` (mTLS + `authority.admin` scope). | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /admin/clients                 # create/update client (confidential/public) | ||||||
|  | POST /admin/audiences               # register audience resource URIs | ||||||
|  | POST /admin/roles                   # define role→scope mappings | ||||||
|  | POST /admin/tenants                 # create tenant/install entries | ||||||
|  | POST /admin/keys/rotate             # rotate signing key (zero-downtime) | ||||||
|  | GET  /admin/metrics                 # Prometheus exposition (token issue rates, errors) | ||||||
|  | GET  /admin/healthz|readyz          # health/readiness | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Integration hard lines (what resource servers must enforce) | ||||||
|  |  | ||||||
|  | Every Stella Ops service that consumes Authority tokens **must**: | ||||||
|  |  | ||||||
|  | 1. Verify JWT signature (`kid` in JWKS), `iss`, `aud`, `exp`, `nbf`. | ||||||
|  | 2. Enforce **sender‑constraint**: | ||||||
|  |  | ||||||
|  |    * **DPoP**: validate DPoP proof (`htu`, `htm`, `iat`, `jti`) and match `cnf.jkt`; cache `jti` for replay defense; honor nonce challenges. | ||||||
|  |    * **mTLS**: match presented client cert thumbprint to token `cnf.x5t#S256`. | ||||||
|  | 3. Check **scopes**; optionally map to internal roles. | ||||||
|  | 4. Check **tenant** (`tid`) and **installation** (`inst`) as appropriate. | ||||||
|  | 5. For **Signer** only: require **both** OpTok and **PoE** in the request (enforced by Signer, not Authority). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Error surfaces & UX | ||||||
|  |  | ||||||
|  | * Token endpoint errors follow OAuth2 (`invalid_client`, `invalid_grant`, `invalid_scope`, `unauthorized_client`). | ||||||
|  | * Resource servers use RFC 6750 style (`WWW-Authenticate: DPoP error="invalid_token", error_description="…", dpop_nonce="…" `). | ||||||
|  | * For DPoP nonce challenges, clients retry with the server‑supplied nonce once. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Observability & audit | ||||||
|  |  | ||||||
|  | * **Metrics**: | ||||||
|  |  | ||||||
|  |   * `authority.tokens_issued_total{grant,aud}` | ||||||
|  |   * `authority.dpop_validations_total{result}` | ||||||
|  |   * `authority.mtls_bindings_total{result}` | ||||||
|  |   * `authority.jwks_rotations_total` | ||||||
|  |   * `authority.errors_total{type}` | ||||||
|  | * **Audit log** (immutable sink): token issuance (`sub`, `aud`, `scopes`, `tid`, `inst`, `cnf thumbprint`, `jti`), revocations, admin changes. | ||||||
|  | * **Tracing**: token flows, DB reads, JWKS cache. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | authority: | ||||||
|  |   issuer: "https://authority.internal" | ||||||
|  |   keys: | ||||||
|  |     algs: [ "EdDSA", "ES256" ] | ||||||
|  |     rotationDays: 60 | ||||||
|  |     storage: kms://cluster-kms/authority-signing | ||||||
|  |   tokens: | ||||||
|  |     accessTtlSeconds: 180 | ||||||
|  |     enableRefreshTokens: false | ||||||
|  |     clockSkewSeconds: 60 | ||||||
|  |   dpop: | ||||||
|  |     enable: true | ||||||
|  |     nonce: | ||||||
|  |       enable: true | ||||||
|  |       ttlSeconds: 600 | ||||||
|  |   mtls: | ||||||
|  |     enable: true | ||||||
|  |     caBundleFile: /etc/ssl/mtls/clients-ca.pem | ||||||
|  |   clients: | ||||||
|  |     - clientId: scanner-web | ||||||
|  |       grantTypes: [ "client_credentials" ] | ||||||
|  |       audiences: [ "scanner" ] | ||||||
|  |       auth: { type: "private_key_jwt", jwkFile: "/secrets/scanner-web.jwk" } | ||||||
|  |       senderConstraint: "dpop" | ||||||
|  |       scopes: [ "scanner.scan", "scanner.export", "scanner.read" ] | ||||||
|  |     - clientId: signer | ||||||
|  |       grantTypes: [ "client_credentials" ] | ||||||
|  |       audiences: [ "signer" ] | ||||||
|  |       auth: { type: "mtls" } | ||||||
|  |       senderConstraint: "mtls" | ||||||
|  |       scopes: [ "signer.sign" ] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Testing matrix | ||||||
|  |  | ||||||
|  | * **JWT validation**: wrong `aud`, expired `exp`, skewed `nbf`, stale `kid`. | ||||||
|  | * **DPoP**: invalid `htu`/`htm`, replayed `jti`, stale `iat`, wrong `jkt`, nonce dance. | ||||||
|  | * **mTLS**: wrong client cert, wrong CA, thumbprint mismatch. | ||||||
|  | * **RBAC**: scope enforcement per audience; over‑privileged client denied. | ||||||
|  | * **Rotation**: JWKS rotation while load‑testing; zero‑downtime verification. | ||||||
|  | * **HA**: kill one Authority instance; verify issuance continues; JWKS served by peers. | ||||||
|  | * **Performance**: 1k token issuance/sec on 2 cores with Redis enabled for jti caching. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Threat model & mitigations (summary) | ||||||
|  |  | ||||||
|  | | Threat              | Vector           | Mitigation                                                                                 | | ||||||
|  | | ------------------- | ---------------- | ------------------------------------------------------------------------------------------ | | ||||||
|  | | Token theft         | Copy of JWT      | **Short TTL**, **sender‑constraint** (DPoP/mTLS); replay blocked by `jti` cache and nonces | | ||||||
|  | | Replay across hosts | Reuse DPoP proof | Enforce `htu`/`htm`, `iat` freshness, `jti` uniqueness; services may require **nonce**     | | ||||||
|  | | Impersonation       | Fake client      | mTLS or `private_key_jwt` with pinned JWK; client registration & rotation                  | | ||||||
|  | | Key compromise      | Signing key leak | HSM/KMS storage, key rotation, audit; emergency key revoke path; narrow token TTL          | | ||||||
|  | | Cross‑tenant abuse  | Scope elevation  | Enforce `aud`, `tid`, `inst` at issuance and resource servers                              | | ||||||
|  | | Downgrade to bearer | Strip DPoP       | Resource servers require DPoP/mTLS based on `aud`; reject bearer without `cnf`             | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Deployment & HA | ||||||
|  |  | ||||||
|  | * **Stateless** microservice, containerized; run ≥ 2 replicas behind LB. | ||||||
|  | * **DB**: HA Postgres (or MySQL) for clients/roles; **Redis** for device codes, DPoP nonces/jtis. | ||||||
|  | * **Secrets**: mount client JWKs via K8s Secrets/HashiCorp Vault; signing keys via KMS. | ||||||
|  | * **Backups**: DB daily; Redis not critical (ephemeral). | ||||||
|  | * **Disaster recovery**: export/import of client registry; JWKS rehydrate from KMS. | ||||||
|  | * **Compliance**: TLS audit; penetration testing for OIDC flows. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 18) Implementation notes | ||||||
|  |  | ||||||
|  | * Reference stack: **.NET 10** + **OpenIddict 6** (or IdentityServer if licensed) with custom DPoP validator and mTLS binding middleware. | ||||||
|  | * Keep the DPoP/JTI cache pluggable; allow Redis/Memcached. | ||||||
|  | * Provide **client SDKs** for C# and Go: DPoP key mgmt, proof generation, nonce handling, token refresh helper. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 19) Quick reference — wire examples | ||||||
|  |  | ||||||
|  | **Access token (payload excerpt)** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "iss": "https://authority.internal", | ||||||
|  |   "sub": "scanner-web", | ||||||
|  |   "aud": "signer", | ||||||
|  |   "exp": 1760668800, | ||||||
|  |   "iat": 1760668620, | ||||||
|  |   "nbf": 1760668620, | ||||||
|  |   "jti": "9d9c3f01-6e1a-49f1-8f77-9b7e6f7e3c50", | ||||||
|  |   "scope": "signer.sign", | ||||||
|  |   "tid": "tenant-01", | ||||||
|  |   "inst": "install-7A2B", | ||||||
|  |   "cnf": { "jkt": "KcVb2V...base64url..." } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **DPoP proof header fields (for POST /sign/dsse)** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "htu": "https://signer.internal/sign/dsse", | ||||||
|  |   "htm": "POST", | ||||||
|  |   "iat": 1760668620, | ||||||
|  |   "jti": "4b1c9b3c-8a95-4c58-8a92-9c6cfb4a6a0b" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Signer validates that `hash(JWK)` in the proof matches `cnf.jkt` in the token. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 20) Rollout plan | ||||||
|  |  | ||||||
|  | 1. **MVP**: Client Credentials (private_key_jwt + DPoP), JWKS, short OpToks, per‑audience scopes. | ||||||
|  | 2. **Add**: mTLS‑bound tokens for Signer/Attestor; device code for CLI; optional introspection. | ||||||
|  | 3. **Hardening**: DPoP nonce support; full audit pipeline; HA tuning. | ||||||
|  | 4. **UX**: Tenant/installation admin UI; role→scope editors; client bootstrap wizards. | ||||||
|  |  | ||||||
							
								
								
									
										389
									
								
								docs/ARCHITECTURE_CLI.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								docs/ARCHITECTURE_CLI.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,389 @@ | |||||||
|  | # component_architecture_cli.md — **Stella Ops CLI** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for **Stella Ops CLI**: command surface, process model, auth (Authority/DPoP), integration with Scanner/Vexer/Feedser/Signer/Attestor, Buildx plug‑in management, offline kit behavior, packaging, observability, security posture, and CI ergonomics. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Provide a **fast, deterministic, CI‑friendly** command‑line interface to drive Stella Ops workflows: | ||||||
|  |  | ||||||
|  | * Build‑time SBOM generation via **Buildx generator** orchestration. | ||||||
|  | * Post‑build **scan/compose/diff/export** against **Scanner.WebService**. | ||||||
|  | * **Policy** operations and **VEX/Vuln** data pulls (operator tasks). | ||||||
|  | * **Verification** (attestation, referrers, signatures) for audits. | ||||||
|  | * Air‑gapped/offline **kit** administration. | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * CLI **never** signs; it only calls **Signer**/**Attestor** via backend APIs when needed (e.g., `report --attest`). | ||||||
|  | * CLI **does not** store long‑lived credentials beyond OS keychain; tokens are **short** (Authority OpToks). | ||||||
|  | * Heavy work (scanning, merging, policy) is executed **server‑side** (Scanner/Vexer/Feedser). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Solution layout & runtime form | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | src/ | ||||||
|  |  ├─ StellaOps.Cli/                         # net10.0 (Native AOT) single binary | ||||||
|  |  ├─ StellaOps.Cli.Core/                    # verb plumbing, config, HTTP, auth | ||||||
|  |  ├─ StellaOps.Cli.Plugins/                 # optional verbs packaged as plugins | ||||||
|  |  ├─ StellaOps.Cli.Tests/                   # unit + golden-output tests | ||||||
|  |  └─ packaging/ | ||||||
|  |      ├─ msix / msi / deb / rpm / brew formula | ||||||
|  |      └─ scoop manifest / winget manifest | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Language/runtime**: .NET 10 **Native AOT** for speed/startup; Linux builds use **musl** static when possible. | ||||||
|  |  | ||||||
|  | **OS targets**: linux‑x64/arm64, windows‑x64/arm64, macOS‑x64/arm64. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Command surface (verbs) | ||||||
|  |  | ||||||
|  | > All verbs default to **JSON** output when `--json` is set (CI mode). Human output is concise, deterministic. | ||||||
|  |  | ||||||
|  | ### 2.1 Auth & profile | ||||||
|  |  | ||||||
|  | * `auth login` | ||||||
|  |  | ||||||
|  |   * Modes: **device‑code** (default), **client‑credentials** (service principal). | ||||||
|  |   * Produces **Authority** access token (OpTok) + stores **DPoP** keypair in OS keychain. | ||||||
|  | * `auth status` — show current issuer, subject, audiences, expiry. | ||||||
|  | * `auth logout` — wipe cached tokens/keys. | ||||||
|  |  | ||||||
|  | ### 2.2 Build‑time SBOM (Buildx) | ||||||
|  |  | ||||||
|  | * `buildx install` — install/update the **StellaOps.Scanner.Sbomer.BuildXPlugin** on the host. | ||||||
|  | * `buildx verify` — ensure generator is usable. | ||||||
|  | * `buildx build` — thin wrapper around `docker buildx build --attest=type=sbom,generator=stellaops/sbom-indexer` with convenience flags: | ||||||
|  |  | ||||||
|  |   * `--attest` (request Signer/Attestor via backend post‑push) | ||||||
|  |   * `--provenance` pass‑through (optional) | ||||||
|  |  | ||||||
|  | ### 2.3 Scanning & artifacts | ||||||
|  |  | ||||||
|  | * `scan image <ref|digest>` | ||||||
|  |  | ||||||
|  |   * Options: `--force`, `--wait`, `--view=inventory|usage|both`, `--format=cdx-json|cdx-pb|spdx-json`, `--attest` (ask backend to sign/log). | ||||||
|  |   * Streams progress; exits early unless `--wait`. | ||||||
|  | * `diff image --old <digest> --new <digest> [--view ...]` — show layer‑attributed changes. | ||||||
|  | * `export sbom <digest> [--view ... --format ... --out file]` — download artifact. | ||||||
|  | * `report final <digest> [--policy-revision ... --attest]` — request PASS/FAIL report from backend (policy+vex) and optional attestation. | ||||||
|  |  | ||||||
|  | ### 2.4 Policy & data | ||||||
|  |  | ||||||
|  | * `policy get/set/apply` — fetch active policy, apply staged policy, compute digest. | ||||||
|  | * `feedser export` — trigger/export canonical JSON or Trivy DB (admin). | ||||||
|  | * `vexer export` — trigger/export consensus/raw claims (admin). | ||||||
|  |  | ||||||
|  | ### 2.5 Verification | ||||||
|  |  | ||||||
|  | * `verify attestation --uuid <rekor-uuid> | --artifact <sha256> | --bundle <path>` — call **Attestor /verify** and print proof summary. | ||||||
|  | * `verify referrers <digest>` — ask **Signer /verify/referrers** (is image Stella‑signed?). | ||||||
|  | * `verify image-signature <ref|digest>` — standalone cosign verification (optional, local). | ||||||
|  |  | ||||||
|  | ### 2.6 Runtime (Zastava helper) | ||||||
|  |  | ||||||
|  | * `runtime policy test --images <digest,...> [--ns <name> --labels k=v,...]` — ask backend `/policy/runtime` like the webhook would. | ||||||
|  |  | ||||||
|  | ### 2.7 Offline kit | ||||||
|  |  | ||||||
|  | * `offline kit pull` — fetch latest **Feedser JSON + Trivy DB + Vexer exports** as a tarball from a mirror. | ||||||
|  | * `offline kit import <tar>` — upload the kit to on‑prem services (Feedser/Vexer). | ||||||
|  | * `offline kit status` — list current seed versions. | ||||||
|  |  | ||||||
|  | ### 2.8 Utilities | ||||||
|  |  | ||||||
|  | * `config set/get` — endpoint & defaults. | ||||||
|  | * `whoami` — short auth display. | ||||||
|  | * `version` — CLI + protocol versions; release channel. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) AuthN: Authority + DPoP | ||||||
|  |  | ||||||
|  | ### 3.1 Token acquisition | ||||||
|  |  | ||||||
|  | * **Device‑code**: the CLI opens an OIDC device code flow against **Authority**; the browser login is optional for service principals. | ||||||
|  | * **Client‑credentials**: service principals use **private_key_jwt** or **mTLS** to get tokens. | ||||||
|  |  | ||||||
|  | ### 3.2 DPoP key management | ||||||
|  |  | ||||||
|  | * On first login, the CLI generates an **ephemeral JWK** (Ed25519) and stores it in the **OS keychain** (Keychain/DPAPI/KWallet/Gnome Keyring). | ||||||
|  | * Every request to backend services includes a **DPoP proof**; CLI refreshes tokens as needed. | ||||||
|  |  | ||||||
|  | ### 3.3 Multi‑audience & scopes | ||||||
|  |  | ||||||
|  | * CLI requests **audiences** as needed per verb: | ||||||
|  |  | ||||||
|  |   * `scanner` for scan/export/report/diff | ||||||
|  |   * `signer` (indirect; usually backend calls Signer) | ||||||
|  |   * `attestor` for verify | ||||||
|  |   * `feedser`/`vexer` for admin verbs | ||||||
|  |  | ||||||
|  | CLI rejects verbs if required scopes are missing. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Process model & reliability | ||||||
|  |  | ||||||
|  | ### 4.1 HTTP client | ||||||
|  |  | ||||||
|  | * Single **http2** client with connection pooling, DNS pinning, retry/backoff (idempotent GET/POST marked safe). | ||||||
|  | * **DPoP nonce** handling: on `401` with nonce challenge, CLI replays once. | ||||||
|  |  | ||||||
|  | ### 4.2 Streaming | ||||||
|  |  | ||||||
|  | * `scan` and `report` support **server‑sent JSON lines** (progress events). | ||||||
|  | * `--json` prints machine events; human mode shows compact spinners and crucial updates only. | ||||||
|  |  | ||||||
|  | ### 4.3 Exit codes (CI‑safe) | ||||||
|  |  | ||||||
|  | | Code | Meaning                                     | | ||||||
|  | | ---- | ------------------------------------------- | | ||||||
|  | | 0    | Success                                     | | ||||||
|  | | 2    | Policy fail (final report verdict=fail)     | | ||||||
|  | | 3    | Verification failed (attestation/signature) | | ||||||
|  | | 4    | Auth error (invalid/missing token/DPoP)     | | ||||||
|  | | 5    | Resource not found (image/SBOM)             | | ||||||
|  | | 6    | Rate limited / quota exceeded               | | ||||||
|  | | 7    | Backend unavailable (retryable)             | | ||||||
|  | | 9    | Invalid arguments                           | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Configuration model | ||||||
|  |  | ||||||
|  | **Precedence:** CLI flags → env vars → config file → defaults. | ||||||
|  |  | ||||||
|  | **Config file**: `${XDG_CONFIG_HOME}/stellaops/config.yaml` (Windows: `%APPDATA%\StellaOps\config.yaml`) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | cli: | ||||||
|  |   authority: "https://authority.internal" | ||||||
|  |   backend: | ||||||
|  |     scanner: "https://scanner-web.internal" | ||||||
|  |     attestor: "https://attestor.internal" | ||||||
|  |     feedser: "https://feedser-web.internal" | ||||||
|  |     vexer: "https://vexer-web.internal" | ||||||
|  |   auth: | ||||||
|  |     audienceDefault: "scanner" | ||||||
|  |     deviceCode: true | ||||||
|  |   output: | ||||||
|  |     json: false | ||||||
|  |     color: auto | ||||||
|  |   tls: | ||||||
|  |     caBundle: "/etc/ssl/certs/ca-bundle.crt" | ||||||
|  |   offline: | ||||||
|  |     kitMirror: "s3://mirror/stellaops-kit" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Environment variables: `STELLAOPS_AUTHORITY`, `STELLAOPS_SCANNER_URL`, etc. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Buildx generator orchestration | ||||||
|  |  | ||||||
|  | * `buildx install` locates the Docker root directory, writes the **generator** plugin manifest, and pulls `stellaops/sbom-indexer` image (pinned digest). | ||||||
|  | * `buildx build` wrapper injects: | ||||||
|  |  | ||||||
|  |   * `--attest=type=sbom,generator=stellaops/sbom-indexer` | ||||||
|  |   * `--label org.stellaops.request=sbom` | ||||||
|  | * Post‑build: CLI optionally calls **Scanner.WebService** to **verify referrers**, **compose** image SBOMs, and **attest** via Signer/Attestor. | ||||||
|  |  | ||||||
|  | **Detection**: If Buildx or generator unavailable, CLI falls back to **post‑build scan** with a warning. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Artifact handling | ||||||
|  |  | ||||||
|  | * **Downloads** (`export sbom`, `report final`): stream to file; compute sha256 on the fly; write sidecar `.sha256` and optional **verification bundle** (if `--bundle`). | ||||||
|  | * **Uploads** (`offline kit import`): chunked upload; retry on transient errors; show progress bar (unless `--json`). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Security posture | ||||||
|  |  | ||||||
|  | * **DPoP private keys** stored in **OS keychain**; metadata cached in config. | ||||||
|  | * **No plaintext tokens** on disk; short‑lived **OpToks** held in memory. | ||||||
|  | * **TLS**: verify backend certificates; allow custom CA bundle for on‑prem. | ||||||
|  | * **Redaction**: CLI logs remove `Authorization`, DPoP headers, PoE tokens. | ||||||
|  | * **Supply chain**: CLI distribution binaries are **cosign‑signed**; `stellaops version --verify` checks its own signature. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Observability | ||||||
|  |  | ||||||
|  | * `--verbose` adds request IDs, timings, and retry traces. | ||||||
|  | * **Metrics** (optional, disabled by default): Prometheus text file exporter for local monitoring in long‑running agents. | ||||||
|  | * **Structured logs** (`--json`): per‑event JSON lines with `ts`, `verb`, `status`, `latencyMs`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Performance targets | ||||||
|  |  | ||||||
|  | * Startup ≤ **20 ms** (AOT). | ||||||
|  | * `scan image` request/response overhead ≤ **5 ms** (excluding server work). | ||||||
|  | * Buildx wrapper overhead negligible (<1 ms). | ||||||
|  | * Large artifact download (100 MB) sustained ≥ **80 MB/s** on local networks. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Tests & golden outputs | ||||||
|  |  | ||||||
|  | * **Unit tests**: argument parsing, config precedence, URL resolution, DPoP proof creation. | ||||||
|  | * **Integration tests** (Testcontainers): mock Authority/Scanner/Attestor; CI pipeline with fake registry. | ||||||
|  | * **Golden outputs**: verb snapshots for `--json` across OSes; kept in `tests/golden/…`. | ||||||
|  | * **Contract tests**: ensure API shapes match service OpenAPI; fail build if incompatible. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Error envelopes (human + JSON) | ||||||
|  |  | ||||||
|  | **Human:** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | ✖ Policy FAIL: 3 high, 1 critical (VEX suppressed 12) | ||||||
|  |   - pkg:rpm/openssl (CVE-2025-12345) — affected (vendor) — fixed in 3.0.14 | ||||||
|  |   - pkg:npm/lodash (GHSA-xxxx) — affected — no fix | ||||||
|  |   See: https://ui.internal/scans/sha256:... | ||||||
|  | Exit code: 2 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **JSON (`--json`):** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { "event":"report", "status":"fail", "critical":1, "high":3, "url":"https://ui..." } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Admin & advanced flags | ||||||
|  |  | ||||||
|  | * `--authority`, `--scanner`, `--attestor`, `--feedser`, `--vexer` override config URLs. | ||||||
|  | * `--no-color`, `--quiet`, `--json`. | ||||||
|  | * `--timeout`, `--retries`, `--retry-backoff-ms`. | ||||||
|  | * `--ca-bundle`, `--insecure` (dev only; prints warning). | ||||||
|  | * `--trace` (dump HTTP traces to file; scrubbed). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Interop with other tools | ||||||
|  |  | ||||||
|  | * Emits **CycloneDX Protobuf** directly to stdout when `export sbom --format cdx-pb --out -`. | ||||||
|  | * Pipes to `jq`/`yq` cleanly in JSON mode. | ||||||
|  | * Can act as a **credential helper** for scripts: `stellaops auth token --aud scanner` prints a one‑shot token for curl. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Packaging & distribution | ||||||
|  |  | ||||||
|  | * **Installers**: deb/rpm (postinst registers completions), Homebrew, Scoop, Winget, MSI/MSIX. | ||||||
|  | * **Shell completions**: bash/zsh/fish/pwsh. | ||||||
|  | * **Update channel**: `stellaops self-update` (optional) fetches cosign‑signed release manifest; corporate environments can disable. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Security hard lines | ||||||
|  |  | ||||||
|  | * Refuse to print token values; redact Authorization headers in verbose output. | ||||||
|  | * Disallow `--insecure` unless `STELLAOPS_CLI_ALLOW_INSECURE=1` set (double opt‑in). | ||||||
|  | * Enforce **short token TTL**; refresh proactively when <30 s left. | ||||||
|  | * Device‑code cache binding to **machine** and **user** (protect against copy to other machines). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Wire sequences | ||||||
|  |  | ||||||
|  | **A) Scan & wait with attestation** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant CLI | ||||||
|  |   participant Auth as Authority | ||||||
|  |   participant SW as Scanner.WebService | ||||||
|  |   participant SG as Signer | ||||||
|  |   participant AT as Attestor | ||||||
|  |  | ||||||
|  |   CLI->>Auth: device code flow (DPoP) | ||||||
|  |   Auth-->>CLI: OpTok (aud=scanner) | ||||||
|  |  | ||||||
|  |   CLI->>SW: POST /scans { imageRef, attest:true } | ||||||
|  |   SW-->>CLI: { scanId } | ||||||
|  |   CLI->>SW: GET /scans/{id} (poll) | ||||||
|  |   SW-->>CLI: { status: completed, artifacts, rekor? }  # if attested | ||||||
|  |  | ||||||
|  |   alt attestation pending | ||||||
|  |     SW->>SG: POST /sign/dsse (server-side) | ||||||
|  |     SG-->>SW: DSSE | ||||||
|  |     SW->>AT: POST /rekor/entries | ||||||
|  |     AT-->>SW: { uuid, proof } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   CLI->>SW: GET /sboms/<digest>?format=cdx-pb&view=usage | ||||||
|  |   SW-->>CLI: bytes | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **B) Verify attestation by artifact** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant CLI | ||||||
|  |   participant AT as Attestor | ||||||
|  |  | ||||||
|  |   CLI->>AT: POST /rekor/verify { artifactSha256 } | ||||||
|  |   AT-->>CLI: { ok:true, uuid, index, logURL } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 18) Roadmap (CLI) | ||||||
|  |  | ||||||
|  | * `scan fs <path>` (local filesystem tree) → upload to backend for analysis. | ||||||
|  | * `policy test --sbom <file>` (simulate policy results offline using local policy bundle). | ||||||
|  | * `runtime capture` (developer mode) — capture small `/proc/<pid>/maps` for troubleshooting. | ||||||
|  | * Pluggable output renderers for SARIF/HTML (admin‑controlled). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 19) Example CI snippets | ||||||
|  |  | ||||||
|  | **GitHub Actions (post‑build)** | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | - name: Login (device code w/ OIDC broker) | ||||||
|  |   run: stellaops auth login --json --authority ${{ secrets.AUTHORITY_URL }} | ||||||
|  |  | ||||||
|  | - name: Scan | ||||||
|  |   run: stellaops scan image ${{ steps.build.outputs.digest }} --wait --json | ||||||
|  |  | ||||||
|  | - name: Export (usage view, protobuf) | ||||||
|  |   run: stellaops export sbom ${{ steps.build.outputs.digest }} --view usage --format cdx-pb --out sbom.pb | ||||||
|  |  | ||||||
|  | - name: Verify attestation | ||||||
|  |   run: stellaops verify attestation --artifact $(sha256sum sbom.pb | cut -d' ' -f1) --json | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **GitLab (buildx generator)** | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | script: | ||||||
|  |   - stellaops buildx install | ||||||
|  |   - docker buildx build --attest=type=sbom,generator=stellaops/sbom-indexer -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . | ||||||
|  |   - stellaops scan image $CI_REGISTRY_IMAGE@$IMAGE_DIGEST --wait --json | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 20) Test matrix (OS/arch) | ||||||
|  |  | ||||||
|  | * Linux: ubuntu‑20.04/22.04/24.04 (x64, arm64), alpine (musl). | ||||||
|  | * macOS: 13–15 (x64, arm64). | ||||||
|  | * Windows: 10/11, Server 2019/2022 (x64, arm64). | ||||||
|  | * Docker engines: Docker Desktop, containerd‑based runners. | ||||||
|  |  | ||||||
							
								
								
									
										462
									
								
								docs/ARCHITECTURE_DEVOPS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								docs/ARCHITECTURE_DEVOPS.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,462 @@ | |||||||
|  | # component_architecture_devops.md — **Stella Ops Release & Operations** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready blueprint for **how Stella Ops is built, versioned, signed, distributed, upgraded, licensed (PoE)**, and operated in customer environments (online and air‑gapped). Covers reproducible builds, supply‑chain attestations, registries, offline kits, migration/rollback, artifact lifecycle (MinIO/Mongo), monitoring SLOs, and customer activation. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Product vision (operations lens) | ||||||
|  |  | ||||||
|  | Stella Ops must be **trustable at a glance** and **boringly operable**: | ||||||
|  |  | ||||||
|  | * Every release ships with **first‑party SBOMs, provenance, and signatures**; services verify **each other’s** integrity at runtime. | ||||||
|  | * Customers can deploy by **digest** and stay aligned with **LTS/stable/edge** channels. | ||||||
|  | * Paid customers receive **attestation authority** (Signer accepts their PoE) while the core platform remains **free to run**. | ||||||
|  | * Air‑gapped customers receive **offline kits** with verifiable digests and deterministic import. | ||||||
|  | * Artifacts expire predictably; operators know what’s kept, for how long, and why. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Release trains & versioning | ||||||
|  |  | ||||||
|  | ### 1.1 Channels | ||||||
|  |  | ||||||
|  | * **LTS** (12‑month support window): quarterly cadence (Q1/Q2/Q3/Q4). | ||||||
|  | * **Stable** (default): monthly rollup (bug fixes + compatible features). | ||||||
|  | * **Edge**: weekly; for early adopters, no guarantees. | ||||||
|  |  | ||||||
|  | ### 1.2 Version strings | ||||||
|  |  | ||||||
|  | Semantic core + calendar tag: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | <MAJOR>.<MINOR>.<PATCH>  (<YYYY>.<MM>)   e.g., 2.4.1 (2027.06) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | * **MAJOR**: breaking API/DB changes (rare). | ||||||
|  | * **MINOR**: new features, compatible schema migrations (expand/contract pattern). | ||||||
|  | * **PATCH**: bug fixes, perf and security updates. | ||||||
|  | * **Calendar tag** exposes **release year** used by Signer for **PoE window checks**. | ||||||
|  |  | ||||||
|  | ### 1.3 Component alignment | ||||||
|  |  | ||||||
|  | A release is a **bundle** of image digests + charts + manifests. All services in a bundle are **wire‑compatible**. Mixed minor versions are allowed within a bounded skew: | ||||||
|  |  | ||||||
|  | * **Web UI ↔ backend**: `±1 minor`. | ||||||
|  | * **Scanner ↔ Policy/Vexer/Feedser**: `±1 minor`. | ||||||
|  | * **Authority/Signer/Attestor triangle**: **must** be same minor (crypto and DPoP/mTLS binding rules). | ||||||
|  |  | ||||||
|  | At startup, services **self‑advertise** their semver & channel; the UI surfaces **mismatch warnings**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Supply‑chain pipeline (how a release is built) | ||||||
|  |  | ||||||
|  | ### 2.1 Deterministic builds | ||||||
|  |  | ||||||
|  | * **Builders**: isolated **BuildKit** workers with pinned base images (digest only). | ||||||
|  | * **Pinning**: lock files or `go.mod`, `package-lock.json`, `global.json`, `Directory.Packages.props` are **frozen** at tag. | ||||||
|  | * **Reproducibility**: timestamps normalized; source date epoch; deterministic zips/tars. | ||||||
|  | * **Multi‑arch**: linux/amd64 + linux/arm64 (Windows images track M2 roadmap). | ||||||
|  |  | ||||||
|  | ### 2.2 First‑party SBOMs & provenance | ||||||
|  |  | ||||||
|  | * Each image gets **CycloneDX (JSON+Protobuf) SBOM** and **SLSA‑style provenance** attached as **OCI referrers**. | ||||||
|  | * Scanner’s **Buildx generator** is used to produce SBOMs *during* build; a separate post‑build scan verifies parity (red flag if drift). | ||||||
|  | * **Release manifest** (see §6.1) lists all digests and SBOM/attestation refs. | ||||||
|  |  | ||||||
|  | ### 2.3 Signing & transparency | ||||||
|  |  | ||||||
|  | * Images are **cosign‑signed** (keyless) with a Stella Ops release identity; inclusion in a **transparency log** (Rekor) is required. | ||||||
|  | * SBOM and provenance attestations are **DSSE** and also transparency‑logged. | ||||||
|  | * Release keys (Fulcio roots or public keys) are embedded in **Signer** policy (for **scanner‑release validation** at customer side). | ||||||
|  |  | ||||||
|  | ### 2.4 Gates & tests | ||||||
|  |  | ||||||
|  | * **Static**: linters, codegen checks, protobuf API freeze (backward‑compat tests). | ||||||
|  | * **Unit/integration**: per‑component, plus **end‑to‑end** flows (scan→vex→policy→sign→attest). | ||||||
|  | * **Perf SLOs**: hot paths (SBOM compose, diff, export) measured against budgets. | ||||||
|  | * **Security**: dependency audit vs Feedser export; container hardening tests; minimal caps. | ||||||
|  | * **Canary cohort**: internal staging + selected customers; one week on **edge** before **stable** tag. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Distribution & activation | ||||||
|  |  | ||||||
|  | ### 3.1 Registries | ||||||
|  |  | ||||||
|  | * **Primary**: `registry.stella-ops.org` (OCI v2, supports Referrers API). | ||||||
|  | * **Mirrors**: GHCR (read‑only), regional mirrors for latency. | ||||||
|  | * **Pull by digest only** in Kubernetes/Compose manifests. | ||||||
|  |  | ||||||
|  | **Gating policy**: | ||||||
|  |  | ||||||
|  | * **Core images** (Authority, Scanner, Feedser, Vexer, Attestor, UI): public **read**. | ||||||
|  | * **Enterprise add‑ons** (if any) and **pre‑release**: private repos via OAuth2 token service. | ||||||
|  |  | ||||||
|  | > Monetization lever is **signing** (PoE gate), not image pulls, so the core remains simple to consume. | ||||||
|  |  | ||||||
|  | ### 3.2 OAuth2 token service (for private repos) | ||||||
|  |  | ||||||
|  | * Docker Registry’s token flow backed by **Authority**: | ||||||
|  |  | ||||||
|  |   1. Client hits registry (`401` with `WWW-Authenticate: Bearer realm=…`). | ||||||
|  |   2. Client gets an **access token** from the token service (validated by Authority) with `scope=repository:…:pull`. | ||||||
|  |   3. Registry allows pull for the requested repo. | ||||||
|  | * Tokens are **short‑lived** (60–300 s) and **DPoP‑bound**. | ||||||
|  |  | ||||||
|  | ### 3.3 Offline kits (air‑gapped) | ||||||
|  |  | ||||||
|  | * Tarball per release channel: | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   stellaops-kit-<ver>-<channel>.tar.zst | ||||||
|  |     /images/   OCI layout with all first-party images (multi-arch) | ||||||
|  |     /sboms/    CycloneDX JSON+PB for each image | ||||||
|  |     /attest/   DSSE bundles + Rekor proofs | ||||||
|  |     /charts/   Helm charts + values templates | ||||||
|  |     /compose/  docker-compose.yml + .env template | ||||||
|  |     /plugins/  Feedser/Vexer connectors (restart-time) | ||||||
|  |     /policy/   example policies | ||||||
|  |     /manifest/ release.yaml  (see §6.1) | ||||||
|  |   ``` | ||||||
|  | * Import via CLI `offline kit import`; checks digests and signatures before load. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Licensing (PoE) & monetization | ||||||
|  |  | ||||||
|  | **Principle**: **Only paid Stella Ops issues valid signed attestations.** Running the stack is free; signing requires PoE. | ||||||
|  |  | ||||||
|  | ### 4.1 PoE issuance | ||||||
|  |  | ||||||
|  | * Customers purchase a plan and obtain a **PoE artifact** from `www.stella-ops.org`: | ||||||
|  |  | ||||||
|  |   * **PoE‑JWT** (DPoP/mTLS‑bound) **or** **PoE mTLS client certificate**. | ||||||
|  |   * Contains: `license_id`, `plan`, `valid_release_year`, `max_version`, `exp`, optional `tenant/customer` IDs. | ||||||
|  |  | ||||||
|  | ### 4.2 Online enforcement | ||||||
|  |  | ||||||
|  | * **Signer** calls **Licensing /license/introspect** on every signing request (see signer doc). | ||||||
|  | * If **revoked/expired/out‑of‑window** → deny with machine‑readable reason. | ||||||
|  | * All **valid** bundles are DSSE‑signed and **Attestor** logs them; Rekor UUID returned. | ||||||
|  | * UI badges: “**Verified by Stella Ops**” with link to the public log. | ||||||
|  |  | ||||||
|  | ### 4.3 Air‑gapped / offline | ||||||
|  |  | ||||||
|  | * Customers obtain a **time‑boxed PoE lease** (signed JSON, 7–30 days). | ||||||
|  | * Signer accepts the lease and emits **provisional** attestations (clearly labeled). | ||||||
|  | * When connectivity returns, a background job **endorses** the provisional entries with the cloud service, updating their status to **verified**. | ||||||
|  | * Operators can export a **verification bundle** for auditors even before endorsement (contains DSSE + local Rekor proof + lease snapshot). | ||||||
|  |  | ||||||
|  | ### 4.4 Stolen/abused PoE | ||||||
|  |  | ||||||
|  | * Customers report theft; **Licensing** flags `license_id` as **revoked**. | ||||||
|  | * Subsequent Signer requests **deny**; previous attestations remain but can be marked **contested** (UI shows badge, optional re‑sign path upon new PoE). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Deployment path (customer side) | ||||||
|  |  | ||||||
|  | ### 5.1 First install | ||||||
|  |  | ||||||
|  | * **Helm** (Kubernetes) or **Compose** (VMs). Example (K8s): | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | helm repo add stellaops https://charts.stella-ops.org | ||||||
|  | helm install stella stellaops/platform \ | ||||||
|  |   --version 2.4.0 \ | ||||||
|  |   --set global.channel=stable \ | ||||||
|  |   --set authority.issuer=https://authority.stella.local \ | ||||||
|  |   --set scanner.minio.endpoint=http://minio.stella.local:9000 \ | ||||||
|  |   --set scanner.mongo.uri=mongodb://mongo/scanner \ | ||||||
|  |   --set feedser.mongo.uri=mongodb://mongo/feedser \ | ||||||
|  |   --set vexer.mongo.uri=mongodb://mongo/vexer | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | * Post‑install job registers **Authority clients** (Scanner, Signer, Attestor, UI) and prints **bootstrap** URLs and client credentials (sealed secrets). | ||||||
|  | * UI banner shows **release bundle** and verification state (cosign OK? Rekor OK?). | ||||||
|  |  | ||||||
|  | ### 5.2 Updates | ||||||
|  |  | ||||||
|  | * **Blue/green**: pull new bundle by **digest**; deploy side‑by‑side; cut traffic. | ||||||
|  |  | ||||||
|  | * **Rolling**: upgrade stateful components in safe order: | ||||||
|  |  | ||||||
|  |   1. Authority (stateless, dual‑key rotation ready) | ||||||
|  |   2. Signer/Attestor (same minor) | ||||||
|  |   3. Scanner WebService & Workers | ||||||
|  |   4. Feedser, then Vexer (schema migrations are expand/contract) | ||||||
|  |   5. UI last | ||||||
|  |  | ||||||
|  | * **DB migrations** are **expand/contract**: | ||||||
|  |  | ||||||
|  |   * Phase A (release N): **add** new fields/indexes, write old+new. | ||||||
|  |   * Phase B (N+1): **read** new fields; **drop** old. | ||||||
|  |   * Rollback is a matter of redeploying previous images and keeping both schemas valid. | ||||||
|  |  | ||||||
|  | ### 5.3 Rollback | ||||||
|  |  | ||||||
|  | * Images referenced by **digest**; keep previous release manifest `K` versions back. | ||||||
|  | * `helm rollback` or compose `docker compose -f release-K.yml up -d`. | ||||||
|  | * Mongo migrations are additive; **no destructive changes** within a single minor. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Release payloads & manifests | ||||||
|  |  | ||||||
|  | ### 6.1 Release manifest (`release.yaml`) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | release: | ||||||
|  |   version: "2.4.1" | ||||||
|  |   channel: "stable" | ||||||
|  |   date: "2027-06-20T12:00:00Z" | ||||||
|  |   calendar: "2027.06" | ||||||
|  |   components: | ||||||
|  |     - name: scanner-webservice | ||||||
|  |       image: registry.stella-ops.org/stellaops/scanner-web@sha256:aa..bb | ||||||
|  |       sbom: oci://.../referrers/cdx-json@sha256:11..22 | ||||||
|  |       provenance: oci://.../attest/provenance@sha256:33..44 | ||||||
|  |       signature: { rekorUUID: "…" } | ||||||
|  |     - name: signer | ||||||
|  |       image: registry.stella-ops.org/stellaops/signer@sha256:cc..dd | ||||||
|  |       signature: { rekorUUID: "…" } | ||||||
|  |   charts: | ||||||
|  |     - name: platform | ||||||
|  |       version: "2.4.1" | ||||||
|  |       digest: "sha256:ee..ff" | ||||||
|  |   compose: | ||||||
|  |     file: "docker-compose.yml" | ||||||
|  |     digest: "sha256:77..88" | ||||||
|  |   checksums: | ||||||
|  |     sha256: "… digest of this release.yaml …" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | The manifest is **cosign‑signed**; UI/CLI can verify a bundle without talking to registries. | ||||||
|  |  | ||||||
|  | ### 6.2 Image labels (release metadata) | ||||||
|  |  | ||||||
|  | Each image sets OCI labels: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | org.opencontainers.image.version = "2.4.1" | ||||||
|  | org.opencontainers.image.revision = "<git sha>" | ||||||
|  | org.opencontainers.image.created = "2027-06-20T12:00:00Z" | ||||||
|  | org.stellaops.release.calendar = "2027.06" | ||||||
|  | org.stellaops.release.channel  = "stable" | ||||||
|  | org.stellaops.build.slsaProvenance = "oci://…" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Signer validates **scanner** image’s cosign identity + calendar tag for **release window** checks. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Artifact lifecycle & storage (MinIO/Mongo) | ||||||
|  |  | ||||||
|  | ### 7.1 Buckets & prefixes (MinIO) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | s3://stellaops/ | ||||||
|  |   scanner/ | ||||||
|  |     layers/<sha256>/sbom.cdx.json.zst | ||||||
|  |     images/<imgDigest>/inventory.cdx.pb | ||||||
|  |     images/<imgDigest>/usage.cdx.pb | ||||||
|  |     diffs/<old>_<new>/diff.json.zst | ||||||
|  |     attest/<artifactSha256>.dsse.json | ||||||
|  |   feedser/ | ||||||
|  |     json/<exportId>/... | ||||||
|  |     trivy/<exportId>/... | ||||||
|  |   vexer/ | ||||||
|  |     exports/<exportId>/... | ||||||
|  |   attestor/ | ||||||
|  |     dsse/<bundleSha256>.json | ||||||
|  |     proof/<rekorUuid>.json | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.2 ILM classes | ||||||
|  |  | ||||||
|  | * **`short`**: working artifacts (diffs, queues) — TTL 7–14 days. | ||||||
|  | * **`default`**: SBOMs & indexes — TTL 90–180 days (configurable). | ||||||
|  | * **`compliance`**: signed reports & attested exports — **Object Lock** (governance/compliance) 1–7 years. | ||||||
|  |  | ||||||
|  | ### 7.3 Artifact Lifecycle Controller (ALC) | ||||||
|  |  | ||||||
|  | * A background worker (part of Scanner.WebService) enforces **TTL** and **reference counting**: | ||||||
|  |  | ||||||
|  |   * Artifacts referenced by **reports** or **tickets** are pinned. | ||||||
|  |   * ILM actions logged; UI shows per‑class usage & upcoming purges. | ||||||
|  |  | ||||||
|  | ### 7.4 Mongo retention | ||||||
|  |  | ||||||
|  | * **Scanner**: `runtime.events` use TTL (e.g., 30–90 days); **catalog** permanent. | ||||||
|  | * **Feedser/Vexer**: raw docs keep **last N windows**; canonical stores permanent. | ||||||
|  | * **Attestor**: `entries` permanent; `dedupe` TTL 24–48h. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Observability & SLOs (operations) | ||||||
|  |  | ||||||
|  | * **Uptime SLO**: 99.9% for Signer/Authority/Attestor; 99.5% for Scanner WebService; Vexer/Feedser 99.0%. | ||||||
|  | * **Error budgets**: tracked per month; dashboards show burn rates. | ||||||
|  | * **Golden signals**: | ||||||
|  |  | ||||||
|  |   * **Latency**: token issuance, sign→attest round‑trip, scan enqueue→emit, export build. | ||||||
|  |   * **Saturation**: queue depth, Mongo write IOPS, MinIO net throughput. | ||||||
|  |   * **Traffic**: scans/min, attestations/min, webhook admits/min. | ||||||
|  |   * **Errors**: 5xx rates, cosign verification failures, Rekor timeouts. | ||||||
|  |  | ||||||
|  | Prometheus + OTLP; Grafana dashboards ship in the charts. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Security & compliance operations | ||||||
|  |  | ||||||
|  | * **Key rotation**: | ||||||
|  |  | ||||||
|  |   * Authority JWKS: 60‑day cadence, dual‑key overlap. | ||||||
|  |   * Release signing identities: rotate per minor or quarterly. | ||||||
|  |   * Sigstore roots mirrored and pinned; alarms on drift. | ||||||
|  |  | ||||||
|  | * **FIPS mode** (Gov build): | ||||||
|  |  | ||||||
|  |   * Enforce `ES256` + KMS/HSM; disable Ed25519; MLS ciphers only. | ||||||
|  |   * Local **Rekor v2** and **Fulcio** alternatives; **air‑gapped** CA. | ||||||
|  |  | ||||||
|  | * **Vulnerability response**: | ||||||
|  |  | ||||||
|  |   * Feedser red‑flag advisories trigger accelerated **stable** patch rollout; UI/CLI “security patch available” notice. | ||||||
|  |  | ||||||
|  | * **Backups/DR**: | ||||||
|  |  | ||||||
|  |   * Mongo nightly snapshots; MinIO versioning + replication (if configured). | ||||||
|  |   * Restore runbooks tested quarterly with synthetic data. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Customer update flow (how versions are fetched & activated) | ||||||
|  |  | ||||||
|  | ### 10.1 Online clusters | ||||||
|  |  | ||||||
|  | * **UI** surfaces update banner with **release manifest** diff and risk notes. | ||||||
|  | * Operator approves → **Controller** pulls new images by digest; health‑checks; moves traffic; deprecates old revision. | ||||||
|  | * Post‑switch, **schema Phase B** migrations (if any) run automatically. | ||||||
|  |  | ||||||
|  | ### 10.2 Air‑gapped clusters | ||||||
|  |  | ||||||
|  | * Operator downloads **offline kit** from a mirror → `stellaops offline kit import`. | ||||||
|  | * Controller validates bundle checksums and **cosign signatures**; applies charts/compose by digest. | ||||||
|  | * After install, **verify** page shows green checks: image sigs, SBOMs attached, provenance logged. | ||||||
|  |  | ||||||
|  | ### 10.3 CLI self‑update (optional) | ||||||
|  |  | ||||||
|  | * `stellaops self-update` pulls a **signed release manifest** and verifies the **CLI binary** with cosign before swapping (admin can disable). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Compatibility & deprecation policy | ||||||
|  |  | ||||||
|  | * **APIs** are stable within a **major**; breaking changes imply **MAJOR++** and deprecation period of one minor. | ||||||
|  | * **Storage**: expand/contract; “drop old fields” only after one minor grace. | ||||||
|  | * **Config**: feature flags (default off) for risky features (e.g., eBPF). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Runbooks (selected) | ||||||
|  |  | ||||||
|  | ### 12.1 Lost PoE | ||||||
|  |  | ||||||
|  | 1. Suspend **automatic attestation** jobs. | ||||||
|  | 2. Use CLI `stellaops signer status` to confirm `entitlement_denied`. | ||||||
|  | 3. Obtain new PoE from portal; verify on Signer `/poe/verify`. | ||||||
|  | 4. Re‑enable; optionally **re‑sign** last N reports (UI button → batch). | ||||||
|  |  | ||||||
|  | ### 12.2 Rekor outage (self‑hosted) | ||||||
|  |  | ||||||
|  | * Attestor returns `202 (pending)` with queued proof fetch. | ||||||
|  | * Keep DSSE bundles locally; re‑submit on schedule; UI badge shows **Pending**. | ||||||
|  | * If outage > SLA, you can switch to a **mirror** log in config; Attestor writes to both when restored. | ||||||
|  |  | ||||||
|  | ### 12.3 Emergency downgrade | ||||||
|  |  | ||||||
|  | * Identify prior release manifest (UI → Admin → Releases). | ||||||
|  | * `helm rollback stella <revision>` (or compose apply previous file). | ||||||
|  | * Services tolerate skew per §1.3; ensure **Signer/Authority/Attestor** are rolled together. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Example: cluster bootstrap (Compose) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | version: "3.9" | ||||||
|  | services: | ||||||
|  |   authority: | ||||||
|  |     image: registry.stella-ops.org/stellaops/authority@sha256:... | ||||||
|  |     env_file: ./env/authority.env | ||||||
|  |     ports: ["8440:8440"] | ||||||
|  |   signer: | ||||||
|  |     image: registry.stella-ops.org/stellaops/signer@sha256:... | ||||||
|  |     depends_on: [authority] | ||||||
|  |     environment: | ||||||
|  |       - SIGNER__POE__LICENSING__INTROSPECTURL=https://www.stella-ops.org/api/v1/license/introspect | ||||||
|  |   attestor: | ||||||
|  |     image: registry.stella-ops.org/stellaops/attestor@sha256:... | ||||||
|  |     depends_on: [signer] | ||||||
|  |   scanner-web: | ||||||
|  |     image: registry.stella-ops.org/stellaops/scanner-web@sha256:... | ||||||
|  |     environment: | ||||||
|  |       - SCANNER__S3__ENDPOINT=http://minio:9000 | ||||||
|  |   scanner-worker: | ||||||
|  |     image: registry.stella-ops.org/stellaops/scanner-worker@sha256:... | ||||||
|  |     deploy: { replicas: 4 } | ||||||
|  |   feedser: | ||||||
|  |     image: registry.stella-ops.org/stellaops/feedser@sha256:... | ||||||
|  |   vexer: | ||||||
|  |     image: registry.stella-ops.org/stellaops/vexer@sha256:... | ||||||
|  |   web-ui: | ||||||
|  |     image: registry.stella-ops.org/stellaops/web-ui@sha256:... | ||||||
|  |   mongo: | ||||||
|  |     image: mongo:7 | ||||||
|  |   minio: | ||||||
|  |     image: minio/minio:RELEASE.2025-07-10T00-00-00Z | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Governance & keys (who owns the trust root) | ||||||
|  |  | ||||||
|  | * **Release key policy**: only the Release Engineering group can push signed releases; 4‑eyes approval; TUF‑style manifest possible in future. | ||||||
|  | * **Signer acceptance policy**: embedded release identities are updated **only** via minor upgrade; emergency CRL supported. | ||||||
|  | * **Customer keys**: none needed for core use; enterprise add‑ons may require per‑customer registries and keys. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Roadmap (Ops) | ||||||
|  |  | ||||||
|  | * **Windows containers GA** (Scanner + Zastava). | ||||||
|  | * **Key Transparency** for Signer certs. | ||||||
|  | * **Delta‑kit** (offline) for incremental updates. | ||||||
|  | * **Operator CRDs** (K8s) to manage policy and ILM declaratively. | ||||||
|  | * **SBOM **protobuf** as default transport at rest (smaller, faster). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ### Appendix A — Minimal SLO monitors | ||||||
|  |  | ||||||
|  | * `authority.tokens_issued_total` slope ≈ normal. | ||||||
|  | * `signer.requests_total{result="success"}/minute` > 0 (when scans occur). | ||||||
|  | * `attestor.submit_latency_seconds{quantile=0.95}` < 0.3. | ||||||
|  | * `scanner.scan_latency_seconds{quantile=0.95}` < target per image size. | ||||||
|  | * `feedser.export.duration_seconds` stable; `vexer.consensus.conflicts_total` not exploding after policy changes. | ||||||
|  | * MinIO `s3_requests_errors_total` near zero; Mongo `opcounters` hit expected baseline. | ||||||
|  |  | ||||||
|  | ### Appendix B — Upgrade safety checklist | ||||||
|  |  | ||||||
|  | * Verify **release manifest** signature. | ||||||
|  | * Ensure **Signer/Authority/Attestor** are same minor. | ||||||
|  | * Verify **DB backups** < 24h old. | ||||||
|  | * Confirm **ILM** won’t purge compliance artifacts during upgrade window. | ||||||
|  | * Roll **one component** at a time; watch SLOs; abort on regression. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | **End — component_architecture_devops.md** | ||||||
| @@ -1,190 +1,433 @@ | |||||||
| # ARCHITECTURE.md — **StellaOps.Feedser** | # component_architecture_feedser.md — **Stella Ops Feedser** (2025Q4) | ||||||
|  |  | ||||||
| > **Goal**: Build a sovereign-ready, self-hostable **feed-merge service** that ingests authoritative vulnerability sources, normalizes and de-duplicates them into **MongoDB**, and exports **JSON** and **Trivy-compatible DB** artifacts. | > **Scope.** Implementation‑ready architecture for **Feedser**: the vulnerability ingest/normalize/merge/export subsystem that produces deterministic advisory data for the Scanner + Policy + Vexer pipeline. Covers domain model, connectors, merge rules, storage schema, exports, APIs, performance, security, and test matrices. | ||||||
| > **Form factor**: Long-running **Web Service** with **REST APIs** (health, status, control) and an embedded **internal cron scheduler**. Controllable by StellaOps.Cli (# stella db ...) |  | ||||||
| > **No signing inside Feedser** (signing is a separate pipeline step). |  | ||||||
| > **Runtime SDK baseline**: .NET 10 Preview 7 (SDK 10.0.100-preview.7.25380.108) targeting `net10.0`, aligned with the deployed api.stella-ops.org service. |  | ||||||
| > **Four explicit stages**: |  | ||||||
| > |  | ||||||
| > 1. **Source Download** → raw documents. |  | ||||||
| > 2. **Parse & Normalize** → schema-validated DTOs enriched with canonical identifiers. |  | ||||||
| > 3. **Merge & Deduplicate** → precedence-aware canonical records persisted to MongoDB. |  | ||||||
| > 4. **Export** → JSON or TrivyDB (full or delta), then (externally) sign/publish. |  | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 1) Naming & Solution Layout | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
| **Source connectors** namespace prefix: `StellaOps.Feedser.Source.*` | **Mission.** Acquire authoritative **vulnerability advisories** (vendor PSIRTs, distros, OSS ecosystems, CERTs), normalize them into a **canonical model**, reconcile aliases and version ranges, and export **deterministic artifacts** (JSON, Trivy DB) for fast backend joins. | ||||||
| **Exporters**: |  | ||||||
|  |  | ||||||
| * `StellaOps.Feedser.Exporter.Json` | **Boundaries.** | ||||||
| * `StellaOps.Feedser.Exporter.TrivyDb` |  | ||||||
|  |  | ||||||
| **Projects** (`/src`): | * Feedser **does not** sign with private keys. When attestation is required, the export artifact is handed to the **Signer**/**Attestor** pipeline (out‑of‑process). | ||||||
|  | * Feedser **does not** decide PASS/FAIL; it provides data to the **Policy** engine. | ||||||
|  | * Online operation is **allowlist‑only**; air‑gapped deployments use the **Offline Kit**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Topology & processes | ||||||
|  |  | ||||||
|  | **Process shape:** single ASP.NET Core service `StellaOps.Feedser.WebService` hosting: | ||||||
|  |  | ||||||
|  | * **Scheduler** with distributed locks (Mongo backed). | ||||||
|  | * **Connectors** (fetch/parse/map). | ||||||
|  | * **Merger** (canonical record assembly + precedence). | ||||||
|  | * **Exporters** (JSON, Trivy DB). | ||||||
|  | * **Minimal REST** for health/status/trigger/export. | ||||||
|  |  | ||||||
|  | **Scale:** HA by running N replicas; **locks** prevent overlapping jobs per source/exporter. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Canonical domain model | ||||||
|  |  | ||||||
|  | > Stored in MongoDB (database `feedser`), serialized with a **canonical JSON** writer (stable order, camelCase, normalized timestamps). | ||||||
|  |  | ||||||
|  | ### 2.1 Core entities | ||||||
|  |  | ||||||
|  | **Advisory** | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| StellaOps.Feedser.WebService/        # ASP.NET Core (Minimal API, net10.0 preview) WebService + embedded scheduler | advisoryId          // internal GUID | ||||||
| StellaOps.Feedser.Core/              # Domain models, pipelines, merge/dedupe engine, jobs orchestration | advisoryKey         // stable string key (e.g., CVE-2025-12345 or vendor ID) | ||||||
| StellaOps.Feedser.Models/            # Canonical POCOs, JSON Schemas, enums | title               // short title (best-of from sources) | ||||||
| StellaOps.Feedser.Storage.Mongo/     # Mongo repositories, GridFS access, indexes, resume "flags" | summary             // normalized summary (English; i18n optional) | ||||||
| StellaOps.Feedser.Source.Common/     # HTTP clients, rate-limiters, schema validators, parsers utils | published           // earliest source timestamp | ||||||
| StellaOps.Feedser.Source.Cve/ | modified            // latest source timestamp | ||||||
| StellaOps.Feedser.Source.Nvd/ | severity            // normalized {none, low, medium, high, critical} | ||||||
| StellaOps.Feedser.Source.Ghsa/ | cvss                // {v2?, v3?, v4?} objects (vector, baseScore, severity, source) | ||||||
| StellaOps.Feedser.Source.Osv/ | exploitKnown        // bool (e.g., KEV/active exploitation flags) | ||||||
| StellaOps.Feedser.Source.Jvn/ | references[]        // typed links (advisory, kb, patch, vendor, exploit, blog) | ||||||
| StellaOps.Feedser.Source.CertCc/ | sources[]           // provenance for traceability (doc digests, URIs) | ||||||
| StellaOps.Feedser.Source.Kev/ |  | ||||||
| StellaOps.Feedser.Source.Kisa/ |  | ||||||
| StellaOps.Feedser.Source.CertIn/ |  | ||||||
| StellaOps.Feedser.Source.CertFr/ |  | ||||||
| StellaOps.Feedser.Source.CertBund/ |  | ||||||
| StellaOps.Feedser.Source.Acsc/ |  | ||||||
| StellaOps.Feedser.Source.Cccs/ |  | ||||||
| StellaOps.Feedser.Source.Ru.Bdu/     # HTML→schema with LLM fallback (gated) |  | ||||||
| StellaOps.Feedser.Source.Ru.Nkcki/   # PDF/HTML bulletins → structured |  | ||||||
| StellaOps.Feedser.Source.Vndr.Msrc/ |  | ||||||
| StellaOps.Feedser.Source.Vndr.Cisco/ |  | ||||||
| StellaOps.Feedser.Source.Vndr.Oracle/ |  | ||||||
| StellaOps.Feedser.Source.Vndr.Adobe/   # APSB ingest; emits vendor RangePrimitives with adobe.track/platform/priority telemetry + fixed-status provenance. |  | ||||||
| StellaOps.Feedser.Source.Vndr.Apple/ |  | ||||||
| StellaOps.Feedser.Source.Vndr.Chromium/ |  | ||||||
| StellaOps.Feedser.Source.Vndr.Vmware/ |  | ||||||
| StellaOps.Feedser.Source.Distro.RedHat/ |  | ||||||
| StellaOps.Feedser.Source.Distro.Debian/    # Fetches DSA list + detail HTML, emits EVR RangePrimitives with per-release provenance and telemetry. |  | ||||||
| StellaOps.Feedser.Source.Distro.Ubuntu/   # Ubuntu Security Notices connector (JSON index → EVR ranges with ubuntu.pocket telemetry). |  | ||||||
| StellaOps.Feedser.Source.Distro.Suse/     # CSAF fetch pipeline emitting NEVRA RangePrimitives with suse.status vendor telemetry. |  | ||||||
| StellaOps.Feedser.Source.Ics.Cisa/ |  | ||||||
| StellaOps.Feedser.Source.Ics.Kaspersky/ |  | ||||||
| StellaOps.Feedser.Normalization/     # Canonical mappers, validators, version-range normalization |  | ||||||
| StellaOps.Feedser.Merge/             # Identity graph, precedence, deterministic merge |  | ||||||
| StellaOps.Feedser.Exporter.Json/ |  | ||||||
| StellaOps.Feedser.Exporter.TrivyDb/ |  | ||||||
| StellaOps.Feedser.<Component>.Tests/  # Component-scoped unit/integration suites (Core, Storage.Mongo, Source.*, Exporter.*, WebService, etc.) |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| --- | **Alias** | ||||||
|  |  | ||||||
| ## 2) Runtime Shape | ``` | ||||||
|  | advisoryId | ||||||
|  | scheme              // CVE, GHSA, RHSA, DSA, USN, MSRC, etc. | ||||||
|  | value               // e.g., "CVE-2025-12345" | ||||||
|  | ``` | ||||||
|  |  | ||||||
| **Process**: single service (`StellaOps.Feedser.WebService`) | **Affected** | ||||||
|  |  | ||||||
| * `Program.cs`: top-level entry using **Generic Host**, **DI**, **Options** binding from `appsettings.json` + environment + optional `feedser.yaml`. | ``` | ||||||
| * Built-in **scheduler** (cron-like) + **job manager** with **distributed locks** in Mongo to prevent overlaps, enforce timeouts, allow cancel/kill. | advisoryId | ||||||
| * **REST APIs** for health/readiness/progress/trigger/kill/status. | productKey          // canonical product identity (see 2.2) | ||||||
|  | rangeKind           // semver | evr | nvra | apk | rpm | deb | generic | exact | ||||||
|  | introduced?         // string (format depends on rangeKind) | ||||||
|  | fixed?              // string (format depends on rangeKind) | ||||||
|  | lastKnownSafe?      // optional explicit safe floor | ||||||
|  | arch?               // arch or platform qualifier if source declares (x86_64, aarch64) | ||||||
|  | distro?             // distro qualifier when applicable (rhel:9, debian:12, alpine:3.19) | ||||||
|  | ecosystem?          // npm|pypi|maven|nuget|golang|… | ||||||
|  | notes?              // normalized notes per source | ||||||
|  | ``` | ||||||
|  |  | ||||||
| **Key NuGet concepts** (indicative): `MongoDB.Driver`, `Polly` (retry/backoff), `System.Threading.Channels`, `Microsoft.Extensions.Http`, `Microsoft.Extensions.Hosting`, `Serilog`, `OpenTelemetry`. | **Reference** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | advisoryId | ||||||
|  | url | ||||||
|  | kind                // advisory | patch | kb | exploit | mitigation | blog | cvrf | csaf | ||||||
|  | sourceTag           // e.g., vendor/redhat, distro/debian, oss/ghsa | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **MergeEvent** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | advisoryKey | ||||||
|  | beforeHash          // canonical JSON hash before merge | ||||||
|  | afterHash           // canonical JSON hash after merge | ||||||
|  | mergedAt | ||||||
|  | inputs[]            // source doc digests that contributed | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **ExportState** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | exportKind          // json | trivydb | ||||||
|  | baseExportId?       // last full baseline | ||||||
|  | baseDigest?         // digest of last full baseline | ||||||
|  | lastFullDigest?     // digest of last full export | ||||||
|  | lastDeltaDigest?    // digest of last delta export | ||||||
|  | cursor              // per-kind incremental cursor | ||||||
|  | files[]             // last manifest snapshot (path → sha256) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2.2 Product identity (`productKey`) | ||||||
|  |  | ||||||
|  | * **Primary:** `purl` (Package URL). | ||||||
|  | * **OS packages:** RPM (NEVRA→purl:rpm), DEB (dpkg→purl:deb), APK (apk→purl:alpine), with **EVR/NVRA** preserved. | ||||||
|  | * **Secondary:** `cpe` retained for compatibility; advisory records may carry both. | ||||||
|  | * **Image/platform:** `oci:<registry>/<repo>@<digest>` for image‑level advisories (rare). | ||||||
|  | * **Unmappable:** if a source is non‑deterministic, keep native string under `productKey="native:<provider>:<id>"` and mark **non‑joinable**. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 3) Data Storage — **MongoDB** (single source of truth) | ## 3) Source families & precedence | ||||||
|  |  | ||||||
| **Database**: `feedser` | ### 3.1 Families | ||||||
| **Write concern**: `majority` for merge/export state, `acknowledged` for raw docs. |  | ||||||
| **Collections** (with “flags”/resume points): |  | ||||||
|  |  | ||||||
| * `source` | * **Vendor PSIRTs**: Microsoft, Oracle, Cisco, Adobe, Apple, VMware, Chromium… | ||||||
|   * `_id`, `name`, `type`, `baseUrl`, `auth`, `notes`. | * **Linux distros**: Red Hat, SUSE, Ubuntu, Debian, Alpine… | ||||||
| * `source_state` | * **OSS ecosystems**: OSV, GHSA (GitHub Security Advisories), PyPI, npm, Maven, NuGet, Go. | ||||||
|   * Keys: `sourceName` (unique), `enabled`, `cursor`, `lastSuccess`, `failCount`, `backoffUntil`, `paceOverrides`, `paused`. | * **CERTs / national CSIRTs**: CISA (KEV, ICS), JVN, ACSC, CCCS, KISA, CERT‑FR/BUND, etc. | ||||||
|   * Drives incremental fetch/parse/map resume and operator pause/pace controls. |  | ||||||
| * `document` |  | ||||||
|   * `_id`, `sourceName`, `uri`, `fetchedAt`, `sha256`, `contentType`, `status`, `metadata`, `gridFsId`, `etag`, `lastModified`. |  | ||||||
|   * Index `{sourceName:1, uri:1}` unique; optional TTL for superseded versions. |  | ||||||
| * `dto` |  | ||||||
|   * `_id`, `sourceName`, `documentId`, `schemaVer`, `payload` (BSON), `validatedAt`. |  | ||||||
|   * Index `{sourceName:1, documentId:1}`. |  | ||||||
| * `advisory` |  | ||||||
|   * `_id`, `advisoryKey`, `title`, `summary`, `lang`, `published`, `modified`, `severity`, `exploitKnown`. |  | ||||||
|   * Unique `{advisoryKey:1}` plus indexes on `modified` and `published`. |  | ||||||
| * `alias` |  | ||||||
|   * `advisoryId`, `scheme`, `value` with index `{scheme:1, value:1}`. |  | ||||||
| * `affected` |  | ||||||
|   * `advisoryId`, `platform`, `name`, `versionRange`, `cpe`, `purl`, `fixedBy`, `introducedVersion`. |  | ||||||
|   * Index `{platform:1, name:1}`, `{advisoryId:1}`. |  | ||||||
| * `reference` |  | ||||||
|   * `advisoryId`, `url`, `kind`, `sourceTag` (e.g., advisory/patch/kb). |  | ||||||
| * Flags collections: `kev_flag`, `ru_flags`, `jp_flags`, `psirt_flags` keyed by `advisoryId`. |  | ||||||
| * `merge_event` |  | ||||||
|   * `_id`, `advisoryKey`, `beforeHash`, `afterHash`, `mergedAt`, `inputs` (document ids). |  | ||||||
| * `export_state` |  | ||||||
|   * `_id` (`json`/`trivydb`), `baseExportId`, `baseDigest`, `lastFullDigest`, `lastDeltaDigest`, `exportCursor`, `targetRepo`, `exporterVersion`. |  | ||||||
| * `locks` |  | ||||||
|   * `_id` (`jobKey`), `holder`, `acquiredAt`, `heartbeatAt`, `leaseMs`, `ttlAt` (TTL index cleans dead locks). |  | ||||||
| * `jobs` |  | ||||||
|   * `_id`, `type`, `args`, `state`, `startedAt`, `endedAt`, `error`, `owner`, `heartbeatAt`, `timeoutMs`. |  | ||||||
|  |  | ||||||
| **GridFS buckets**: `fs.documents` for raw large payloads; referenced by `document.gridFsId`. | ### 3.2 Precedence (when claims conflict) | ||||||
|  |  | ||||||
|  | 1. **Vendor PSIRT** (authoritative for their product). | ||||||
|  | 2. **Distro** (authoritative for packages they ship, including backports). | ||||||
|  | 3. **Ecosystem** (OSV/GHSA) for library semantics. | ||||||
|  | 4. **CERTs/aggregators** for enrichment (KEV/known exploited). | ||||||
|  |  | ||||||
|  | > Precedence affects **Affected** ranges and **fixed** info; **severity** is normalized to the **maximum** credible severity unless policy overrides. Conflicts are retained with **source provenance**. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 4) Job & Scheduler Model | ## 4) Connectors & normalization | ||||||
|  |  | ||||||
| * Scheduler stores cron expressions per source/exporter in config; persists next-run pointers in Mongo. | ### 4.1 Connector contract | ||||||
| * Jobs acquire locks (`locks` collection) to ensure singleton execution per source/exporter. |  | ||||||
| * Supports manual triggers via API endpoints (`POST /jobs/{type}`) and pause/resume toggles per source. |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ## 5) Connector Contracts |  | ||||||
|  |  | ||||||
| Connectors implement: |  | ||||||
|  |  | ||||||
| ```csharp | ```csharp | ||||||
| public interface IFeedConnector { | public interface IFeedConnector { | ||||||
|   string SourceName { get; } |   string SourceName { get; } | ||||||
|     Task FetchAsync(IServiceProvider sp, CancellationToken ct); |   Task FetchAsync(IServiceProvider sp, CancellationToken ct);   // -> document collection | ||||||
|     Task ParseAsync(IServiceProvider sp, CancellationToken ct); |   Task ParseAsync(IServiceProvider sp, CancellationToken ct);   // -> dto collection (validated) | ||||||
|     Task MapAsync(IServiceProvider sp, CancellationToken ct); |   Task MapAsync(IServiceProvider sp, CancellationToken ct);     // -> advisory/alias/affected/reference | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * Fetch populates `document` rows respecting rate limits, conditional GET, and `source_state.cursor`. | * **Fetch**: windowed (cursor), conditional GET (ETag/Last‑Modified), retry/backoff, rate limiting. | ||||||
| * Parse validates schema (JSON Schema, XSD) and writes sanitized DTO payloads. | * **Parse**: schema validation (JSON Schema, XSD/CSAF), content type checks; write **DTO** with normalized casing. | ||||||
| * Map produces canonical advisory rows + provenance entries; must be idempotent. | * **Map**: build canonical records; all outputs carry **provenance** (doc digest, URI, anchors). | ||||||
| * Base helpers in `StellaOps.Feedser.Source.Common` provide HTTP clients, retry policies, and watermark utilities. |  | ||||||
|  | ### 4.2 Version range normalization | ||||||
|  |  | ||||||
|  | * **SemVer** ecosystems (npm, pypi, maven, nuget, golang): normalize to `introduced`/`fixed` semver ranges (use `~`, `^`, `<`, `>=` canonicalized to intervals). | ||||||
|  | * **RPM EVR**: `epoch:version-release` with `rpmvercmp` semantics; store raw EVR strings and also **computed order keys** for query. | ||||||
|  | * **DEB**: dpkg version comparison semantics mirrored; store computed keys. | ||||||
|  | * **APK**: Alpine version semantics; compute order keys. | ||||||
|  | * **Generic**: if provider uses text, retain raw; do **not** invent ranges. | ||||||
|  |  | ||||||
|  | ### 4.3 Severity & CVSS | ||||||
|  |  | ||||||
|  | * Normalize **CVSS v2/v3/v4** where available (vector, baseScore, severity). | ||||||
|  | * If multiple CVSS sources exist, track them all; **effective severity** defaults to **max** by policy (configurable). | ||||||
|  | * **ExploitKnown** toggled by KEV and equivalent sources; store **evidence** (source, date). | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 6) Merge & Normalization | ## 5) Merge engine | ||||||
|  |  | ||||||
| * Canonical model stored in `StellaOps.Feedser.Models` with serialization contracts used by storage/export layers. | ### 5.1 Keying & identity | ||||||
| * `StellaOps.Feedser.Normalization` handles NEVRA/EVR/PURL range parsing, CVSS normalization, localization. |  | ||||||
| * `StellaOps.Feedser.Merge` builds alias graphs keyed by CVE first, then falls back to vendor/regional IDs. | * Identity graph: **CVE** is primary node; vendor/distro IDs resolved via **Alias** edges (from connectors and Feedser’s alias tables). | ||||||
| * Precedence rules: PSIRT/OVAL overrides generic ranges; KEV only toggles exploitation; regional feeds enrich severity but don’t override vendor truth. | * `advisoryKey` is the canonical primary key (CVE if present, else vendor/distro key). | ||||||
| * Determinism enforced via canonical JSON hashing logged in `merge_event`. |  | ||||||
|  | ### 5.2 Merge algorithm (deterministic) | ||||||
|  |  | ||||||
|  | 1. **Gather** all rows for `advisoryKey` (across sources). | ||||||
|  | 2. **Select title/summary** by precedence source (vendor>distro>ecosystem>cert). | ||||||
|  | 3. **Union aliases** (dedupe by scheme+value). | ||||||
|  | 4. **Merge `Affected`** with rules: | ||||||
|  |  | ||||||
|  |    * Prefer **vendor** ranges for vendor products; prefer **distro** for **distro‑shipped** packages. | ||||||
|  |    * If both exist for same `productKey`, keep **both**; mark `sourceTag` and `precedence` so **Policy** can decide. | ||||||
|  |    * Never collapse range semantics across different families (e.g., rpm EVR vs semver). | ||||||
|  | 5. **CVSS/severity**: record all CVSS sets; compute **effectiveSeverity** = max (unless policy override). | ||||||
|  | 6. **References**: union with type precedence (advisory > patch > kb > exploit > blog); dedupe by URL; preserve `sourceTag`. | ||||||
|  | 7. Produce **canonical JSON**; compute **afterHash**; store **MergeEvent** with inputs and hashes. | ||||||
|  |  | ||||||
|  | > The merge is **pure** given inputs. Any change in inputs or precedence matrices changes the **hash** predictably. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Storage schema (MongoDB) | ||||||
|  |  | ||||||
|  | **Collections & indexes** | ||||||
|  |  | ||||||
|  | * `source` `{_id, type, baseUrl, enabled, notes}` | ||||||
|  | * `source_state` `{sourceName(unique), enabled, cursor, lastSuccess, backoffUntil, paceOverrides}` | ||||||
|  | * `document` `{_id, sourceName, uri, fetchedAt, sha256, contentType, status, metadata, gridFsId?, etag?, lastModified?}` | ||||||
|  |  | ||||||
|  |   * Index: `{sourceName:1, uri:1}` unique, `{fetchedAt:-1}` | ||||||
|  | * `dto` `{_id, sourceName, documentId, schemaVer, payload, validatedAt}` | ||||||
|  |  | ||||||
|  |   * Index: `{sourceName:1, documentId:1}` | ||||||
|  | * `advisory` `{_id, advisoryKey, title, summary, published, modified, severity, cvss, exploitKnown, sources[]}` | ||||||
|  |  | ||||||
|  |   * Index: `{advisoryKey:1}` unique, `{modified:-1}`, `{severity:1}`, text index (title, summary) | ||||||
|  | * `alias` `{advisoryId, scheme, value}` | ||||||
|  |  | ||||||
|  |   * Index: `{scheme:1,value:1}`, `{advisoryId:1}` | ||||||
|  | * `affected` `{advisoryId, productKey, rangeKind, introduced?, fixed?, arch?, distro?, ecosystem?}` | ||||||
|  |  | ||||||
|  |   * Index: `{productKey:1}`, `{advisoryId:1}`, `{productKey:1, rangeKind:1}` | ||||||
|  | * `reference` `{advisoryId, url, kind, sourceTag}` | ||||||
|  |  | ||||||
|  |   * Index: `{advisoryId:1}`, `{kind:1}` | ||||||
|  | * `merge_event` `{advisoryKey, beforeHash, afterHash, mergedAt, inputs[]}` | ||||||
|  |  | ||||||
|  |   * Index: `{advisoryKey:1, mergedAt:-1}` | ||||||
|  | * `export_state` `{_id(exportKind), baseExportId?, baseDigest?, lastFullDigest?, lastDeltaDigest?, cursor, files[]}` | ||||||
|  | * `locks` `{_id(jobKey), holder, acquiredAt, heartbeatAt, leaseMs, ttlAt}` (TTL cleans dead locks) | ||||||
|  | * `jobs` `{_id, type, args, state, startedAt, heartbeatAt, endedAt, error}` | ||||||
|  |  | ||||||
|  | **GridFS buckets**: `fs.documents` for raw payloads. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 7) Exporters | ## 7) Exporters | ||||||
|  |  | ||||||
| * JSON exporter mirrors `aquasecurity/vuln-list` layout with deterministic ordering and reproducible timestamps. | ### 7.1 Deterministic JSON (vuln‑list style) | ||||||
| * Trivy DB exporter shells out to `trivy-db build`, produces Bolt archives, and reuses unchanged blobs from the last full baseline when running in delta mode. The exporter annotates `metadata.json` with `mode`, `baseExportId`, `baseManifestDigest`, `resetBaseline`, and `delta.changedFiles[]`/`delta.removedPaths[]`, and honours `publishFull` / `publishDelta` (ORAS) plus `includeFull` / `includeDelta` (offline bundle) toggles. |  | ||||||
| * `StellaOps.Feedser.Storage.Mongo` provides cursors for delta exports based on `export_state.exportCursor` and the persisted per-file manifest (`export_state.files`). | * Folder structure mirroring `/<scheme>/<first-two>/<rest>/…` with one JSON per advisory; deterministic ordering, stable timestamps, normalized whitespace. | ||||||
| * Export jobs produce OCI tarballs (layer media type `application/vnd.aquasec.trivy.db.layer.v1.tar+gzip`) and optionally push via ORAS; `metadata.json` accompanies each layout so mirrors can decide between full refreshes and deltas. | * `manifest.json` lists all files with SHA‑256 and a top‑level **export digest**. | ||||||
|  |  | ||||||
|  | ### 7.2 Trivy DB exporter | ||||||
|  |  | ||||||
|  | * Builds Bolt DB archives compatible with Trivy; supports **full** and **delta** modes. | ||||||
|  | * In delta, unchanged blobs are reused from the base; metadata captures: | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   { | ||||||
|  |     "mode": "delta|full", | ||||||
|  |     "baseExportId": "...", | ||||||
|  |     "baseManifestDigest": "sha256:...", | ||||||
|  |     "changed": ["path1", "path2"], | ||||||
|  |     "removed": ["path3"] | ||||||
|  |   } | ||||||
|  |   ``` | ||||||
|  | * Optional ORAS push (OCI layout) for registries. | ||||||
|  | * Offline kit bundles include Trivy DB + JSON tree + export manifest. | ||||||
|  |  | ||||||
|  | ### 7.3 Hand‑off to Signer/Attestor (optional) | ||||||
|  |  | ||||||
|  | * On export completion, if `attest: true` is set in job args, Feedser **posts** the artifact metadata to **Signer**/**Attestor**; Feedser itself **does not** hold signing keys. | ||||||
|  | * Export record stores returned `{ uuid, index, url }` from **Rekor v2**. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 8) Observability | ## 8) REST APIs | ||||||
|  |  | ||||||
| * Serilog structured logging with enrichment fields (`source`, `uri`, `stage`, `durationMs`). | All under `/api/v1/feedser`. | ||||||
| * OpenTelemetry traces around fetch/parse/map/export; metrics for rate limit hits, schema failures, dedupe ratios, package size. Connector HTTP metrics are emitted via the shared `feedser.source.http.*` instruments tagged with `feedser.source=<connector>` so per-source dashboards slice on that label instead of bespoke metric names. |  | ||||||
| * Prometheus scraping endpoint served by WebService. | **Health & status** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | GET  /healthz | /readyz | ||||||
|  | GET  /status                              → sources, last runs, export cursors | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Sources & jobs** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | GET  /sources                              → list of configured sources | ||||||
|  | POST /sources/{name}/trigger               → { jobId } | ||||||
|  | POST /sources/{name}/pause | /resume       → toggle | ||||||
|  | GET  /jobs/{id}                            → job status | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Exports** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /exports/json   { full?:bool, force?:bool, attest?:bool } → { exportId, digest, rekor? } | ||||||
|  | POST /exports/trivy  { full?:bool, force?:bool, publish?:bool, attest?:bool } → { exportId, digest, rekor? } | ||||||
|  | GET  /exports/{id}   → export metadata (kind, digest, createdAt, rekor?) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Search (operator debugging)** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | GET  /advisories/{key} | ||||||
|  | GET  /advisories?scheme=CVE&value=CVE-2025-12345 | ||||||
|  | GET  /affected?productKey=pkg:rpm/openssl&limit=100 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **AuthN/Z:** Authority tokens (OpTok) with roles: `feedser.read`, `feedser.admin`, `feedser.export`. | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 9) Security Considerations | ## 9) Configuration (YAML) | ||||||
|  |  | ||||||
| * Offline-first: connectors only reach allowlisted hosts. | ```yaml | ||||||
| * BDU LLM fallback gated by config flag; logs audit trail with confidence score. | feedser: | ||||||
| * No secrets written to logs; secrets loaded via environment or mounted files. |   mongo: { uri: "mongodb://mongo/feedser" } | ||||||
| * Signing handled outside Feedser pipeline. |   s3: | ||||||
|  |     endpoint: "http://minio:9000" | ||||||
|  |     bucket: "stellaops-feedser" | ||||||
|  |   scheduler: | ||||||
|  |     windowSeconds: 30 | ||||||
|  |     maxParallelSources: 4 | ||||||
|  |   sources: | ||||||
|  |     - name: redhat | ||||||
|  |       kind: csaf | ||||||
|  |       baseUrl: https://access.redhat.com/security/data/csaf/v2/ | ||||||
|  |       signature: { type: pgp, keys: [ "…redhat PGP…" ] } | ||||||
|  |       enabled: true | ||||||
|  |       windowDays: 7 | ||||||
|  |     - name: suse | ||||||
|  |       kind: csaf | ||||||
|  |       baseUrl: https://ftp.suse.com/pub/projects/security/csaf/ | ||||||
|  |       signature: { type: pgp, keys: [ "…suse PGP…" ] } | ||||||
|  |     - name: ubuntu | ||||||
|  |       kind: usn-json | ||||||
|  |       baseUrl: https://ubuntu.com/security/notices.json | ||||||
|  |       signature: { type: none } | ||||||
|  |     - name: osv | ||||||
|  |       kind: osv | ||||||
|  |       baseUrl: https://api.osv.dev/v1/ | ||||||
|  |       signature: { type: none } | ||||||
|  |     - name: ghsa | ||||||
|  |       kind: ghsa | ||||||
|  |       baseUrl: https://api.github.com/graphql | ||||||
|  |       auth: { tokenRef: "env:GITHUB_TOKEN" } | ||||||
|  |   exporters: | ||||||
|  |     json: | ||||||
|  |       enabled: true | ||||||
|  |       output: s3://stellaops-feedser/json/ | ||||||
|  |     trivy: | ||||||
|  |       enabled: true | ||||||
|  |       mode: full | ||||||
|  |       output: s3://stellaops-feedser/trivy/ | ||||||
|  |       oras: | ||||||
|  |         enabled: false | ||||||
|  |         repo: ghcr.io/org/feedser | ||||||
|  |   precedence: | ||||||
|  |     vendorWinsOverDistro: true | ||||||
|  |     distroWinsOverOsv: true | ||||||
|  |   severity: | ||||||
|  |     policy: max    # or 'vendorPreferred' / 'distroPreferred' | ||||||
|  | ``` | ||||||
|  |  | ||||||
| --- | --- | ||||||
|  |  | ||||||
| ## 10) Deployment Notes | ## 10) Security & compliance | ||||||
|  |  | ||||||
|  | * **Outbound allowlist** per connector (domains, protocols); proxy support; TLS pinning where possible. | ||||||
|  | * **Signature verification** for raw docs (PGP/cosign/x509) with results stored in `document.metadata.sig`. Docs failing verification may still be ingested but flagged; **merge** can down‑weight or ignore them by config. | ||||||
|  | * **No secrets in logs**; auth material via `env:` or mounted files; HTTP redaction of `Authorization` headers. | ||||||
|  | * **Multi‑tenant**: per‑tenant DBs or prefixes; per‑tenant S3 prefixes; tenant‑scoped API tokens. | ||||||
|  | * **Determinism**: canonical JSON writer; export digests stable across runs given same inputs. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Performance targets & scale | ||||||
|  |  | ||||||
|  | * **Ingest**: ≥ 5k documents/min on 4 cores (CSAF/OpenVEX/JSON). | ||||||
|  | * **Normalize/map**: ≥ 50k `Affected` rows/min on 4 cores. | ||||||
|  | * **Merge**: ≤ 10 ms P95 per advisory at steady‑state updates. | ||||||
|  | * **Export**: 1M advisories JSON in ≤ 90 s (streamed, zstd), Trivy DB in ≤ 60 s on 8 cores. | ||||||
|  | * **Memory**: hard cap per job; chunked streaming writers; backpressure to avoid GC spikes. | ||||||
|  |  | ||||||
|  | **Scale pattern**: add Feedser replicas; Mongo scaling via indices and read/write concerns; GridFS only for oversized docs. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Observability | ||||||
|  |  | ||||||
|  | * **Metrics** | ||||||
|  |  | ||||||
|  |   * `feedser.fetch.docs_total{source}` | ||||||
|  |   * `feedser.fetch.bytes_total{source}` | ||||||
|  |   * `feedser.parse.failures_total{source}` | ||||||
|  |   * `feedser.map.affected_total{source}` | ||||||
|  |   * `feedser.merge.changed_total` | ||||||
|  |   * `feedser.export.bytes{kind}` | ||||||
|  |   * `feedser.export.duration_seconds{kind}` | ||||||
|  | * **Tracing** around fetch/parse/map/merge/export. | ||||||
|  | * **Logs**: structured with `source`, `uri`, `docDigest`, `advisoryKey`, `exportId`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Testing matrix | ||||||
|  |  | ||||||
|  | * **Connectors:** fixture suites for each provider/format (happy path; malformed; signature fail). | ||||||
|  | * **Version semantics:** EVR vs dpkg vs semver edge cases (epoch bumps, tilde versions, pre‑releases). | ||||||
|  | * **Merge:** conflicting sources (vendor vs distro vs OSV); verify precedence & dual retention. | ||||||
|  | * **Export determinism:** byte‑for‑byte stable outputs across runs; digest equality. | ||||||
|  | * **Performance:** soak tests with 1M advisories; cap memory; verify backpressure. | ||||||
|  | * **API:** pagination, filters, RBAC, error envelopes (RFC 7807). | ||||||
|  | * **Offline kit:** bundle build & import correctness. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Failure modes & recovery | ||||||
|  |  | ||||||
|  | * **Source outages:** scheduler backs off with exponential delay; `source_state.backoffUntil`; alerts on staleness. | ||||||
|  | * **Schema drifts:** parse stage marks DTO invalid; job fails with clear diagnostics; connector version flags track supported schema ranges. | ||||||
|  | * **Partial exports:** exporters write to temp prefix; **manifest commit** is atomic; only then move to final prefix and update `export_state`. | ||||||
|  | * **Resume:** all stages idempotent; `source_state.cursor` supports window resume. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Operator runbook (quick) | ||||||
|  |  | ||||||
|  | * **Trigger all sources:** `POST /api/v1/feedser/sources/*/trigger` | ||||||
|  | * **Force full export JSON:** `POST /api/v1/feedser/exports/json { "full": true, "force": true }` | ||||||
|  | * **Force Trivy DB delta publish:** `POST /api/v1/feedser/exports/trivy { "full": false, "publish": true }` | ||||||
|  | * **Inspect advisory:** `GET /api/v1/feedser/advisories?scheme=CVE&value=CVE-2025-12345` | ||||||
|  | * **Pause noisy source:** `POST /api/v1/feedser/sources/osv/pause` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Rollout plan | ||||||
|  |  | ||||||
|  | 1. **MVP**: Red Hat (CSAF), SUSE (CSAF), Ubuntu (USN JSON), OSV; JSON export. | ||||||
|  | 2. **Add**: GHSA GraphQL, Debian (DSA HTML/JSON), Alpine secdb; Trivy DB export. | ||||||
|  | 3. **Attestation hand‑off**: integrate with **Signer/Attestor** (optional). | ||||||
|  | 4. **Scale & diagnostics**: provider dashboards, staleness alerts, export cache reuse. | ||||||
|  | 5. **Offline kit**: end‑to‑end verified bundles for air‑gap. | ||||||
|  |  | ||||||
| * Default storage MongoDB; for air-gapped, bundle Mongo image + seeded data backup. |  | ||||||
| * Horizontal scale achieved via multiple web service instances sharing Mongo locks. |  | ||||||
| * Provide `feedser.yaml` template describing sources, rate limits, and export settings. |  | ||||||
|   | |||||||
							
								
								
									
										413
									
								
								docs/ARCHITECTURE_SCANNER.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										413
									
								
								docs/ARCHITECTURE_SCANNER.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,413 @@ | |||||||
|  | # component_architecture_scanner.md — **Stella Ops Scanner** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for the **Scanner** subsystem: WebService, Workers, analyzers, SBOM assembly (inventory & usage), per‑layer caching, three‑way diffs, artifact catalog (MinIO+Mongo), attestation hand‑off, and scale/security posture. This document is the contract between the scanning plane and everything else (Policy, Vexer, Feedser, UI, CLI). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Produce **deterministic**, **explainable** SBOMs and diffs for container images and filesystems, quickly and repeatedly, without guessing. Emit two views: **Inventory** (everything present) and **Usage** (entrypoint closure + actually linked libs). Attach attestations through **Signer→Attestor→Rekor v2**. | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * Scanner **does not** produce PASS/FAIL. The backend (Policy + Vexer + Feedser) decides presentation and verdicts. | ||||||
|  | * Scanner **does not** keep third‑party SBOM warehouses. It may **bind** to existing attestations for exact hashes. | ||||||
|  | * Core analyzers are **deterministic** (no fuzzy identity). Optional heuristic plug‑ins (e.g., patch‑presence) run under explicit flags and never contaminate the core SBOM. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Solution & project layout | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | src/ | ||||||
|  |  ├─ StellaOps.Scanner.WebService/            # REST control plane, catalog, diff, exports | ||||||
|  |  ├─ StellaOps.Scanner.Worker/                # queue consumer; executes analyzers | ||||||
|  |  ├─ StellaOps.Scanner.Models/                # DTOs, evidence, graph nodes, CDX/SPDX adapters | ||||||
|  |  ├─ StellaOps.Scanner.Storage/               # Mongo repositories; MinIO object client; ILM/GC | ||||||
|  |  ├─ StellaOps.Scanner.Queue/                 # queue abstraction (Redis/NATS/RabbitMQ) | ||||||
|  |  ├─ StellaOps.Scanner.Cache/                 # layer cache; file CAS; bloom/bitmap indexes | ||||||
|  |  ├─ StellaOps.Scanner.EntryTrace/            # ENTRYPOINT/CMD → terminal program resolver (shell AST) | ||||||
|  |  ├─ StellaOps.Scanner.Analyzers.OS.[Apk|Dpkg|Rpm]/ | ||||||
|  |  ├─ StellaOps.Scanner.Analyzers.Lang.[Java|Node|Python|Go|DotNet|Rust]/ | ||||||
|  |  ├─ StellaOps.Scanner.Analyzers.Native.[ELF|PE|MachO]/   # PE/Mach-O planned (M2) | ||||||
|  |  ├─ StellaOps.Scanner.Emit.CDX/              # CycloneDX (JSON + Protobuf) | ||||||
|  |  ├─ StellaOps.Scanner.Emit.SPDX/             # SPDX 3.0.1 JSON | ||||||
|  |  ├─ StellaOps.Scanner.Diff/                  # image→layer→component three‑way diff | ||||||
|  |  ├─ StellaOps.Scanner.Index/                 # BOM‑Index sidecar (purls + roaring bitmaps) | ||||||
|  |  ├─ StellaOps.Scanner.Tests.*                # unit/integration/e2e fixtures | ||||||
|  |  └─ tools/ | ||||||
|  |      ├─ StellaOps.Scanner.Sbomer.BuildXPlugin/   # BuildKit generator (image referrer SBOMs) | ||||||
|  |      └─ StellaOps.Scanner.Sbomer.DockerImage/    # CLI‑driven scanner container | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Runtime form‑factor:** two deployables | ||||||
|  |  | ||||||
|  | * **Scanner.WebService** (stateless REST) | ||||||
|  | * **Scanner.Worker** (N replicas; queue‑driven) | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) External dependencies | ||||||
|  |  | ||||||
|  | * **OCI registry** with **Referrers API** (discover attached SBOMs/signatures). | ||||||
|  | * **MinIO** (S3‑compatible) for SBOM artifacts; **Object Lock** for immutable classes; **ILM** for TTL. | ||||||
|  | * **MongoDB** for catalog, job state, diffs, ILM rules. | ||||||
|  | * **Queue** (Redis Streams/NATS/RabbitMQ). | ||||||
|  | * **Authority** (on‑prem OIDC) for **OpToks** (DPoP/mTLS). | ||||||
|  | * **Signer** + **Attestor** (+ **Fulcio/KMS** + **Rekor v2**) for DSSE + transparency. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Contracts & data model | ||||||
|  |  | ||||||
|  | ### 3.1 Evidence‑first component model | ||||||
|  |  | ||||||
|  | **Nodes** | ||||||
|  |  | ||||||
|  | * `Image`, `Layer`, `File` | ||||||
|  | * `Component` (`purl?`, `name`, `version?`, `type`, `id` — may be `bin:{sha256}`) | ||||||
|  | * `Executable` (ELF/PE/Mach‑O), `Library` (native or managed), `EntryScript` (shell/launcher) | ||||||
|  |  | ||||||
|  | **Edges** (all carry **Evidence**) | ||||||
|  |  | ||||||
|  | * `contains(Image|Layer → File)` | ||||||
|  | * `installs(PackageDB → Component)` (OS database row) | ||||||
|  | * `declares(InstalledMetadata → Component)` (dist‑info, pom.properties, deps.json…) | ||||||
|  | * `links_to(Executable → Library)` (ELF `DT_NEEDED`, PE imports) | ||||||
|  | * `calls(EntryScript → Program)` (file:line from shell AST) | ||||||
|  | * `attests(Rekor → Component|Image)` (SBOM/predicate binding) | ||||||
|  | * `bound_from_attestation(Component_attested → Component_observed)` (hash equality proof) | ||||||
|  |  | ||||||
|  | **Evidence** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | { source: enum, locator: (path|offset|line), sha256?, method: enum, timestamp } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | No confidences. Either a fact is proven with listed mechanisms, or it is not claimed. | ||||||
|  |  | ||||||
|  | ### 3.2 Catalog schema (Mongo) | ||||||
|  |  | ||||||
|  | * `artifacts` | ||||||
|  |  | ||||||
|  |   ``` | ||||||
|  |   { _id, type: layer-bom|image-bom|diff|index, | ||||||
|  |     format: cdx-json|cdx-pb|spdx-json, | ||||||
|  |     bytesSha256, size, rekor: { uuid,index,url }?, | ||||||
|  |     ttlClass, immutable, refCount, createdAt } | ||||||
|  |   ``` | ||||||
|  | * `images { imageDigest, repo, tag?, arch, createdAt, lastSeen }` | ||||||
|  | * `layers { layerDigest, mediaType, size, createdAt, lastSeen }` | ||||||
|  | * `links  { fromType, fromDigest, artifactId }`               // image/layer -> artifact | ||||||
|  | * `jobs   { _id, kind, args, state, startedAt, heartbeatAt, endedAt, error }` | ||||||
|  | * `lifecycleRules { ruleId, scope, ttlDays, retainIfReferenced, immutable }` | ||||||
|  |  | ||||||
|  | ### 3.3 Object store layout (MinIO) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | layers/<sha256>/sbom.cdx.json.zst | ||||||
|  | layers/<sha256>/sbom.spdx.json.zst | ||||||
|  | images/<imgDigest>/inventory.cdx.pb            # CycloneDX Protobuf | ||||||
|  | images/<imgDigest>/usage.cdx.pb | ||||||
|  | indexes/<imgDigest>/bom-index.bin              # purls + roaring bitmaps | ||||||
|  | diffs/<old>_<new>/diff.json.zst | ||||||
|  | attest/<artifactSha256>.dsse.json              # DSSE bundle (cert chain + Rekor proof) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) REST API (Scanner.WebService) | ||||||
|  |  | ||||||
|  | All under `/api/v1/scanner`. Auth: **OpTok** (DPoP/mTLS); RBAC scopes. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /scans                        { imageRef|digest, force?:bool } → { scanId } | ||||||
|  | GET  /scans/{id}                   → { status, imageDigest, artifacts[], rekor? } | ||||||
|  | GET  /sboms/{imageDigest}          ?format=cdx-json|cdx-pb|spdx-json&view=inventory|usage → bytes | ||||||
|  | GET  /diff?old=<digest>&new=<digest>&view=inventory|usage → diff.json | ||||||
|  | POST /exports                      { imageDigest, format, view, attest?:bool } → { artifactId, rekor? } | ||||||
|  | POST /reports                      { imageDigest, policyRevision? } → { reportId, rekor? }   # delegates to backend policy+vex | ||||||
|  | GET  /catalog/artifacts/{id}       → { meta } | ||||||
|  | GET  /healthz | /readyz | /metrics | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Execution flow (Worker) | ||||||
|  |  | ||||||
|  | ### 5.1 Acquire & verify | ||||||
|  |  | ||||||
|  | 1. **Resolve image** (prefer `repo@sha256:…`). | ||||||
|  | 2. **(Optional) verify image signature** per policy (cosign). | ||||||
|  | 3. **Pull blobs**, compute layer digests; record metadata. | ||||||
|  |  | ||||||
|  | ### 5.2 Layer union FS | ||||||
|  |  | ||||||
|  | * Apply whiteouts; materialize final filesystem; map **file → first introducing layer**. | ||||||
|  | * Windows layers (MSI/SxS/GAC) planned in **M2**. | ||||||
|  |  | ||||||
|  | ### 5.3 Evidence harvest (parallel analyzers; deterministic only) | ||||||
|  |  | ||||||
|  | **A) OS packages** | ||||||
|  |  | ||||||
|  | * **apk**: `/lib/apk/db/installed` | ||||||
|  | * **dpkg**: `/var/lib/dpkg/status`, `/var/lib/dpkg/info/*.list` | ||||||
|  | * **rpm**: `/var/lib/rpm/Packages` (via librpm or parser) | ||||||
|  | * Record `name`, `version` (epoch/revision), `arch`, source package where present, and **declared file lists**. | ||||||
|  |  | ||||||
|  | **B) Language ecosystems (installed state only)** | ||||||
|  |  | ||||||
|  | * **Java**: `META-INF/maven/*/pom.properties`, MANIFEST → `pkg:maven/...` | ||||||
|  | * **Node**: `node_modules/**/package.json` → `pkg:npm/...` | ||||||
|  | * **Python**: `*.dist-info/{METADATA,RECORD}` → `pkg:pypi/...` | ||||||
|  | * **Go**: Go **buildinfo** in binaries → `pkg:golang/...` | ||||||
|  | * **.NET**: `*.deps.json` + assembly metadata → `pkg:nuget/...` | ||||||
|  | * **Rust**: crates only when **explicitly present** (embedded metadata or cargo/registry traces); otherwise binaries reported as `bin:{sha256}`. | ||||||
|  |  | ||||||
|  | > **Rule:** We only report components proven **on disk** with authoritative metadata. Lockfiles are evidence only. | ||||||
|  |  | ||||||
|  | **C) Native link graph** | ||||||
|  |  | ||||||
|  | * **ELF**: parse `PT_INTERP`, `DT_NEEDED`, RPATH/RUNPATH, **GNU symbol versions**; map **SONAMEs** to file paths; link executables → libs. | ||||||
|  | * **PE/Mach‑O** (planned M2): import table, delay‑imports; version resources; code signatures. | ||||||
|  | * Map libs back to **OS packages** if possible (via file lists); else emit `bin:{sha256}` components. | ||||||
|  |  | ||||||
|  | **D) EntryTrace (ENTRYPOINT/CMD → terminal program)** | ||||||
|  |  | ||||||
|  | * Read image config; parse shell (POSIX/Bash subset) with AST: `source`/`.` includes; `case/if`; `exec`/`command`; `run‑parts`. | ||||||
|  | * Resolve commands via **PATH** within the **built rootfs**; follow language launchers (Java/Node/Python) to identify the terminal program (ELF/JAR/venv script). | ||||||
|  | * Record **file:line** and choices for each hop; output chain graph. | ||||||
|  | * Unresolvable dynamic constructs are recorded as **unknown** edges with reasons (e.g., `$FOO` unresolved). | ||||||
|  |  | ||||||
|  | **E) Attestation & SBOM bind (optional)** | ||||||
|  |  | ||||||
|  | * For each **file hash** or **binary hash**, query local cache of **Rekor v2** indices; if an SBOM attestation is found for **exact hash**, bind it to the component (origin=`attested`). | ||||||
|  | * For the **image** digest, likewise bind SBOM attestations (build‑time referrers). | ||||||
|  |  | ||||||
|  | ### 5.4 Component normalization (exact only) | ||||||
|  |  | ||||||
|  | * Create `Component` nodes only with deterministic identities: purl, or **`bin:{sha256}`** for unlabeled binaries. | ||||||
|  | * Record **origin** (OS DB, installed metadata, linker, attestation). | ||||||
|  |  | ||||||
|  | ### 5.5 SBOM assembly & emit | ||||||
|  |  | ||||||
|  | * **Per‑layer SBOM fragments**: components introduced by the layer (+ relationships). | ||||||
|  | * **Image SBOMs**: merge fragments; refer back to them via **CycloneDX BOM‑Link** (or SPDX ExternalRef). | ||||||
|  | * Emit both **Inventory** & **Usage** views. | ||||||
|  | * Serialize **CycloneDX JSON** and **CycloneDX Protobuf**; optionally **SPDX 3.0.1 JSON**. | ||||||
|  | * Build **BOM‑Index** sidecar: purl table + roaring bitmap; flag `usedByEntrypoint` components for fast backend joins. | ||||||
|  |  | ||||||
|  | ### 5.6 DSSE attestation (via Signer/Attestor) | ||||||
|  |  | ||||||
|  | * WebService constructs **predicate** with `image_digest`, `stellaops_version`, `license_id`, `policy_digest?` (when emitting **final reports**), timestamps. | ||||||
|  | * Calls **Signer** (requires **OpTok + PoE**); Signer verifies **entitlement + scanner image integrity** and returns **DSSE bundle**. | ||||||
|  | * **Attestor** logs to **Rekor v2**; returns `{uuid,index,proof}` → stored in `artifacts.rekor`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Three‑way diff (image → layer → component) | ||||||
|  |  | ||||||
|  | ### 6.1 Keys & classification | ||||||
|  |  | ||||||
|  | * Component key: **purl** when present; else `bin:{sha256}`. | ||||||
|  | * Diff classes: `added`, `removed`, `version_changed` (`upgraded|downgraded`), `metadata_changed` (e.g., origin from attestation vs observed). | ||||||
|  | * Layer attribution: for each change, resolve the **introducing/removing layer**. | ||||||
|  |  | ||||||
|  | ### 6.2 Algorithm (outline) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | A = components(imageOld, key) | ||||||
|  | B = components(imageNew, key) | ||||||
|  |  | ||||||
|  | added   = B \ A | ||||||
|  | removed = A \ B | ||||||
|  | changed = { k in A∩B : version(A[k]) != version(B[k]) || origin changed } | ||||||
|  |  | ||||||
|  | for each item in added/removed/changed: | ||||||
|  |    layer = attribute_to_layer(item, imageOld|imageNew) | ||||||
|  |    usageFlag = usedByEntrypoint(item, imageNew) | ||||||
|  | emit diff.json (grouped by layer with badges) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Diffs are stored as artifacts and feed **UI** and **CLI**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Build‑time SBOMs (fast CI path) | ||||||
|  |  | ||||||
|  | **Scanner.Sbomer.BuildXPlugin** can act as a BuildKit **generator**: | ||||||
|  |  | ||||||
|  | * During `docker buildx build --attest=type=sbom,generator=stellaops/sbom-indexer`, run analyzers on the build context/output; attach SBOMs as OCI **referrers** to the built image. | ||||||
|  | * Optionally request **Signer/Attestor** to produce **Stella Ops‑verified** attestation immediately; else, Scanner.WebService can verify and re‑attest post‑push. | ||||||
|  | * Scanner.WebService trusts build‑time SBOMs per policy, enabling **no‑rescan** for unchanged bases. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | scanner: | ||||||
|  |   queue: | ||||||
|  |     kind: redis | ||||||
|  |     url: "redis://queue:6379/0" | ||||||
|  |   mongo: | ||||||
|  |     uri: "mongodb://mongo/scanner" | ||||||
|  |   s3: | ||||||
|  |     endpoint: "http://minio:9000" | ||||||
|  |     bucket: "stellaops" | ||||||
|  |     objectLock: "governance"   # or 'compliance' | ||||||
|  |   analyzers: | ||||||
|  |     os: { apk: true, dpkg: true, rpm: true } | ||||||
|  |     lang: { java: true, node: true, python: true, go: true, dotnet: true, rust: true } | ||||||
|  |     native: { elf: true, pe: false, macho: false }    # PE/Mach-O in M2 | ||||||
|  |     entryTrace: { enabled: true, shellMaxDepth: 64, followRunParts: true } | ||||||
|  |   emit: | ||||||
|  |     cdx: { json: true, protobuf: true } | ||||||
|  |     spdx: { json: true } | ||||||
|  |     compress: "zstd" | ||||||
|  |   rekor: | ||||||
|  |     url: "https://rekor-v2.internal" | ||||||
|  |   signer: | ||||||
|  |     url: "https://signer.internal" | ||||||
|  |   limits: | ||||||
|  |     maxParallel: 8 | ||||||
|  |     perRegistryConcurrency: 2 | ||||||
|  |   policyHints: | ||||||
|  |     verifyImageSignature: false | ||||||
|  |     trustBuildTimeSboms: true | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Scale & performance | ||||||
|  |  | ||||||
|  | * **Parallelism**: per‑analyzer concurrency; bounded directory walkers; file CAS dedupe by sha256. | ||||||
|  | * **Distributed locks** per **layer digest** to prevent duplicate work across Workers. | ||||||
|  | * **Registry throttles**: per‑host concurrency budgets; exponential backoff on 429/5xx. | ||||||
|  | * **Targets**: | ||||||
|  |  | ||||||
|  |   * **Build‑time**: P95 ≤ 3–5 s on warmed bases (CI generator). | ||||||
|  |   * **Post‑build delta**: P95 ≤ 10 s for 200 MB images with cache hit. | ||||||
|  |   * **Emit**: CycloneDX Protobuf ≤ 150 ms for 5k components; JSON ≤ 500 ms. | ||||||
|  |   * **Diff**: ≤ 200 ms for 5k vs 5k components. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Security posture | ||||||
|  |  | ||||||
|  | * **AuthN**: Authority‑issued short OpToks (DPoP/mTLS). | ||||||
|  | * **AuthZ**: scopes (`scanner.scan`, `scanner.export`, `scanner.catalog.read`). | ||||||
|  | * **mTLS** to **Signer**/**Attestor**; only **Signer** can sign. | ||||||
|  | * **No network fetches** during analysis (except registry pulls and optional Rekor index reads). | ||||||
|  | * **Sandboxing**: non‑root containers; read‑only FS; seccomp profiles; disable execution of scanned content. | ||||||
|  | * **Release integrity**: all first‑party images are **cosign‑signed**; Workers/WebService self‑verify at startup. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Observability & audit | ||||||
|  |  | ||||||
|  | * **Metrics**: | ||||||
|  |  | ||||||
|  |   * `scanner.jobs_inflight`, `scanner.scan_latency_seconds` | ||||||
|  |   * `scanner.layer_cache_hits_total`, `scanner.file_cas_hits_total` | ||||||
|  |   * `scanner.artifact_bytes_total{format}` | ||||||
|  |   * `scanner.attestation_latency_seconds`, `scanner.rekor_failures_total` | ||||||
|  | * **Tracing**: spans for acquire→union→analyzers→compose→emit→sign→log. | ||||||
|  | * **Audit logs**: DSSE requests log `license_id`, `image_digest`, `artifactSha256`, `policy_digest?`, Rekor UUID on success. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Testing matrix | ||||||
|  |  | ||||||
|  | * **Determinism:** given same image + analyzers → byte‑identical **CDX Protobuf**; JSON normalized. | ||||||
|  | * **OS packages:** ground‑truth images per distro; compare to package DB. | ||||||
|  | * **Lang ecosystems:** sample images per ecosystem (Java/Node/Python/Go/.NET/Rust) with installed metadata; negative tests w/ lockfile‑only. | ||||||
|  | * **Native & EntryTrace:** ELF graph correctness; shell AST cases (includes, run‑parts, exec, case/if). | ||||||
|  | * **Diff:** layer attribution against synthetic two‑image sequences. | ||||||
|  | * **Performance:** cold vs warm cache; large `node_modules` and `site‑packages`. | ||||||
|  | * **Security:** ensure no code execution from image; fuzz parser inputs; path traversal resistance on layer extract. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Failure modes & degradations | ||||||
|  |  | ||||||
|  | * **Missing OS DB** (files exist, DB removed): record **files**; do **not** fabricate package components; emit `bin:{sha256}` where unavoidable; flag in evidence. | ||||||
|  | * **Unreadable metadata** (corrupt dist‑info): record file evidence; skip component creation; annotate. | ||||||
|  | * **Dynamic shell constructs**: mark unresolved edges with reasons (env var unknown) and continue; **Usage** view may be partial. | ||||||
|  | * **Registry rate limits**: honor backoff; queue job retries with jitter. | ||||||
|  | * **Signer refusal** (license/plan/version): scan completes; artifact produced; **no attestation**; WebService marks result as **unverified**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Optional plug‑ins (off by default) | ||||||
|  |  | ||||||
|  | * **Patch‑presence detector** (signature‑based backport checks). Reads curated function‑level signatures from advisories; inspects binaries for patched code snippets to lower false‑positives for backported fixes. Runs as a sidecar analyzer that **annotates** components; never overrides core identities. | ||||||
|  | * **Runtime probes** (with Zastava): when allowed, compare **/proc/<pid>/maps** (DSOs actually loaded) with static **Usage** view for precision. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) DevOps & operations | ||||||
|  |  | ||||||
|  | * **HA**: WebService horizontal scale; Workers autoscale by queue depth & CPU; distributed locks on layers. | ||||||
|  | * **Retention**: ILM rules per artifact class (`short`, `default`, `compliance`); **Object Lock** for compliance artifacts (reports, signed SBOMs). | ||||||
|  | * **Upgrades**: bump **cache schema** when analyzer outputs change; WebService triggers refresh of dependent artifacts. | ||||||
|  | * **Backups**: Mongo (daily dumps); MinIO (versioned buckets, replication); Rekor v2 DB snapshots. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) CLI & UI touch points | ||||||
|  |  | ||||||
|  | * **CLI**: `stellaops scan <ref>`, `stellaops diff --old --new`, `stellaops export`, `stellaops verify attestation <bundle|url>`. | ||||||
|  | * **UI**: Scan detail shows **Inventory/Usage** toggles, **Diff by Layer**, **Attestation badge** (verified/unverified), Rekor link, and **EntryTrace** chain with file:line breadcrumbs. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Roadmap (Scanner) | ||||||
|  |  | ||||||
|  | * **M2**: Windows containers (MSI/SxS/GAC analyzers), PE/Mach‑O native analyzer, deeper Rust metadata. | ||||||
|  | * **M2**: Buildx generator GA (certified external registries), cross‑registry trust policies. | ||||||
|  | * **M3**: Patch‑presence plug‑in GA (opt‑in), cross‑image corpus clustering (evidence‑only; not identity). | ||||||
|  | * **M3**: Advanced EntryTrace (POSIX shell features breadth, busybox detection). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ### Appendix A — EntryTrace resolution (pseudo) | ||||||
|  |  | ||||||
|  | ```csharp | ||||||
|  | ResolveEntrypoint(ImageConfig cfg, RootFs fs): | ||||||
|  |   cmd = Normalize(cfg.ENTRYPOINT, cfg.CMD) | ||||||
|  |   stack = [ Script(cmd, path=FindOnPath(cmd[0], fs)) ] | ||||||
|  |   visited = set() | ||||||
|  |  | ||||||
|  |   while stack not empty and depth < MAX: | ||||||
|  |     cur = stack.pop() | ||||||
|  |     if cur in visited: continue | ||||||
|  |     visited.add(cur) | ||||||
|  |  | ||||||
|  |     if IsShellScript(cur.path): | ||||||
|  |        ast = ParseShell(cur.path) | ||||||
|  |        foreach directive in ast: | ||||||
|  |          if directive is Source include: | ||||||
|  |             p = ResolveInclude(include.path, cur.env, fs) | ||||||
|  |             stack.push(Script(p)) | ||||||
|  |          if directive is Exec call: | ||||||
|  |             p = ResolveExec(call.argv[0], cur.env, fs) | ||||||
|  |             stack.push(Program(p, argv=call.argv)) | ||||||
|  |          if directive is Interpreter (python -m / node / java -jar): | ||||||
|  |             term = ResolveInterpreterTarget(call, fs) | ||||||
|  |             stack.push(Program(term)) | ||||||
|  |     else: | ||||||
|  |        return Terminal(cur.path) | ||||||
|  |  | ||||||
|  |   return Unknown(reason) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Appendix B — BOM‑Index sidecar | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | struct Header { magic, version, imageDigest, createdAt } | ||||||
|  | vector<string> purls | ||||||
|  | map<purlIndex, roaring_bitmap> components | ||||||
|  | optional map<purlIndex, roaring_bitmap> usedByEntrypoint | ||||||
|  | ``` | ||||||
|  |  | ||||||
							
								
								
									
										418
									
								
								docs/ARCHITECTURE_SIGNER.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										418
									
								
								docs/ARCHITECTURE_SIGNER.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,418 @@ | |||||||
|  | # component_architecture_signer.md — **Stella Ops Signer** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for the **Signer**: the *only* service allowed to produce **Stella Ops‑verified** signatures over SBOMs and reports. It enforces **entitlement** (PoE), **release integrity** (scanner provenance), **sender‑constrained auth** (DPoP/mTLS), and emits **in‑toto/DSSE** bundles suitable for **Rekor v2** logging by the Attestor. Includes APIs, data flow, storage, quotas, security, and test matrices. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Convert authenticated signing requests from trusted Stella Ops services into **verifiable** DSSE bundles while enforcing **license policy** and **supply‑chain integrity**. | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * **Signer does not push to Rekor** — it returns DSSE to the caller; **Attestor** logs to **Rekor v2**. | ||||||
|  | * **Signer does not compute PASS/FAIL** — it signs SBOMs/reports produced by Scanner/WebService after backend evaluation. | ||||||
|  | * **Signer is stateless for hot path** — long‑term storage is limited to audit events; all secrets/keys live in KMS/HSM or are ephemeral (keyless). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Responsibilities (contract) | ||||||
|  |  | ||||||
|  | 1. **Authenticate** caller with **OpTok** (Authority OIDC, DPoP or mTLS‑bound). | ||||||
|  | 2. **Authorize** scopes (`signer.sign`) + audience (`aud=signer`) + tenant/installation. | ||||||
|  | 3. **Validate entitlement** via **PoE** (Proof‑of‑Entitlement) against Cloud Licensing `/license/introspect`. | ||||||
|  | 4. **Verify release integrity** of the **scanner** image digest presented in the request: must be **cosign‑signed** by Stella Ops release key, discoverable via **OCI Referrers API**. | ||||||
|  | 5. **Enforce plan & quotas** (concurrency/QPS/artifact size/rate caps). | ||||||
|  | 6. **Mint signing identity**: | ||||||
|  |  | ||||||
|  |    * **Keyless** (default): get a short‑lived X.509 cert from **Fulcio** using the Signer’s OIDC identity and sign the DSSE. | ||||||
|  |    * **Keyful** (optional): sign with an HSM/KMS key. | ||||||
|  | 7. **Return DSSE bundle** (subject digests + predicate + cert chain or KMS key id). | ||||||
|  | 8. **Audit** every decision; expose metrics. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) External dependencies | ||||||
|  |  | ||||||
|  | * **Authority** (on‑prem OIDC): validates OpToks (JWKS/introspection) and DPoP/mTLS. | ||||||
|  | * **Licensing Service (cloud)**: `/license/introspect` to verify PoE (active, claims, expiry, revocation). | ||||||
|  | * **Fulcio** (Sigstore) *or* **KMS/HSM**: to obtain certs or perform signatures. | ||||||
|  | * **OCI Registry (Referrers API)**: to verify **scanner** image release signature. | ||||||
|  | * **Attestor**: downstream service that writes DSSE bundles to **Rekor v2**. | ||||||
|  | * **Config/state stores**: Redis (caches, rate buckets), Mongo/Postgres (audit log). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) API surface (mTLS; DPoP supported) | ||||||
|  |  | ||||||
|  | Base path: `/api/v1/signer`. **All endpoints require**: | ||||||
|  |  | ||||||
|  | * Access token (JWT) from **Authority** with `aud=signer`, `scope=signer.sign`. | ||||||
|  | * **Sender constraint**: DPoP proof per request or mTLS client cert. | ||||||
|  | * **PoE** presented as either: | ||||||
|  |  | ||||||
|  |   * **Client TLS cert** (if PoE is mTLS‑style) chained to Licensing CA, *or* | ||||||
|  |   * **PoE JWT** (DPoP/mTLS‑bound) in `X-PoE` header or request body. | ||||||
|  |  | ||||||
|  | ### 3.1 `POST /sign/dsse` | ||||||
|  |  | ||||||
|  | Request (JSON): | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "subject": [ | ||||||
|  |     { "name": "s3://stellaops/images/sha256:.../inventory.cdx.pb", | ||||||
|  |       "digest": { "sha256": "..." } } | ||||||
|  |   ], | ||||||
|  |   "predicateType": "https://stella-ops.org/attestations/sbom/1", | ||||||
|  |   "predicate": { | ||||||
|  |     "image_digest": "sha256:...", | ||||||
|  |     "stellaops_version": "2.3.1 (2027.04)", | ||||||
|  |     "license_id": "LIC-9F2A...", | ||||||
|  |     "customer_id": "CUST-ACME", | ||||||
|  |     "plan": "pro", | ||||||
|  |     "policy_digest": "sha256:...",        // optional for final reports | ||||||
|  |     "views": ["inventory", "usage"], | ||||||
|  |     "created": "2025-10-17T12:34:56Z" | ||||||
|  |   }, | ||||||
|  |   "scannerImageDigest": "sha256:sc-web-or-worker-digest", | ||||||
|  |   "poe": { | ||||||
|  |     "format": "jwt",                      // or "mtls" | ||||||
|  |     "value": "eyJhbGciOi..."              // PoE JWT when not using mTLS PoE | ||||||
|  |   }, | ||||||
|  |   "options": { | ||||||
|  |     "signingMode": "keyless",             // "keyless" | "kms" | ||||||
|  |     "expirySeconds": 600,                 // cert lifetime hint (keyless) | ||||||
|  |     "returnBundle": "dsse+cert"           // dsse (default) | dsse+cert | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Response 200: | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "bundle": { | ||||||
|  |     "dsse": { "payloadType": "application/vnd.in-toto+json", "payload": "<base64>", "signatures": [ ... ] }, | ||||||
|  |     "certificateChain": [ "-----BEGIN CERTIFICATE-----...", "... root ..." ], | ||||||
|  |     "mode": "keyless", | ||||||
|  |     "signingIdentity": { "issuer": "https://fulcio.internal", "san": "urn:stellaops:signer", "certExpiry": "2025-10-17T12:44:56Z" } | ||||||
|  |   }, | ||||||
|  |   "policy": { "plan": "pro", "maxArtifactBytes": 104857600, "qpsRemaining": 97 }, | ||||||
|  |   "auditId": "a7c9e3f2-1b7a-4e87-8c3a-90d7d2c3ad12" | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Errors (RFC 7807): | ||||||
|  |  | ||||||
|  | * `401 invalid_token` (JWT/DPoP/mTLS failure) | ||||||
|  | * `403 entitlement_denied` (PoE invalid/revoked/expired; release year mismatch) | ||||||
|  | * `403 release_untrusted` (scanner image not Stella‑signed) | ||||||
|  | * `429 plan_throttled` (license plan caps) | ||||||
|  | * `413 artifact_too_large` (size cap) | ||||||
|  | * `400 invalid_request` (schema/predicate/type invalid) | ||||||
|  | * `500 signing_unavailable` (Fulcio/KMS outage) | ||||||
|  |  | ||||||
|  | ### 3.2 `GET /verify/referrers?imageDigest=<sha256>` | ||||||
|  |  | ||||||
|  | Checks whether the **image** at digest is signed by **Stella Ops release key**. | ||||||
|  |  | ||||||
|  | Response: | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { "trusted": true, "signatures": [ { "type": "cosign", "digest": "sha256:...", "signedBy": "StellaOps Release 2027 Q2" } ] } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > **Note:** This endpoint is also used internally by Signer before issuing signatures. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Validation pipeline (hot path) | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant Client as Scanner.WebService | ||||||
|  |   participant Auth as Authority (OIDC) | ||||||
|  |   participant Sign as Signer | ||||||
|  |   participant Lic as Licensing Service (cloud) | ||||||
|  |   participant Reg as OCI Registry (Referrers) | ||||||
|  |   participant Ful as Fulcio/KMS | ||||||
|  |  | ||||||
|  |   Client->>Sign: POST /sign/dsse (OpTok + DPoP/mTLS, PoE, request) | ||||||
|  |   Note over Sign: 1) Validate OpTok, audience, scope, DPoP/mTLS binding | ||||||
|  |   Sign->>Lic: /license/introspect(PoE) | ||||||
|  |   Lic-->>Sign: { active, claims: {license_id, plan, valid_release_year, max_version}, exp } | ||||||
|  |   Note over Sign: 2) Enforce plan/version window and revocation | ||||||
|  |  | ||||||
|  |   Sign->>Reg: Verify scannerImageDigest signed (Referrers + cosign) | ||||||
|  |   Reg-->>Sign: OK with signer identity | ||||||
|  |   Note over Sign: 3) Enforce release integrity | ||||||
|  |  | ||||||
|  |   Note over Sign: 4) Enforce quotas (QPS/concurrency/size) | ||||||
|  |   Sign->>Ful: Mint cert (keyless) or sign via KMS | ||||||
|  |   Ful-->>Sign: Cert or signature | ||||||
|  |  | ||||||
|  |   Sign-->>Client: DSSE bundle (+cert chain), policy counters, auditId | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **DPoP nonce dance (when enabled for high‑value ops):** | ||||||
|  |  | ||||||
|  | * If DPoP proof lacks a valid nonce, Signer replies `401` with `WWW-Authenticate: DPoP error="use_dpop_nonce", dpop_nonce="<nonce>"`. | ||||||
|  | * Client retries with new proof including the nonce; Signer validates nonce and `jti` uniqueness (Redis TTL cache). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Entitlement enforcement (PoE) | ||||||
|  |  | ||||||
|  | * **Accepted forms**: | ||||||
|  |  | ||||||
|  |   * **mTLS PoE**: client presents a **PoE client cert** at TLS handshake; Signer validates chain to **Licensing CA** (CA bundle configured) and calls `/license/introspect` with cert thumbprint + serial. | ||||||
|  |   * **JWT PoE**: `X-PoE` bearer token (DPoP/mTLS‑bound) is validated (sig + `cnf`) locally (Licensing JWKS) and then **introspected** for status and claims. | ||||||
|  |  | ||||||
|  | * **Claims required**: | ||||||
|  |  | ||||||
|  |   * `license_id`, `plan` (free|pro|enterprise|gov), `valid_release_year`, `max_version`, `exp`. | ||||||
|  |   * Optional: `tenant_id`, `customer_id`, `entitlements[]`. | ||||||
|  |  | ||||||
|  | * **Enforcements**: | ||||||
|  |  | ||||||
|  |   * Reject if **revoked**, **expired**, **plan mismatch** or **release outside window** (`stellaops_version` in predicate exceeds `max_version` or release date beyond `valid_release_year`). | ||||||
|  |   * Apply plan **throttles** (QPS/concurrency/artifact bytes) via token‑bucket in Redis keyed by `license_id`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Release integrity (scanner provenance) | ||||||
|  |  | ||||||
|  | * **Input**: `scannerImageDigest` representing the actual Scanner component that produced the artifact. | ||||||
|  |  | ||||||
|  | * **Check**: | ||||||
|  |  | ||||||
|  |   1. Use **OCI Referrers API** to enumerate signatures of that digest. | ||||||
|  |   2. Verify **cosign** signatures against the configured **Stella Ops Release** keyring (keyless Fulcio roots *or* keyful public keys). | ||||||
|  |   3. Optionally require Rekor inclusion for those signatures. | ||||||
|  |  | ||||||
|  | * **Policy**: | ||||||
|  |  | ||||||
|  |   * If not signed by an authorized **Stella Ops Release** identity → **deny**. | ||||||
|  |   * If signed but **release year** > PoE `valid_release_year` → **deny**. | ||||||
|  |  | ||||||
|  | * **Cache**: LRU of digest → verification result (TTL 10–30 min) to avoid registry thrash. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Signing modes | ||||||
|  |  | ||||||
|  | ### 7.1 Keyless (default; Sigstore Fulcio) | ||||||
|  |  | ||||||
|  | * Signer authenticates to **Fulcio** using its on‑prem OIDC identity (client credentials) and requests a **short‑lived cert** (5–10 min). | ||||||
|  | * Generates **ephemeral keypair**, gets cert for the public key, signs DSSE with the **private key**. | ||||||
|  | * DSSE **bundle** includes **certificate chain**; verifiers validate to Fulcio root. | ||||||
|  |  | ||||||
|  | ### 7.2 Keyful (optional; KMS/HSM) | ||||||
|  |  | ||||||
|  | * Signer uses a configured **KMS** key (AWS KMS, GCP KMS, Azure Key Vault, Vault Transit, or HSM). | ||||||
|  | * DSSE bundle includes **key metadata** (kid, cert chain if x509). | ||||||
|  | * Recommended for FIPS/sovereign environments. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Predicates & schema | ||||||
|  |  | ||||||
|  | Supported **predicate types** (extensible): | ||||||
|  |  | ||||||
|  | * `https://stella-ops.org/attestations/sbom/1` (SBOM emissions) | ||||||
|  | * `https://stella-ops.org/attestations/report/1` (final PASS/FAIL reports) | ||||||
|  | * `https://stella-ops.org/attestations/vex-export/1` (Vexer exports; optional) | ||||||
|  |  | ||||||
|  | **Validation**: | ||||||
|  |  | ||||||
|  | * JSON‑Schema per predicate type; **canonical property order**. | ||||||
|  | * `subject[*].digest` must include `sha256`. | ||||||
|  | * `predicate.stellaops_version` must parse and match policy windows. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Quotas & throttling | ||||||
|  |  | ||||||
|  | Per `license_id` (from PoE): | ||||||
|  |  | ||||||
|  | * **QPS** (token bucket), **concurrency** (semaphore), **artifact bytes** (sliding window). | ||||||
|  | * On exceed → `429 plan_throttled` with `Retry-After`. | ||||||
|  | * Free/community plan may also receive **randomized delay** to disincentivize farmed signing. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Storage & caches | ||||||
|  |  | ||||||
|  | * **Redis**: | ||||||
|  |  | ||||||
|  |   * DPoP nonce & `jti` replay cache (TTL ≤ 10 min). | ||||||
|  |   * PoE introspection cache (short TTL, e.g., 60–120 s). | ||||||
|  |   * Release‑verify cache (`scannerImageDigest` → { trusted, ts }). | ||||||
|  |  | ||||||
|  | * **Audit store** (Mongo or Postgres): `signer.audit_events` | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | { _id, ts, tenantId, installationId, licenseId, customerId, | ||||||
|  |   plan, actor{sub,cnf}, request{predicateType, subjectSha256[], imageDigest}, | ||||||
|  |   poe{type, thumbprint|jwtKid, exp, introspectSnapshot}, | ||||||
|  |   release{digest, signerId, policy}, | ||||||
|  |   mode: "keyless"|"kms", | ||||||
|  |   result: "success"|"deny:<reason>"|"error:<reason>", | ||||||
|  |   bundleSha256? } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | * **Config**: Stella Ops release signing keyring, Fulcio roots, Licensing CA bundle. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Security & privacy | ||||||
|  |  | ||||||
|  | * **mTLS** on all Signer endpoints. | ||||||
|  | * **No bearer fallbacks** — DPoP/mTLS enforced for `aud=signer`. | ||||||
|  | * **PoE** is never persisted beyond audit snapshots (minimized fields). | ||||||
|  | * **Secrets**: no long‑lived private keys on disk (keyless) or handled via KMS APIs. | ||||||
|  | * **Input hardening**: schema‑validate predicates; cap payload sizes; zstd/gzip decompression bombs guarded. | ||||||
|  | * **Logging**: redact PoE JWTs, access tokens, DPoP proofs; log only hashes and identifiers. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Metrics & observability | ||||||
|  |  | ||||||
|  | * `signer.requests_total{result}` | ||||||
|  | * `signer.latency_seconds{stage=auth|introspect|release_verify|sign}` | ||||||
|  | * `signer.poe_failures_total{reason}` | ||||||
|  | * `signer.release_verify_failures_total{reason}` | ||||||
|  | * `signer.plan_throttle_total{license_id}` | ||||||
|  | * `signer.bundle_bytes_total` | ||||||
|  | * `signer.keyless_certs_issued_total` / `signer.kms_sign_total` | ||||||
|  | * OTEL traces across stages; correlation id (`auditId`) returned to client. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | signer: | ||||||
|  |   listen: "https://0.0.0.0:8443" | ||||||
|  |   authority: | ||||||
|  |     issuer: "https://authority.internal" | ||||||
|  |     jwksUrl: "https://authority.internal/jwks" | ||||||
|  |     require: "dpop"         # "dpop" | "mtls" | ||||||
|  |   poe: | ||||||
|  |     mode: "both"            # "jwt" | "mtls" | "both" | ||||||
|  |     licensing: | ||||||
|  |       introspectUrl: "https://www.stella-ops.org/api/v1/license/introspect" | ||||||
|  |       caBundle: "/etc/ssl/licensing-ca.pem" | ||||||
|  |       cacheTtlSeconds: 90 | ||||||
|  |   release: | ||||||
|  |     referrers: | ||||||
|  |       allowRekorVerified: true | ||||||
|  |       keyrings: | ||||||
|  |         - type: "cosign-keyless" | ||||||
|  |           fulcioRoots: ["/etc/fulcio/root.pem"] | ||||||
|  |           identities: | ||||||
|  |             - san: "mailto:release@stella-ops.org" | ||||||
|  |             - san: "https://sigstore.dev/oidc/stellaops" | ||||||
|  |   signing: | ||||||
|  |     mode: "keyless"         # "keyless" | "kms" | ||||||
|  |     fulcio: | ||||||
|  |       issuer: "https://fulcio.internal" | ||||||
|  |       oidcClientId: "signer" | ||||||
|  |       oidcClientSecretRef: "env:FULCIO_CLIENT_SECRET" | ||||||
|  |       certTtlSeconds: 600 | ||||||
|  |     kms: | ||||||
|  |       provider: "aws-kms" | ||||||
|  |       keyId: "arn:aws:kms:...:key/..." | ||||||
|  |   quotas: | ||||||
|  |     default: | ||||||
|  |       qps: 100 | ||||||
|  |       concurrency: 20 | ||||||
|  |       maxArtifactBytes: 104857600 | ||||||
|  |     free: | ||||||
|  |       qps: 5 | ||||||
|  |       concurrency: 1 | ||||||
|  |       maxArtifactBytes: 1048576 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Testing matrix | ||||||
|  |  | ||||||
|  | * **Auth & DPoP**: bad `aud`, wrong `jkt`, replayed `jti`, missing nonce, mTLS mismatch. | ||||||
|  | * **PoE**: expired, revoked, plan mismatch, release year gate, max_version gate. | ||||||
|  | * **Release verify**: unsigned digest, wrong signer, Rekor‑absent (when required), referrers unreachable. | ||||||
|  | * **Signing**: Fulcio outage; KMS timeouts; bundle correctness (verifier harness). | ||||||
|  | * **Quotas**: burst above QPS, artifact over size, concurrency overflow. | ||||||
|  | * **Schema**: invalid predicate types/required fields. | ||||||
|  | * **Determinism**: same request → identical DSSE (aside from cert validity period). | ||||||
|  | * **Perf**: P95 end‑to‑end under 120 ms with caches warm (excluding network to Fulcio). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Failure modes & responses | ||||||
|  |  | ||||||
|  | | Failure                 | HTTP | Problem type          | Notes                                        | | ||||||
|  | | ----------------------- | ---- | --------------------- | -------------------------------------------- | | ||||||
|  | | Invalid OpTok / DPoP    | 401  | `invalid_token`       | `WWW-Authenticate` with DPoP nonce if needed | | ||||||
|  | | PoE invalid/revoked     | 403  | `entitlement_denied`  | Include `license_id` (hashed) and reason     | | ||||||
|  | | Scanner image untrusted | 403  | `release_untrusted`   | Include digest and required identity         | | ||||||
|  | | Plan throttle           | 429  | `plan_throttled`      | Include limits and `Retry-After`             | | ||||||
|  | | Artifact too large      | 413  | `artifact_too_large`  | Include cap                                  | | ||||||
|  | | Fulcio/KMS down         | 503  | `signing_unavailable` | Retry‑After with jitter                      | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Deployment & HA | ||||||
|  |  | ||||||
|  | * Run ≥ 2 replicas; front with L7 LB; **sticky** not required. | ||||||
|  | * Redis for replay/quota caches (HA). | ||||||
|  | * Audit sink (Mongo/Postgres) in primary region; asynchronous write with local fallback buffer. | ||||||
|  | * Fulcio/KMS clients configured with retries/backoff; circuit breakers. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Implementation notes | ||||||
|  |  | ||||||
|  | * **.NET 10** minimal API + Kestrel mTLS; custom DPoP middleware; JWT/JWKS cache. | ||||||
|  | * **Cosign verification** via sigstore libraries; Referrers queries over registry API with retries. | ||||||
|  | * **DSSE** via in‑toto libs; canonical JSON writer for predicates. | ||||||
|  | * **Backpressure** paths: refuse at auth/quota stages before any expensive network calls. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 18) Examples (wire) | ||||||
|  |  | ||||||
|  | **Request (free plan; expect throttle if burst):** | ||||||
|  |  | ||||||
|  | ```http | ||||||
|  | POST /api/v1/signer/sign/dsse HTTP/1.1 | ||||||
|  | Authorization: DPoP <JWT> | ||||||
|  | DPoP: <proof> | ||||||
|  | Content-Type: application/json | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Error (release untrusted):** | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "type": "https://stella-ops.org/problems/release_untrusted", | ||||||
|  |   "title": "Scanner image not signed by StellaOps", | ||||||
|  |   "status": 403, | ||||||
|  |   "detail": "sha256:abcd... not in trusted keyring", | ||||||
|  |   "instance": "urn:audit:a7c9e3f2-..." | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 19) Roadmap | ||||||
|  |  | ||||||
|  | * **Key Transparency**: optional publication of Signer’s *own* certs to a KT log. | ||||||
|  | * **Attested Build**: SLSA‑style provenance for Signer container itself, checked at startup. | ||||||
|  | * **FIPS mode**: enforce `ES256` + KMS/HSM only; disallow Ed25519. | ||||||
|  | * **Dual attestation**: optional immediate push to **Attestor** (sync mode) with timeout budget, returning Rekor UUID inline. | ||||||
|  |  | ||||||
|  | 
 | ||||||
							
								
								
									
										342
									
								
								docs/ARCHITECTURE_UI.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								docs/ARCHITECTURE_UI.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | |||||||
|  | # component_architecture_web_ui.md — **Stella Ops Web UI** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for the **Angular SPA** that operators and developers use to drive Stella Ops. This document defines UX surfaces, module boundaries, data flows, auth, RBAC, real‑time updates, performance targets, i18n/a11y, security headers, testing and deployment. The UI is a *consumer* of backend APIs (Scanner, Policy, Vexer, Feedser, Attestor, Authority) and never performs scanning, merging, or signing on its own. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & non‑goals | ||||||
|  |  | ||||||
|  | **Mission.** Provide a **fast, explainable** console for: | ||||||
|  |  | ||||||
|  | * Scans (status, SBOMs, diffs, EntryTrace, attestation). | ||||||
|  | * Policy management (rules, exemptions, VEX consumption view). | ||||||
|  | * Vulnerability intel (Feedser status), VEX consensus exploration (Vexer). | ||||||
|  | * Runtime posture (Zastava observer + admission). | ||||||
|  | * Admin operations (tenants, tokens, quotas, licensing posture). | ||||||
|  |  | ||||||
|  | **Non‑goals.** No client‑side crypto signing; no Docker/CRI access; no direct registry access beyond fetching static assets or OCI referrer summaries exposed by backend. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Technology baseline | ||||||
|  |  | ||||||
|  | * **Framework**: Angular 17+ (Stand‑alone APIs / Signals), TypeScript 5. | ||||||
|  | * **Styling**: Tailwind CSS + headless component patterns; CSS variables for theming. | ||||||
|  | * **Charts**: Lightweight SVG (uPlot or Apache ECharts via on‑demand import). | ||||||
|  | * **State**: Angular **Signals** + `@ngrx/signals` store for cross‑page slices. | ||||||
|  | * **Transport**: `fetch` + RxJS interop; **SSE** (EventSource) for progress streams. | ||||||
|  | * **Build**: Angular CLI + Vite builder. | ||||||
|  | * **Testing**: Jest + Testing Library, Playwright for e2e. | ||||||
|  | * **Packaging**: Containerized NGINX (immutable assets, ETag + content hashing). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) High‑level module map | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | /app | ||||||
|  |  ├─ core/                 # bootstrap, config, auth, http, error boundary, i18n | ||||||
|  |  ├─ shared/               # UI kit (tables, code-viewers, badges), pipes | ||||||
|  |  ├─ dashboard/            # live tiles, fleet KPIs, feed/vex age, queue depth | ||||||
|  |  ├─ scans/                # scan list, detail, SBOM viewer, diff-by-layer, EntryTrace | ||||||
|  |  ├─ runtime/              # Zastava posture, drift events, admission decisions | ||||||
|  |  ├─ policy/               # rules editor (YAML/Rego), exemptions, previews | ||||||
|  |  ├─ vex/                  # VEX explorer (claims, consensus, conflicts) | ||||||
|  |  ├─ feedser/              # source health, export cursors, rebuild/export triggers | ||||||
|  |  ├─ attest/               # attestation proofs, verification bundles, Rekor links | ||||||
|  |  ├─ admin/                # tenants, roles, clients, quotas, licensing posture | ||||||
|  |  └─ plugins/              # route plug-ins (lazy remote modules, governed) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Each feature folder builds as a **standalone route** (lazy loaded). All HTTP shapes live in `core/api/` clients with shared DTOs. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Navigation & key views | ||||||
|  |  | ||||||
|  | ### 3.1 Dashboard | ||||||
|  |  | ||||||
|  | * **Tiles**: “New criticals (24h)”, “VEX suppressions applied”, “Attested SBOMs (7d)”, “Feed age per provider”, “Scanner queue depth”, “Admission events”. | ||||||
|  | * **Trends**: sparkline for vulns/day, pass/fail rates, attestation throughput. | ||||||
|  |  | ||||||
|  | ### 3.2 Scans | ||||||
|  |  | ||||||
|  | * **Scan list** with status, image digest, repo, time, artifacts, attestation badge. | ||||||
|  | * **Scan detail**: | ||||||
|  |  | ||||||
|  |   * **SBOM viewer**: Inventory/Usage toggle; component table (virtualized), filters by package type, severity, source. | ||||||
|  |   * **Diff by layer**: A→B change grid (added/removed/upgraded), grouped by introducing/removing layer; tooltips show provenance and links to layer SBOM fragment. | ||||||
|  |   * **EntryTrace**: shell chain with file:line breadcrumbs; jump‑to source viewer (read‑only, hexdump fallback). | ||||||
|  |   * **Attestation**: Rekor UUID, index, inclusion proof; **Verify** button calls Attestor `/verify`. | ||||||
|  |   * **Export**: download buttons (CycloneDX JSON, Protobuf, SPDX JSON); size shown; SHA‑256 inline. | ||||||
|  |  | ||||||
|  | ### 3.3 Runtime (Zastava) | ||||||
|  |  | ||||||
|  | * **Observer timeline**: container start/stop, drift, policy violations; faceted by namespace/owner. | ||||||
|  | * **Live process view**: top N processes, loaded libs summary vs Usage SBOM. | ||||||
|  | * **Admission decisions**: per‑namespace rules, allow/deny events, cache TTL, reasons. | ||||||
|  |  | ||||||
|  | ### 3.4 Policy | ||||||
|  |  | ||||||
|  | * **Policy bundles**: active vs staged; diff viewer with change summary. | ||||||
|  | * **Editors**: | ||||||
|  |  | ||||||
|  |   * YAML rules (ignore lists, thresholds, vendor precedence overrides). | ||||||
|  |   * Rego blocks (advanced gates) with **WASM** preview evaluator (client‑side sandbox) for “preview” against sample SBOMs. | ||||||
|  | * **VEX inclusion controls**: weight sliders (visualization only), provider allow/deny toggles. | ||||||
|  | * **Preview**: select SBOM (or image digest) → show verdict under staged policy. | ||||||
|  |  | ||||||
|  | ### 3.5 Vexer | ||||||
|  |  | ||||||
|  | * **Claims explorer**: search by vulnId/productKey/provider; show raw claim (status, justification, evidence). | ||||||
|  | * **Consensus view**: rollup per (vuln, product) with accepted/rejected sources, weights, timestamps. | ||||||
|  | * **Conflicts**: grid of top conflicts; filters for justification gates failed. | ||||||
|  |  | ||||||
|  | ### 3.6 Feedser | ||||||
|  |  | ||||||
|  | * **Sources** table: staleness, last run, errors. | ||||||
|  | * **Advisory search**: by CVE/alias; show normalized affected ranges. | ||||||
|  | * **Exports**: trigger full/delta JSON/Trivy DB; show manifest digests and Rekor link if attested. | ||||||
|  |  | ||||||
|  | ### 3.7 Attest | ||||||
|  |  | ||||||
|  | * **Proofs list**: last 7 days Rekor entries; filter by kind (sbom/report/vex). | ||||||
|  | * **Verification**: paste UUID or upload bundle → verify; result with explanations (chain, Merkle path). | ||||||
|  |  | ||||||
|  | ### 3.8 Admin | ||||||
|  |  | ||||||
|  | * **Tenants/Installations**: view/edit, isolation hints. | ||||||
|  | * **Clients & roles**: Authority clients, role→scope mapping, rotation hints. | ||||||
|  | * **Quotas**: per license plan, counters, throttle events. | ||||||
|  | * **Licensing posture**: last PoE introspection snapshot (redacted), release window. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Auth, sessions & RBAC | ||||||
|  |  | ||||||
|  | ### 4.1 OIDC flow | ||||||
|  |  | ||||||
|  | * **Authorization Code + PKCE** to **Authority**. | ||||||
|  | * **ID Token** for UX identity; **Access Token** (OpTok) for APIs (2–5 min TTL). | ||||||
|  | * **DPoP (browser)**: generate ephemeral **WebCrypto** keypair; store public JWK in memory, private key in **IndexedDB** (non‑exportable if platform allows). Access token includes `cnf.jkt`; each API call adds `DPoP` proof; handle nonce challenges automatically. | ||||||
|  | * **Refresh**: optional DPoP‑bound refresh tokens with rotation; otherwise silent renew. | ||||||
|  |  | ||||||
|  | ### 4.2 RBAC | ||||||
|  |  | ||||||
|  | * Roles (`ui.read`, `ui.admin`, plus service roles) are embedded in ID token or fetched via `/me` endpoint. | ||||||
|  | * **Route guards** enforce access; **feature flags** hide admin pages for non‑admins. | ||||||
|  |  | ||||||
|  | ### 4.3 Session storage | ||||||
|  |  | ||||||
|  | * Access tokens & refresh metadata in memory; persist **only** minimal session (subject, expiries) in `sessionStorage`. Never persist raw JWTs to `localStorage`. Use **SameSite=Lax** cookies for anti‑CSRF if cookies are required (prefer pure bearer headers + DPoP). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) HTTP layer & API clients | ||||||
|  |  | ||||||
|  | * **`core/http/api-client.ts`** centralizes: | ||||||
|  |  | ||||||
|  |   * Base URLs (Scanner, Vexer, Feedser, Attestor). | ||||||
|  |   * **Retry** policies on idempotent GETs (backoff + jitter). | ||||||
|  |   * **Problem+JSON** parser → uniform error toasts with correlation ID. | ||||||
|  |   * **SSE** helper (EventSource) with auto‑reconnect & backpressure. | ||||||
|  |   * **DPoP** injector & nonce handling. | ||||||
|  |  | ||||||
|  | * Typed API clients (DTOs in `core/api/models.ts`): | ||||||
|  |  | ||||||
|  |   * `ScannerApi`, `PolicyApi`, `VexerApi`, `FeedserApi`, `AttestorApi`, `AuthorityApi`. | ||||||
|  |  | ||||||
|  | **DTO examples (abbrev):** | ||||||
|  |  | ||||||
|  | ```ts | ||||||
|  | export type ImageDigest = `sha256:${string}`; | ||||||
|  | export interface ScanSummary { | ||||||
|  |   imageDigest: ImageDigest; createdAt: string; | ||||||
|  |   artifacts: { view: 'inventory'|'usage'; format: 'cdx-json'|'cdx-pb'|'spdx-json'; sha256: string; size: number }[]; | ||||||
|  |   status: 'queued'|'running'|'completed'|'error'; | ||||||
|  |   rekor?: { uuid: string; index?: number; url?: string }; | ||||||
|  | } | ||||||
|  | export interface DiffEntry { | ||||||
|  |   key: string; change: 'added'|'removed'|'upgraded'|'downgraded'; | ||||||
|  |   fromVersion?: string; toVersion?: string; layer: string; usedByEntrypoint?: boolean; | ||||||
|  | } | ||||||
|  | export interface VexConsensus { | ||||||
|  |   vulnId: string; productKey: string; rollupStatus: 'affected'|'not_affected'|'fixed'|'under_investigation'; | ||||||
|  |   sources: { providerId: string; status: string; weight: number; accepted: boolean; reason: string }[]; | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) State, caching & real‑time | ||||||
|  |  | ||||||
|  | * **Per‑page stores** (Signals) for list filters, pagination, and selected entities. | ||||||
|  | * **Normalized caches** keyed by `(imageDigest, view, format)`; artifacts are downloaded via pre‑signed URLs from Scanner and streamed; SHA‑256 verified client‑side before exposing “verified” badge. | ||||||
|  | * **SSE channels**: | ||||||
|  |  | ||||||
|  |   * `/scans/{id}/events` → progress log. | ||||||
|  |   * `/runtime/events/stream` (optional) → live drift/admission feed (rate‑limited). | ||||||
|  | * **Cache invalidation** on job completion or explicit “refresh”. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) SBOM viewing & diff UX | ||||||
|  |  | ||||||
|  | * **Huge tables** rendered with **virtual scrolling** (CDK Virtual Scroll); sort/filter performed client‑side for ≤ 20k rows; beyond that, server‑side queries via BOM‑Index endpoints. | ||||||
|  | * **Component row** shows purl, version, origin (OS pkg / metadata / linker / attested), licenses, and **used** badge (Usage view). | ||||||
|  | * **Diff**: compact heatmap per layer; clicking opens a right‑pane with evidence: introducing paths, file hashes, VEX notes (from Vexer consensus) and links to advisories (Feedser). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Policy editor & VEX integration | ||||||
|  |  | ||||||
|  | * **YAML editor**: Monaco‑based with schema hints; previews show which rules matched. | ||||||
|  | * **Rego editor**: Monaco with WASM‑OPA sandbox for read‑only evaluation on sample SBOMs. | ||||||
|  | * **VEX toggles**: per‑provider enable/disable; “explain” drawer shows why a claim was accepted/rejected (justification gate, signature state, weight). | ||||||
|  | * **Staged → Active** promotion is a two‑step flow with confirmation and automatic policy digest computation. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Accessibility, i18n & theming | ||||||
|  |  | ||||||
|  | * **A11y**: WCAG 2.2 AA; keyboard navigation, focus management, ARIA roles; color‑contrast tokens verified by unit tests. | ||||||
|  | * **I18n**: Angular i18n + runtime translation loader (`/locales/{lang}.json`); dates/numbers localized via `Intl`. | ||||||
|  | * **Languages**: English default; Bulgarian, German, Japanese as initial additions. | ||||||
|  | * **Theming**: dark/light via CSS variables; persisted in `prefers-color-scheme` aware store. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Performance budgets | ||||||
|  |  | ||||||
|  | * **TTI** ≤ 1.5 s on 4G/slow CPU (first visit), ≤ 0.6 s repeat (HTTP/2, cached). | ||||||
|  | * **JS** initial < 300 KB gz (lazy routes). | ||||||
|  | * **SBOM list**: render 10k rows in < 70 ms with virtualization; filter in < 150 ms. | ||||||
|  | * **Diff view**: compute client‑side grouping for 5k changes in < 120 ms. | ||||||
|  |  | ||||||
|  | Techniques: route‑level code splitting; `ChangeDetectionStrategy.OnPush`; Signals; server compression (zstd/gzip), immutable assets with long max‑age (cache busting via hashes). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Security headers & CSP | ||||||
|  |  | ||||||
|  | * **CSP**: `default-src 'self'; connect-src 'self' https://*.internal; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none';` | ||||||
|  | * **HSTS** enabled at gateway. | ||||||
|  | * **Referrer Policy**: `no-referrer`. | ||||||
|  | * **X‑Frame‑Options**: `DENY`. | ||||||
|  | * **COOP/COEP** to enable faster cross‑origin isolation (for WASM OPA). | ||||||
|  | * **Subresource Integrity (SRI)** for third‑party fonts (minimize third‑party). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Error handling & UX hygiene | ||||||
|  |  | ||||||
|  | * **Global error boundary** surfaces Problem+JSON `title/detail/instance` with correlation ID. | ||||||
|  | * **Retry toast** for 429 (quota throttles) with backoff timer. | ||||||
|  | * **Auth expiry**: pre‑emptive refresh; unobtrusive banner when < 60 s TTL; re‑login modal if refresh fails. | ||||||
|  | * **Network down**: offline banner with queued actions (idempotent resubmits). | ||||||
|  | * **File verify**: show SHA‑256 mismatch warnings if artifact altered in transit. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Observability | ||||||
|  |  | ||||||
|  | * **Front‑end telemetry** (OpenTelemetry Web): route timings, API latency by service, error counts; sampled to 1–5% and shipped to backend OTLP endpoint. | ||||||
|  | * **User actions** logged anonymously (no PII): “policy promote”, “scan export”, “attest verify”. | ||||||
|  | * **Metrics dash** in admin shows SLOs and recent front‑end errors. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Testing strategy | ||||||
|  |  | ||||||
|  | * **Unit**: pure component logic via Jest + Testing Library (no TestBed when possible). | ||||||
|  | * **Component harness** for table, code viewer, diff heatmap. | ||||||
|  | * **Contract tests**: OpenAPI schemas pulled at build time; DTOs validated; breaking changes fail CI. | ||||||
|  | * **e2e**: Playwright scenarios (login, scan detail, diff, policy edit, admit deny). | ||||||
|  | * **A11y**: axe-core CI checks; color‑contrast lints. | ||||||
|  | * **i18n**: key coverage tests (no missing translations in supported locales). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Deployment & ops | ||||||
|  |  | ||||||
|  | * **Container**: `stellaops/web-ui:<ver>-<rev>`; NGINX with `gzip_static` + brotli; immutable assets under `/static/<hash>/…`. | ||||||
|  | * **Config**: `/config.json` served by gateway (injected at runtime): API base URLs, authority issuer, telemetry sampling. | ||||||
|  | * **Version banner**: footer shows UI & backend versions; warns on major mismatches. | ||||||
|  | * **CDN** (optional): cache static bundle; APIs stay behind internal gateway. | ||||||
|  | * **Feature flags**: environment gates (staged policies, eBPF runtime) readable from config. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Plugin system (route plug‑ins) | ||||||
|  |  | ||||||
|  | * **Manifest**: Backend provides a signed plug‑in manifest with remote module URLs and **cosign signature** per JS bundle. | ||||||
|  | * **Loader**: dynamic import with **SRI** and signature verification (WebCrypto). | ||||||
|  | * **Sandbox**: plug‑ins are routed modules receiving a limited **UI SDK** (navigation, theme, API gateway). No direct token access; API calls proxied through the UI SDK which enforces RBAC. | ||||||
|  | * **Examples**: custom reports, vendor dashboards, regulated TLS config UIs. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Wire sequences (representative) | ||||||
|  |  | ||||||
|  | **A) View scan progress** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant UI | ||||||
|  |   participant Auth as Authority | ||||||
|  |   participant SW as Scanner.WebService | ||||||
|  |  | ||||||
|  |   UI->>Auth: /authorize (PKCE) | ||||||
|  |   Auth-->>UI: code → token (DPoP-bound) | ||||||
|  |   UI->>SW: GET /scans/{id} (Authorization+DPoP) | ||||||
|  |   SW-->>UI: { status: running } | ||||||
|  |   UI->>SW: (SSE) GET /scans/{id}/events | ||||||
|  |   SW-->>UI: progress events … | ||||||
|  |   SW-->>UI: terminal event { status: completed, artifacts[] } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **B) Verify attestation** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant UI | ||||||
|  |   participant AT as Attestor | ||||||
|  |   UI->>AT: POST /rekor/verify { uuid } | ||||||
|  |   AT-->>UI: { ok:true, index, logURL } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **C) Promote policy & preview** | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant UI | ||||||
|  |   participant BE as Scanner.WebService (Policy endpoint) | ||||||
|  |   UI->>BE: POST /policy/stage { yaml, rego } | ||||||
|  |   BE-->>UI: { policyRevision, diagnostics } | ||||||
|  |   UI->>BE: POST /policy/preview { imageDigest, policyRevision } | ||||||
|  |   BE-->>UI: { verdict: pass|fail, reasons[] } | ||||||
|  |   UI->>BE: POST /policy/promote { policyRevision } | ||||||
|  |   BE-->>UI: { ok:true } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 18) Security hard lines | ||||||
|  |  | ||||||
|  | * Never store JWTs in `localStorage`. | ||||||
|  | * Enforce DPoP for API calls; if DPoP unsupported for a service, require **SameSite=Lax** cookies with CSRF token header. | ||||||
|  | * Block mixed‑content; only HTTPS origins allowed. | ||||||
|  | * Validate and render only **escaped** user content; code viewer uses safe highlighter. | ||||||
|  | * Downloaded artifacts are treated as **opaque binaries**; no HTML rendering. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 19) Roadmap | ||||||
|  |  | ||||||
|  | * **PWA** offline shell (read‑only) for dashboards and cached scan details. | ||||||
|  | * **SBOM graph** visualization (force‑directed) for small components sets. | ||||||
|  | * **Runtime session replay** (privacy‑safe) to debug operator workflows (opt‑in). | ||||||
|  | * **Assistive wizards** for policy creation with guided templates. | ||||||
| @@ -1,85 +1,463 @@ | |||||||
| # StellaOps Vexer Architecture | # component_architecture_vexer.md — **Stella Ops Vexer** (2025Q4) | ||||||
|  |  | ||||||
| Vexer is StellaOps' vulnerability-exploitability (VEX) platform. It ingests VEX statements from multiple providers, normalizes them into canonical claims, projects trust-weighted consensus, and delivers deterministic export artifacts with signed attestations. This document summarizes the target architecture and how the current implementation maps to those goals. | > **Scope.** This document specifies the **Vexer** service: its purpose, trust model, data structures, APIs, plug‑in contracts, storage schema, normalization/consensus algorithms, performance budgets, testing matrix, and how it integrates with Scanner, Policy, Feedser, and the attestation chain. It is implementation‑ready. | ||||||
|  |  | ||||||
| ## 1. Solution topology | --- | ||||||
|  |  | ||||||
| | Module | Purpose | Key contracts | | ## 0) Mission & role in the platform | ||||||
| | --- | --- | --- | |  | ||||||
| | `StellaOps.Vexer.Core` | Domain models (`VexClaim`, `VexConsensus`, `VexExportManifest`), deterministic JSON helpers, shared abstractions (connectors, exporters, attestations). | `IVexConnector`, `IVexExporter`, `IVexAttestationClient`, `VexCanonicalJsonSerializer` | |  | ||||||
| | `StellaOps.Vexer.Policy` | Loads operator policy (weights, overrides, justification gates) and exposes snapshots for consensus. | `IVexPolicyProvider`, `IVexPolicyEvaluator`, `VexPolicyOptions` | |  | ||||||
| | `StellaOps.Vexer.Storage.Mongo` | Persistence layer for providers, raw docs, claims, consensus, exports, cache. | `IVexRawStore`, `IVexExportStore`, Mongo class maps | |  | ||||||
| | `StellaOps.Vexer.Export` | Orchestrates export pipeline (query signature → cache lookup → snapshot build → attestation handoff). | `IExportEngine`, `IVexExportDataSource` | |  | ||||||
| | `StellaOps.Vexer.Attestation` *(planned)* | Builds in-toto/DSSE envelopes and communicates with Sigstore/Rekor. | `IVexAttestationClient` | |  | ||||||
| | `StellaOps.Vexer.WebService` *(planned)* | Minimal API host for ingest/export endpoints. | `AddVexerWebService()` | |  | ||||||
| | `StellaOps.Vexer.Worker` *(planned)* | Background executor for scheduled pulls, verification, reconciliation, cache GC. | Hosted services | |  | ||||||
|  |  | ||||||
| All modules target .NET 10 preview and follow the same deterministic logging and serialization conventions as Feedser. | **Mission.** Convert heterogeneous **VEX** statements (OpenVEX, CSAF VEX, CycloneDX VEX; vendor/distro/platform sources) into **canonical, queryable claims**; compute **deterministic consensus** per *(vuln, product)*; preserve **conflicts with provenance**; publish **stable, attestable exports** that the backend uses to suppress non‑exploitable findings, prioritize remaining risk, and explain decisions. | ||||||
|  |  | ||||||
| ## 2. Data model | **Boundaries.** | ||||||
|  |  | ||||||
| MongoDB acts as the canonical store; collections (with logical responsibilities) are: | * Vexer **does not** decide PASS/FAIL. It supplies **evidence** (statuses + justifications + provenance weights). | ||||||
|  | * Vexer preserves **conflicting claims** unchanged; consensus encodes how we would pick, but the raw set is always exportable. | ||||||
|  | * VEX consumption is **backend‑only**: Scanner never applies VEX. The backend’s **Policy Engine** asks Vexer for status evidence and then decides what to show. | ||||||
|  |  | ||||||
| - `vex.providers` – provider metadata, trust tiers, discovery endpoints, and cosign/PGP details. | --- | ||||||
| - `vex.raw` – immutable raw documents (CSAF, CycloneDX VEX, OpenVEX, OCI attestations) with digests, retrieval metadata, and signature state. |  | ||||||
| - `vex.claims` – normalized `VexClaim` rows; deduped on `(providerId, vulnId, productKey, docDigest)`. |  | ||||||
| - `vex.consensus` – consensus projections per `(vulnId, productKey)` capturing rollup status, source weights, conflicts, and policy revision. |  | ||||||
| - `vex.exports` – export manifests containing artifact digests, cache metadata, and attestation pointers. |  | ||||||
| - `vex.cache` – index from `querySignature`/`format` to export digest for fast reuse. |  | ||||||
| - `vex.migrations` – tracks applied storage migrations (index bootstrap, future schema updates). |  | ||||||
|  |  | ||||||
| GridFS is used for large raw payloads when necessary, and artifact stores (S3/MinIO/file) hold serialized exports referenced by `vex.exports`. | ## 1) Inputs, outputs & canonical domain | ||||||
|  |  | ||||||
| ## 3. Ingestion and reconciliation flow | ### 1.1 Accepted input formats (ingest) | ||||||
|  |  | ||||||
| 1. **Discovery & configuration** – connectors load YAML/JSON settings via `StellaOps.Vexer.Policy` (provider enablement, trust overrides). | * **OpenVEX** JSON documents (attested or raw). | ||||||
| 2. **Fetch** – each `IVexConnector` pulls source windows, writing raw documents through `IVexRawDocumentSink` (Mongo-backed) with dedupe on digest. | * **CSAF VEX** 2.x (vendor PSIRTs and distros commonly publish CSAF). | ||||||
| 3. **Verification** – signatures/attestations validated through `IVexSignatureVerifier`; metadata stored alongside raw records. | * **CycloneDX VEX** 1.4+ (standalone VEX or embedded VEX blocks). | ||||||
| 4. **Normalization** – format-specific `IVexNormalizer` instances translate raw payloads to canonical `VexClaim` batches. | * **OCI‑attached attestations** (VEX statements shipped as OCI referrers) — optional connectors. | ||||||
| 5. **Consensus** – `VexConsensusResolver` (Core) consumes claims with policy weights supplied by `IVexPolicyEvaluator`, producing deterministic consensus entries and conflict annotations. |  | ||||||
| 6. **Export** – query requests pass through `VexExportEngine`, generating `VexExportManifest` instances, caching by `VexQuerySignature`, and emitting artifacts for attestation/signature. |  | ||||||
| 7. **Attestation & transparency** *(planned)* – `IVexAttestationClient` signs exports (in-toto/DSSE) and records bundles in Rekor v2. |  | ||||||
|  |  | ||||||
| The Worker coordinates the long-running steps (fetch/verify/normalize/export), while the WebService exposes synchronous APIs for on-demand operations and status lookups. | All connectors register **source metadata**: provider identity, trust tier, signature expectations (PGP/cosign/PKI), fetch windows, rate limits, and time anchors. | ||||||
|  |  | ||||||
| ## 4. Policy semantics | ### 1.2 Canonical model (normalized) | ||||||
|  |  | ||||||
| - **Weights** – default tiers (`vendor=1.0`, `distro=0.9`, `platform=0.7`, `hub=0.5`, `attestation=0.6`) loaded via `VexPolicyOptions.Weights`, with per-provider overrides. | Every incoming statement becomes a set of **VexClaim** records: | ||||||
| - **Justification gates** – policy enforces that `not_affected` claims must provide a recognized justification; rejected claims are preserved as conflicts with reason metadata. |  | ||||||
| - **Diagnostics** – policy snapshots carry structured issues for misconfigurations (out-of-range weights, empty overrides) surfaced to operators via logs and future CLI/Web endpoints. |  | ||||||
|  |  | ||||||
| Policy snapshots are immutable and versioned so consensus records capture the policy revision used during evaluation. | ``` | ||||||
|  | VexClaim | ||||||
|  | - providerId           // 'redhat', 'suse', 'ubuntu', 'github', 'vendorX' | ||||||
|  | - vulnId               // 'CVE-2025-12345', 'GHSA-xxxx', canonicalized | ||||||
|  | - productKey           // canonical product identity (see §2.2) | ||||||
|  | - status               // affected | not_affected | fixed | under_investigation | ||||||
|  | - justification?       // for 'not_affected'/'affected' where provided | ||||||
|  | - introducedVersion?   // semantics per provider (range or exact) | ||||||
|  | - fixedVersion?        // where provided (range or exact) | ||||||
|  | - lastObserved         // timestamp from source or fetch time | ||||||
|  | - provenance           // doc digest, signature status, fetch URI, line/offset anchors | ||||||
|  | - evidence[]           // raw source snippets for explainability | ||||||
|  | - supersedes?          // optional cross-doc chain (docDigest → docDigest) | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ## 5. Determinism & caching | ### 1.3 Exports (consumption) | ||||||
|  |  | ||||||
| - JSON serialization uses `VexCanonicalJsonSerializer`, enforcing property ordering and camelCase naming for reproducible snapshots and test fixtures. | * **VexConsensus** per `(vulnId, productKey)` with: | ||||||
| - `VexQuerySignature` produces canonical filter/order strings and SHA-256 digests, enabling cache keys shared across services. |  | ||||||
| - Export manifests reuse cached artifacts when the same signature/format is requested unless `ForceRefresh` is explicitly set. |  | ||||||
| - For scorring multiple sources on same VEX topic use - `VEXER_SCORRING.md` |  | ||||||
|  |  | ||||||
| ## 6. Observability & offline posture |   * `rollupStatus` (after policy weights/justification gates), | ||||||
|  |   * `sources[]` (winning + losing claims with weights & reasons), | ||||||
|  |   * `policyRevisionId` (identifier of the Vexer policy used), | ||||||
|  |   * `consensusDigest` (stable SHA‑256 over canonical JSON). | ||||||
|  | * **Raw claims** export for auditing (unchanged, with provenance). | ||||||
|  | * **Provider snapshots** (per source, last N days) for operator debugging. | ||||||
|  | * **Index** optimized for backend joins: `(productKey, vulnId) → (status, confidence, sourceSet)`. | ||||||
|  |  | ||||||
| - Structured logs (`ILogger`) capture correlation IDs, query signatures, provider IDs, and policy revisions. Metrics/OTel instrumentation will mirror Feedser once tracing hooks are added. | All exports are **deterministic**, and (optionally) **attested** via DSSE and logged to Rekor v2. | ||||||
| - Offline-first: connectors, policy bundles, and export caches can be bundled inside the Offline Kit; no mandatory outbound calls beyond configured provider allowlists. |  | ||||||
| - Operator tooling (CLI/WebService) will expose diagnostics (policy issues, verification failures, cache status) so air-gapped deployments maintain visibility without external telemetry. |  | ||||||
|  |  | ||||||
| ## 7. Roadmap highlights | --- | ||||||
|  |  | ||||||
| - Complete storage mappings for providers/consensus/cache and add migrations/indices per collection. | ## 2) Identity model — products & joins | ||||||
| - Implement Rekor/in-toto attestation clients and wire export engine to produce signed bundles. |  | ||||||
| - Build WebService endpoints (`/vexer/status`, `/vexer/claims`, `/vexer/exports`) plus CLI verbs mirroring Feedser patterns. |  | ||||||
| - Provide CSAF, CycloneDX VEX, and OpenVEX normalizers along with vendor-specific connectors (Red Hat, Cisco, SUSE, MSRC, Oracle, Ubuntu, OCI attestation). |  | ||||||
| - Extend policy diagnostics with schema validation, change tracking, and operator-facing diff reports. |  | ||||||
| - Mongo bootstrapper runs ordered migrations (`vex.migrations`) to ensure indexes for raw documents, providers, consensus snapshots, exports, and cache entries. |  | ||||||
|  |  | ||||||
| ## Appendix A – Policy diagnostics workflow | ### 2.1 Vuln identity | ||||||
|  |  | ||||||
| - `StellaOps.Vexer.Policy` now exposes `IVexPolicyDiagnostics`, producing deterministic diagnostics reports with timestamp, severity counts, active provider overrides, and the full issue list surfaced by `IVexPolicyProvider`. | * Accepts **CVE**, **GHSA**, vendor IDs (MSRC, RHSA…), distro IDs (DSA/USN/RHSA…) — normalized to `vulnId` with alias sets. | ||||||
| - CLI/WebService layers should call `IVexPolicyDiagnostics.GetDiagnostics()` to display operator-friendly summaries (`vexer policy diagnostics` and `/vexer/policy/diagnostics` are the planned entry points). | * **Alias graph** maintained (from Feedser) to map vendor/distro IDs → CVE (primary) and to **GHSA** where applicable. | ||||||
| - Recommendations in the report guide operators to resolve blocking errors, review warnings, and audit override usage before consensus runs—embed them directly in UX copy instead of re-deriving logic. |  | ||||||
| - Export/consensus telemetry should log the diagnostic `Version` alongside `policyRevisionId` so dashboards can correlate policy changes with consensus decisions. | ### 2.2 Product identity (`productKey`) | ||||||
| - Offline installations can persist the diagnostics report (JSON) in the Offline Kit to document policy headroom during audits; the output is deterministic and diff-friendly. |  | ||||||
| - Use `VexPolicyBinder` when ingesting operator-supplied YAML/JSON bundles; it normalizes weight/override values, reports deterministic issues, and returns the consensus-ready `VexConsensusPolicyOptions` used by `VexPolicyProvider`. | * **Primary:** `purl` (Package URL). | ||||||
| - Reload telemetry emits `vex.policy.reloads` (tags: `revision`, `version`, `issues`) whenever a new digest is observed—feed this into dashboards to correlate policy changes with consensus outcomes. | * **Secondary links:** `cpe`, **OS package NVRA/EVR**, NuGet/Maven/Golang identity, and **OS package name** when purl unavailable. | ||||||
|  | * **Fallback:** `oci:<registry>/<repo>@<digest>` for image‑level VEX. | ||||||
|  | * **Special cases:** kernel modules, firmware, platforms → provider‑specific mapping helpers (connector captures provider’s product taxonomy → canonical `productKey`). | ||||||
|  |  | ||||||
|  | > Vexer does not invent identities. If a provider cannot be mapped to purl/CPE/NVRA deterministically, we keep the native **product string** and mark the claim as **non‑joinable**; the backend will ignore it unless a policy explicitly whitelists that provider mapping. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Storage schema (MongoDB) | ||||||
|  |  | ||||||
|  | Database: `vexer` | ||||||
|  |  | ||||||
|  | ### 3.1 Collections | ||||||
|  |  | ||||||
|  | **`vex.providers`** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | _id: providerId | ||||||
|  | name, homepage, contact | ||||||
|  | trustTier: enum {vendor, distro, platform, hub, attestation} | ||||||
|  | signaturePolicy: { type: pgp|cosign|x509|none, keys[], certs[], cosignKeylessRoots[] } | ||||||
|  | fetch: { baseUrl, kind: http|oci|file, rateLimit, etagSupport, windowDays } | ||||||
|  | enabled: bool | ||||||
|  | createdAt, modifiedAt | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.raw`** (immutable raw documents) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | _id: sha256(doc bytes) | ||||||
|  | providerId | ||||||
|  | uri | ||||||
|  | ingestedAt | ||||||
|  | contentType | ||||||
|  | sig: { verified: bool, method: pgp|cosign|x509|none, keyId|certSubject, bundle? } | ||||||
|  | payload: GridFS pointer (if large) | ||||||
|  | disposition: kept|replaced|superseded | ||||||
|  | correlation: { replaces?: sha256, replacedBy?: sha256 } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.claims`** (normalized rows; dedupe on providerId+vulnId+productKey+docDigest) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | _id | ||||||
|  | providerId | ||||||
|  | vulnId | ||||||
|  | productKey | ||||||
|  | status | ||||||
|  | justification? | ||||||
|  | introducedVersion? | ||||||
|  | fixedVersion? | ||||||
|  | lastObserved | ||||||
|  | docDigest | ||||||
|  | provenance { uri, line?, pointer?, signatureState } | ||||||
|  | evidence[] { key, value, locator } | ||||||
|  | indices:  | ||||||
|  |   - {vulnId:1, productKey:1} | ||||||
|  |   - {providerId:1, lastObserved:-1} | ||||||
|  |   - {status:1} | ||||||
|  |   - text index (optional) on evidence.value for debugging | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.consensus`** (rollups) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | _id: sha256(canonical(vulnId, productKey, policyRevision)) | ||||||
|  | vulnId | ||||||
|  | productKey | ||||||
|  | rollupStatus | ||||||
|  | sources[]: [ | ||||||
|  |   { providerId, status, justification?, weight, lastObserved, accepted:bool, reason } | ||||||
|  | ] | ||||||
|  | policyRevisionId | ||||||
|  | evaluatedAt | ||||||
|  | consensusDigest  // same as _id | ||||||
|  | indices: | ||||||
|  |   - {vulnId:1, productKey:1} | ||||||
|  |   - {policyRevisionId:1, evaluatedAt:-1} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.exports`** (manifest of emitted artifacts) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | _id | ||||||
|  | querySignature | ||||||
|  | format: raw|consensus|index | ||||||
|  | artifactSha256 | ||||||
|  | rekor { uuid, index, url }? | ||||||
|  | createdAt | ||||||
|  | policyRevisionId | ||||||
|  | cacheable: bool | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.cache`** | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | querySignature -> exportId (for fast reuse) | ||||||
|  | ttl, hits | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **`vex.migrations`** | ||||||
|  |  | ||||||
|  | * ordered migrations applied at bootstrap to ensure indexes. | ||||||
|  |  | ||||||
|  | ### 3.2 Indexing strategy | ||||||
|  |  | ||||||
|  | * Hot path queries use exact `(vulnId, productKey)` and time‑bounded windows; compound indexes cover both. | ||||||
|  | * Providers list view by `lastObserved` for monitoring staleness. | ||||||
|  | * `vex.consensus` keyed by `(vulnId, productKey, policyRevision)` for deterministic reuse. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Ingestion pipeline | ||||||
|  |  | ||||||
|  | ### 4.1 Connector contract | ||||||
|  |  | ||||||
|  | ```csharp | ||||||
|  | public interface IVexConnector | ||||||
|  | { | ||||||
|  |     string ProviderId { get; } | ||||||
|  |     Task FetchAsync(VexConnectorContext ctx, CancellationToken ct);   // raw docs | ||||||
|  |     Task NormalizeAsync(VexConnectorContext ctx, CancellationToken ct); // raw -> VexClaim[] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | * **Fetch** must implement: window scheduling, conditional GET (ETag/If‑Modified‑Since), rate limiting, retry/backoff. | ||||||
|  | * **Normalize** parses the format, validates schema, maps product identities deterministically, emits `VexClaim` records with **provenance**. | ||||||
|  |  | ||||||
|  | ### 4.2 Signature verification (per provider) | ||||||
|  |  | ||||||
|  | * **cosign (keyless or keyful)** for OCI referrers or HTTP‑served JSON with Sigstore bundles. | ||||||
|  | * **PGP** (provider keyrings) for distro/vendor feeds that sign docs. | ||||||
|  | * **x509** (mutual TLS / provider‑pinned certs) where applicable. | ||||||
|  | * Signature state is stored on **vex.raw.sig** and copied into **provenance.signatureState** on claims. | ||||||
|  |  | ||||||
|  | > Claims from sources failing signature policy are marked `"signatureState.verified=false"` and **policy** can down‑weight or ignore them. | ||||||
|  |  | ||||||
|  | ### 4.3 Time discipline | ||||||
|  |  | ||||||
|  | * For each doc, prefer **provider’s document timestamp**; if absent, use fetch time. | ||||||
|  | * Claims carry `lastObserved` which drives **tie‑breaking** within equal weight tiers. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Normalization: product & status semantics | ||||||
|  |  | ||||||
|  | ### 5.1 Product mapping | ||||||
|  |  | ||||||
|  | * **purl** first; **cpe** second; OS package NVRA/EVR mapping helpers (distro connectors) produce purls via canonical tables (e.g., rpm→purl:rpm, deb→purl:deb). | ||||||
|  | * Where a provider publishes **platform‑level** VEX (e.g., “RHEL 9 not affected”), connectors expand to known product inventory rules (e.g., map to sets of packages/components shipped in the platform). Expansion tables are versioned and kept per provider; every expansion emits **evidence** indicating the rule applied. | ||||||
|  | * If expansion would be speculative, the claim remains **platform‑scoped** with `productKey="platform:redhat:rhel:9"` and is flagged **non‑joinable**; backend can decide to use platform VEX only when Scanner proves the platform runtime. | ||||||
|  |  | ||||||
|  | ### 5.2 Status + justification mapping | ||||||
|  |  | ||||||
|  | * Canonical **status**: `affected | not_affected | fixed | under_investigation`. | ||||||
|  | * **Justifications** normalized to a controlled vocabulary (CISA‑aligned), e.g.: | ||||||
|  |  | ||||||
|  |   * `component_not_present` | ||||||
|  |   * `vulnerable_code_not_in_execute_path` | ||||||
|  |   * `vulnerable_configuration_unused` | ||||||
|  |   * `inline_mitigation_applied` | ||||||
|  |   * `fix_available` (with `fixedVersion`) | ||||||
|  |   * `under_investigation` | ||||||
|  | * Providers with free‑text justifications are mapped by deterministic tables; raw text preserved as `evidence`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Consensus algorithm | ||||||
|  |  | ||||||
|  | **Goal:** produce a **stable**, explainable `rollupStatus` per `(vulnId, productKey)` given possibly conflicting claims. | ||||||
|  |  | ||||||
|  | ### 6.1 Inputs | ||||||
|  |  | ||||||
|  | * Set **S** of `VexClaim` for the key. | ||||||
|  | * **Vexer policy snapshot**: | ||||||
|  |  | ||||||
|  |   * **weights** per provider tier and per provider overrides. | ||||||
|  |   * **justification gates** (e.g., require justification for `not_affected` to be acceptable). | ||||||
|  |   * **minEvidence** rules (e.g., `not_affected` must come from ≥1 vendor or 2 distros). | ||||||
|  |   * **signature requirements** (e.g., require verified signature for ‘fixed’ to be considered). | ||||||
|  |  | ||||||
|  | ### 6.2 Steps | ||||||
|  |  | ||||||
|  | 1. **Filter invalid** claims by signature policy & justification gates → set `S'`. | ||||||
|  | 2. **Score** each claim: | ||||||
|  |    `score = weight(provider) * freshnessFactor(lastObserved)` where freshnessFactor ∈ [0.8, 1.0] for staleness decay (configurable; small effect). | ||||||
|  | 3. **Aggregate** scores per status: `W(status) = Σ score(claims with that status)`. | ||||||
|  | 4. **Pick** `rollupStatus = argmax_status W(status)`. | ||||||
|  | 5. **Tie‑breakers** (in order): | ||||||
|  |  | ||||||
|  |    * Higher **max single** provider score wins (vendor > distro > platform > hub). | ||||||
|  |    * More **recent** lastObserved wins. | ||||||
|  |    * Deterministic lexicographic order of status (`fixed` > `not_affected` > `under_investigation` > `affected`) as final tiebreaker. | ||||||
|  | 6. **Explain**: mark accepted sources (`accepted=true; reason="weight"`/`"freshness"`), mark rejected sources with explicit `reason` (`"insufficient_justification"`, `"signature_unverified"`, `"lower_weight"`). | ||||||
|  |  | ||||||
|  | > The algorithm is **pure** given S and policy snapshot; result is reproducible and hashed into `consensusDigest`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Query & export APIs | ||||||
|  |  | ||||||
|  | All endpoints are versioned under `/api/v1/vex`. | ||||||
|  |  | ||||||
|  | ### 7.1 Query (online) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /claims/search | ||||||
|  |   body: { vulnIds?: string[], productKeys?: string[], providers?: string[], since?: timestamp, limit?: int, pageToken?: string } | ||||||
|  |   → { claims[], nextPageToken? } | ||||||
|  |  | ||||||
|  | POST /consensus/search | ||||||
|  |   body: { vulnIds?: string[], productKeys?: string[], policyRevisionId?: string, since?: timestamp, limit?: int, pageToken?: string } | ||||||
|  |   → { entries[], nextPageToken? } | ||||||
|  |  | ||||||
|  | POST /resolve | ||||||
|  |   body: { purls: string[], vulnIds: string[], policyRevisionId?: string } | ||||||
|  |   → { results: [ { vulnId, productKey, rollupStatus, sources[] } ] } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.2 Exports (cacheable snapshots) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | POST /exports | ||||||
|  |   body: { signature: { vulnFilter?, productFilter?, providers?, since? }, format: raw|consensus|index, policyRevisionId?: string, force?: bool } | ||||||
|  |   → { exportId, artifactSha256, rekor? } | ||||||
|  |  | ||||||
|  | GET  /exports/{exportId}        → bytes (application/json or binary index) | ||||||
|  | GET  /exports/{exportId}/meta   → { signature, policyRevisionId, createdAt, artifactSha256, rekor? } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 7.3 Provider operations | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | GET  /providers                  → provider list & signature policy | ||||||
|  | POST /providers/{id}/refresh     → trigger fetch/normalize window | ||||||
|  | GET  /providers/{id}/status      → last fetch, doc counts, signature stats | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Auth:** service‑to‑service via Authority tokens; operator operations via UI/CLI with RBAC. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Attestation integration | ||||||
|  |  | ||||||
|  | * Exports can be **DSSE‑signed** via **Signer** and logged to **Rekor v2** via **Attestor** (optional but recommended for regulated pipelines). | ||||||
|  | * `vex.exports.rekor` stores `{uuid, index, url}` when present. | ||||||
|  | * **Predicate type**: `https://stella-ops.org/attestations/vex-export/1` with fields: | ||||||
|  |  | ||||||
|  |   * `querySignature`, `policyRevisionId`, `artifactSha256`, `createdAt`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | vexer: | ||||||
|  |   mongo: { uri: "mongodb://mongo/vexer" } | ||||||
|  |   s3: | ||||||
|  |     endpoint: http://minio:9000 | ||||||
|  |     bucket: stellaops | ||||||
|  |   policy: | ||||||
|  |     weights: | ||||||
|  |       vendor: 1.0 | ||||||
|  |       distro: 0.9 | ||||||
|  |       platform: 0.7 | ||||||
|  |       hub: 0.5 | ||||||
|  |       attestation: 0.6 | ||||||
|  |     providerOverrides: | ||||||
|  |       redhat: 1.0 | ||||||
|  |       suse: 0.95 | ||||||
|  |     requireJustificationForNotAffected: true | ||||||
|  |     signatureRequiredForFixed: true | ||||||
|  |     minEvidence: | ||||||
|  |       not_affected: | ||||||
|  |         vendorOrTwoDistros: true | ||||||
|  |   connectors: | ||||||
|  |     - providerId: redhat | ||||||
|  |       kind: csaf | ||||||
|  |       baseUrl: https://access.redhat.com/security/data/csaf/v2/ | ||||||
|  |       signaturePolicy: { type: pgp, keys: [ "…redhat-pgp-key…" ] } | ||||||
|  |       windowDays: 7 | ||||||
|  |     - providerId: suse | ||||||
|  |       kind: csaf | ||||||
|  |       baseUrl: https://ftp.suse.com/pub/projects/security/csaf/ | ||||||
|  |       signaturePolicy: { type: pgp, keys: [ "…suse-pgp-key…" ] } | ||||||
|  |     - providerId: ubuntu | ||||||
|  |       kind: openvex | ||||||
|  |       baseUrl: https://…/vex/ | ||||||
|  |       signaturePolicy: { type: none } | ||||||
|  |     - providerId: vendorX | ||||||
|  |       kind: cyclonedx-vex | ||||||
|  |       ociRef: ghcr.io/vendorx/vex@sha256:… | ||||||
|  |       signaturePolicy: { type: cosign, cosignKeylessRoots: [ "sigstore-root" ] } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Security model | ||||||
|  |  | ||||||
|  | * **Input signature verification** enforced per provider policy (PGP, cosign, x509). | ||||||
|  | * **Connector allowlists**: outbound fetch constrained to configured domains. | ||||||
|  | * **Tenant isolation**: per‑tenant DB prefixes or separate DBs; per‑tenant S3 prefixes; per‑tenant policies. | ||||||
|  | * **AuthN/Z**: Authority‑issued OpToks; RBAC roles (`vex.read`, `vex.admin`, `vex.export`). | ||||||
|  | * **No secrets in logs**; deterministic logging contexts include providerId, docDigest, claim keys. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Performance & scale | ||||||
|  |  | ||||||
|  | * **Targets:** | ||||||
|  |  | ||||||
|  |   * Normalize 10k VEX claims/minute/core. | ||||||
|  |   * Consensus compute ≤ 50 ms for 1k unique `(vuln, product)` pairs in hot cache. | ||||||
|  |   * Export (consensus) 1M rows in ≤ 60 s on 8 cores with streaming writer. | ||||||
|  |  | ||||||
|  | * **Scaling:** | ||||||
|  |  | ||||||
|  |   * WebService handles control APIs; **Worker** background services (same image) execute fetch/normalize in parallel with rate‑limits; Mongo writes batched; upserts by natural keys. | ||||||
|  |   * Exports stream straight to S3 (MinIO) with rolling buffers. | ||||||
|  |  | ||||||
|  | * **Caching:** | ||||||
|  |  | ||||||
|  |   * `vex.cache` maps query signatures → export; TTL to avoid stampedes; optimistic reuse unless `force`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Observability | ||||||
|  |  | ||||||
|  | * **Metrics:** | ||||||
|  |  | ||||||
|  |   * `vex.ingest.docs_total{provider}` | ||||||
|  |   * `vex.normalize.claims_total{provider}` | ||||||
|  |   * `vex.signature.failures_total{provider,method}` | ||||||
|  |   * `vex.consensus.conflicts_total{vulnId}` | ||||||
|  |   * `vex.exports.bytes{format}` / `vex.exports.latency_seconds` | ||||||
|  | * **Tracing:** spans for fetch, verify, parse, map, consensus, export. | ||||||
|  | * **Dashboards:** provider staleness, top conflicting vulns/components, signature posture, export cache hit‑rate. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Testing matrix | ||||||
|  |  | ||||||
|  | * **Connectors:** golden raw docs → deterministic claims (fixtures per provider/format). | ||||||
|  | * **Signature policies:** valid/invalid PGP/cosign/x509 samples; ensure rejects are recorded but not accepted. | ||||||
|  | * **Normalization edge cases:** platform‑only claims, free‑text justifications, non‑purl products. | ||||||
|  | * **Consensus:** conflict scenarios across tiers; check tie‑breakers; justification gates. | ||||||
|  | * **Performance:** 1M‑row export timing; memory ceilings; stream correctness. | ||||||
|  | * **Determinism:** same inputs + policy → identical `consensusDigest` and export bytes. | ||||||
|  | * **API contract tests:** pagination, filters, RBAC, rate limits. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Integration points | ||||||
|  |  | ||||||
|  | * **Backend Policy Engine** (in Scanner.WebService): calls `POST /resolve` with batched `(purl, vulnId)` pairs to fetch `rollupStatus + sources`. | ||||||
|  | * **Feedser**: provides alias graph (CVE↔vendor IDs) and may supply VEX‑adjacent metadata (e.g., KEV flag) for policy escalation. | ||||||
|  | * **UI**: VEX explorer screens use `/claims/search` and `/consensus/search`; show conflicts & provenance. | ||||||
|  | * **CLI**: `stellaops vex export --consensus --since 7d --out vex.json` for audits. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Failure modes & fallback | ||||||
|  |  | ||||||
|  | * **Provider unreachable:** stale thresholds trigger warnings; policy can down‑weight stale providers automatically (freshness factor). | ||||||
|  | * **Signature outage:** continue to ingest but mark `signatureState.verified=false`; consensus will likely exclude or down‑weight per policy. | ||||||
|  | * **Schema drift:** unknown fields are preserved as `evidence`; normalization rejects only on **invalid identity** or **status**. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 16) Rollout plan (incremental) | ||||||
|  |  | ||||||
|  | 1. **MVP**: OpenVEX + CSAF connectors for 3 major providers (e.g., Red Hat/SUSE/Ubuntu), normalization + consensus + `/resolve`. | ||||||
|  | 2. **Signature policies**: PGP for distros; cosign for OCI. | ||||||
|  | 3. **Exports + optional attestation**. | ||||||
|  | 4. **CycloneDX VEX** connectors; platform claim expansion tables; UI explorer. | ||||||
|  | 5. **Scale hardening**: export indexes; conflict analytics. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 17) Appendix — canonical JSON (stable ordering) | ||||||
|  |  | ||||||
|  | All exports and consensus entries are serialized via `VexCanonicalJsonSerializer`: | ||||||
|  |  | ||||||
|  | * UTF‑8 without BOM; | ||||||
|  | * keys sorted (ASCII); | ||||||
|  | * arrays sorted by `(providerId, vulnId, productKey, lastObserved)` unless semantic order mandated; | ||||||
|  | * timestamps in `YYYY‑MM‑DDThh:mm:ssZ`; | ||||||
|  | * no insignificant whitespace. | ||||||
|  |  | ||||||
| This architecture keeps Vexer aligned with StellaOps' deterministic, offline-operable design while layering VEX-specific consensus and attestation capabilities on top of the Feedser foundations. |  | ||||||
|   | |||||||
							
								
								
									
										451
									
								
								docs/ARCHITECTURE_ZASTAVA.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										451
									
								
								docs/ARCHITECTURE_ZASTAVA.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,451 @@ | |||||||
|  | # component_architecture_zastava.md — **Stella Ops Zastava** (2025Q4) | ||||||
|  |  | ||||||
|  | > **Scope.** Implementation‑ready architecture for **Zastava**: the **runtime inspector/enforcer** that watches real workloads, detects drift from the scanned baseline, verifies image/SBOM/attestation posture, and (optionally) **admits/blocks** deployments. Includes Kubernetes & plain‑Docker topologies, data contracts, APIs, security posture, performance targets, test matrices, and failure modes. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 0) Mission & boundaries | ||||||
|  |  | ||||||
|  | **Mission.** Give operators **ground‑truth** from running environments and a **fast guardrail** before workloads land: | ||||||
|  |  | ||||||
|  | * **Observer:** inventory containers, entrypoints actually executed, and DSOs actually loaded; verify **image signature**, **SBOM referrers**, and **attestation** presence; detect **drift** (unexpected processes/paths) and **policy violations**; publish **runtime events** to Scanner.WebService. | ||||||
|  | * **Admission (optional):** Kubernetes ValidatingAdmissionWebhook that enforces minimal posture (signed images, SBOM availability, known base images, policy PASS) **pre‑flight**. | ||||||
|  |  | ||||||
|  | **Boundaries.** | ||||||
|  |  | ||||||
|  | * Zastava **does not** compute SBOMs and does not sign; it **consumes** Scanner/WebService outputs and **enforces** backend policy verdicts. | ||||||
|  | * Zastava can **request** a delta scan when the baseline is missing/stale, but scanning is done by **Scanner.Worker**. | ||||||
|  | * On non‑K8s Docker hosts, Zastava runs as a host service with **observer‑only** features. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1) Topology & processes | ||||||
|  |  | ||||||
|  | ### 1.1 Components (Kubernetes) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | stellaops/zastava-observer    # DaemonSet on every node (read-only host mounts) | ||||||
|  | stellaops/zastava-webhook     # ValidatingAdmissionWebhook (Deployment, 2+ replicas) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 1.2 Components (Docker/VM) | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | stellaops/zastava-agent       # System service; watch Docker events; observer only | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 1.3 Dependencies | ||||||
|  |  | ||||||
|  | * **Authority** (OIDC): short OpToks (DPoP/mTLS) for API calls to Scanner.WebService. | ||||||
|  | * **Scanner.WebService**: `/runtime/events` ingestion; `/policy/runtime` fetch. | ||||||
|  | * **OCI Registry** (optional): for direct referrers/sig checks if not delegated to backend. | ||||||
|  | * **Container runtime**: containerd/CRI‑O/Docker (read interfaces only). | ||||||
|  | * **Kubernetes API** (watch Pods in cluster; validating webhook). | ||||||
|  | * **Host mounts** (K8s DaemonSet): `/proc`, `/var/lib/containerd` (or CRI‑O), `/run/containerd/containerd.sock` (optional, read‑only). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2) Data contracts | ||||||
|  |  | ||||||
|  | ### 2.1 Runtime event (observer → Scanner.WebService) | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "eventId": "9f6a…", | ||||||
|  |   "when": "2025-10-17T12:34:56Z", | ||||||
|  |   "kind": "CONTAINER_START|CONTAINER_STOP|DRIFT|POLICY_VIOLATION|ATTESTATION_STATUS", | ||||||
|  |   "tenant": "tenant-01", | ||||||
|  |   "node": "ip-10-0-1-23", | ||||||
|  |   "runtime": { "engine": "containerd", "version": "1.7.19" }, | ||||||
|  |   "workload": { | ||||||
|  |     "platform": "kubernetes", | ||||||
|  |     "namespace": "payments", | ||||||
|  |     "pod": "api-7c9fbbd8b7-ktd84", | ||||||
|  |     "container": "api", | ||||||
|  |     "containerId": "containerd://...", | ||||||
|  |     "imageRef": "ghcr.io/acme/api@sha256:abcd…", | ||||||
|  |     "owner": { "kind": "Deployment", "name": "api" } | ||||||
|  |   }, | ||||||
|  |   "process": { | ||||||
|  |     "pid": 12345, | ||||||
|  |     "entrypoint": ["/entrypoint.sh", "--serve"], | ||||||
|  |     "entryTrace": [ | ||||||
|  |       {"file":"/entrypoint.sh","line":3,"op":"exec","target":"/usr/bin/python3"}, | ||||||
|  |       {"file":"<argv>","op":"python","target":"/opt/app/server.py"} | ||||||
|  |     ] | ||||||
|  |   }, | ||||||
|  |   "loadedLibs": [ | ||||||
|  |     { "path": "/lib/x86_64-linux-gnu/libssl.so.3", "inode": 123456, "sha256": "…"}, | ||||||
|  |     { "path": "/usr/lib/x86_64-linux-gnu/libcrypto.so.3", "inode": 123457, "sha256": "…"} | ||||||
|  |   ], | ||||||
|  |   "posture": { | ||||||
|  |     "imageSigned": true, | ||||||
|  |     "sbomReferrer": "present|missing", | ||||||
|  |     "attestation": { "uuid": "rekor-uuid", "verified": true } | ||||||
|  |   }, | ||||||
|  |   "delta": { | ||||||
|  |     "baselineImageDigest": "sha256:abcd…", | ||||||
|  |     "changedFiles": ["/opt/app/server.py"],           // optional quick signal | ||||||
|  |     "newBinaries": [{ "path":"/usr/local/bin/helper","sha256":"…" }] | ||||||
|  |   }, | ||||||
|  |   "evidence": [ | ||||||
|  |     {"signal":"procfs.maps","value":"/lib/.../libssl.so.3@0x7f..."}, | ||||||
|  |     {"signal":"cri.task.inspect","value":"pid=12345"}, | ||||||
|  |     {"signal":"registry.referrers","value":"sbom: application/vnd.cyclonedx+json"} | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 2.2 Admission decision (webhook → API server) | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "admissionId": "…", | ||||||
|  |   "namespace": "payments", | ||||||
|  |   "podSpecDigest": "sha256:…", | ||||||
|  |   "images": [ | ||||||
|  |     { | ||||||
|  |       "name": "ghcr.io/acme/api:1.2.3", | ||||||
|  |       "resolved": "ghcr.io/acme/api@sha256:abcd…", | ||||||
|  |       "signed": true, | ||||||
|  |       "hasSbomReferrers": true, | ||||||
|  |       "policyVerdict": "pass|warn|fail", | ||||||
|  |       "reasons": ["unsigned base image", "missing SBOM"] | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "decision": "Allow|Deny", | ||||||
|  |   "ttlSeconds": 300 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3) Observer — node agent (DaemonSet) | ||||||
|  |  | ||||||
|  | ### 3.1 Responsibilities | ||||||
|  |  | ||||||
|  | * **Watch** container lifecycle (start/stop) via CRI (`/run/containerd/containerd.sock` gRPC read‑only) or `/var/log/containers/*.log` tail fallback. | ||||||
|  | * **Resolve** container → image digest, mount point rootfs. | ||||||
|  | * **Trace entrypoint**: attach **short‑lived** nsenter/exec to PID 1 in container, parse shell for `exec` chain (bounded depth), record **terminal program**. | ||||||
|  | * **Sample loaded libs**: read `/proc/<pid>/maps` and `exe` symlink to collect **actually loaded** DSOs; compute **sha256** for each mapped file (bounded count/size). | ||||||
|  | * **Posture check** (cheap): | ||||||
|  |  | ||||||
|  |   * Image signature presence (if cosign policies are local; else ask backend). | ||||||
|  |   * SBOM **referrers** presence (HEAD to registry, optional). | ||||||
|  |   * Rekor UUID known (query Scanner.WebService by image digest). | ||||||
|  | * **Publish runtime events** to Scanner.WebService `/runtime/events` (batch & compress). | ||||||
|  | * **Request delta scan** if: no SBOM in catalog OR base differs from known baseline. | ||||||
|  |  | ||||||
|  | ### 3.2 Privileges & mounts (K8s) | ||||||
|  |  | ||||||
|  | * **SecurityContext:** `runAsUser: 0`, `readOnlyRootFilesystem: true`, `allowPrivilegeEscalation: false`. | ||||||
|  | * **Capabilities:** `CAP_SYS_PTRACE` (optional if using nsenter trace), `CAP_DAC_READ_SEARCH`. | ||||||
|  | * **Host mounts (read‑only):** | ||||||
|  |  | ||||||
|  |   * `/proc` (host) → `/host/proc` | ||||||
|  |   * `/run/containerd/containerd.sock` (or CRI‑O socket) | ||||||
|  |   * `/var/lib/containerd/io.containerd.runtime.v2.task` (rootfs paths & pids) | ||||||
|  | * **Networking:** cluster‑internal egress to Scanner.WebService only. | ||||||
|  | * **Rate limits:** hard caps for bytes hashed and file count per container to avoid noisy tenants. | ||||||
|  |  | ||||||
|  | ### 3.3 Event batching | ||||||
|  |  | ||||||
|  | * Buffer ND‑JSON; flush by **N events** or **2 s**. | ||||||
|  | * Backpressure: local disk ring buffer (50 MB default) if Scanner is temporarily unavailable; drop oldest after cap with **metrics** and **warning** event. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4) Admission Webhook (Kubernetes) | ||||||
|  |  | ||||||
|  | ### 4.1 Gate criteria | ||||||
|  |  | ||||||
|  | Configurable policy (fetched from backend and cached): | ||||||
|  |  | ||||||
|  | * **Image signature**: must be cosign‑verifiable to configured key(s) or keyless identities. | ||||||
|  | * **SBOM availability**: at least one **CycloneDX** referrer or **Scanner.WebService** catalog entry. | ||||||
|  | * **Scanner policy verdict**: backend `PASS` required for namespaces/labels matching rules; allow `WARN` if configured. | ||||||
|  | * **Registry allowlists/denylists**. | ||||||
|  | * **Tag bans** (e.g., `:latest`). | ||||||
|  | * **Base image allowlists** (by digest). | ||||||
|  |  | ||||||
|  | ### 4.2 Flow | ||||||
|  |  | ||||||
|  | ```mermaid | ||||||
|  | sequenceDiagram | ||||||
|  |   autonumber | ||||||
|  |   participant K8s as API Server | ||||||
|  |   participant WH as Zastava Webhook | ||||||
|  |   participant SW as Scanner.WebService | ||||||
|  |  | ||||||
|  |   K8s->>WH: AdmissionReview(Pod) | ||||||
|  |   WH->>WH: Resolve images to digests (remote HEAD/pull if needed) | ||||||
|  |   WH->>SW: POST /policy/runtime { digests, namespace, labels } | ||||||
|  |   SW-->>WH: { per-image: {signed, hasSbom, verdict, reasons}, ttl } | ||||||
|  |   alt All pass | ||||||
|  |     WH-->>K8s: AdmissionResponse(Allow, ttl) | ||||||
|  |   else Any fail (enforce=true) | ||||||
|  |     WH-->>K8s: AdmissionResponse(Deny, message) | ||||||
|  |   end | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Caching:** Per‑digest result cached `ttlSeconds` (default 300 s). **Fail‑open** or **fail‑closed** is configurable per namespace. | ||||||
|  |  | ||||||
|  | ### 4.3 TLS & HA | ||||||
|  |  | ||||||
|  | * Webhook has its own **serving cert** signed by cluster CA (or custom cert + CA bundle on configuration). | ||||||
|  | * Deployment ≥ 2 replicas; **leaderless**; stateless. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5) Backend integration (Scanner.WebService) | ||||||
|  |  | ||||||
|  | ### 5.1 Ingestion endpoint | ||||||
|  |  | ||||||
|  | `POST /api/v1/scanner/runtime/events` *(OpTok + DPoP/mTLS)* | ||||||
|  |  | ||||||
|  | * Validates event schema; enforces rate caps by tenant/node; persists to **Mongo** (`runtime.events` capped collection or regular with TTL). | ||||||
|  | * Performs **correlation**: | ||||||
|  |  | ||||||
|  |   * Attach nearest **image SBOM** (inventory/usage) and **BOM‑Index** if known. | ||||||
|  |   * If unknown/missing, schedule **delta scan** and return `202 Accepted`. | ||||||
|  | * Emits **derived signals** (usedByEntrypoint per component based on `/proc/<pid>/maps`). | ||||||
|  |  | ||||||
|  | ### 5.2 Policy decision API (for webhook) | ||||||
|  |  | ||||||
|  | `POST /api/v1/scanner/policy/runtime` | ||||||
|  |  | ||||||
|  | Request: | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "namespace": "payments", | ||||||
|  |   "labels": { "app": "api", "env": "prod" }, | ||||||
|  |   "images": ["ghcr.io/acme/api@sha256:...", "ghcr.io/acme/nginx@sha256:..."] | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Response: | ||||||
|  |  | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  |   "ttlSeconds": 300, | ||||||
|  |   "results": { | ||||||
|  |     "ghcr.io/acme/api@sha256:...": { | ||||||
|  |       "signed": true, | ||||||
|  |       "hasSbom": true, | ||||||
|  |       "policyVerdict": "pass", | ||||||
|  |       "reasons": [], | ||||||
|  |       "rekor": { "uuid": "..." } | ||||||
|  |     }, | ||||||
|  |     "ghcr.io/acme/nginx@sha256:...": { | ||||||
|  |       "signed": false, | ||||||
|  |       "hasSbom": false, | ||||||
|  |       "policyVerdict": "fail", | ||||||
|  |       "reasons": ["unsigned", "missing SBOM"] | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6) Configuration (YAML) | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | zastava: | ||||||
|  |   mode: | ||||||
|  |     observer: true | ||||||
|  |     webhook: true | ||||||
|  |   authority: | ||||||
|  |     issuer: "https://authority.internal" | ||||||
|  |     aud: ["scanner","zastava"]    # tokens for backend and self-id | ||||||
|  |   backend: | ||||||
|  |     url: "https://scanner-web.internal" | ||||||
|  |     connectTimeoutMs: 500 | ||||||
|  |     requestTimeoutMs: 1500 | ||||||
|  |     retry: { attempts: 3, backoffMs: 200 } | ||||||
|  |   runtime: | ||||||
|  |     engine: "auto"    # containerd|cri-o|docker|auto | ||||||
|  |     procfs: "/host/proc" | ||||||
|  |     collect: | ||||||
|  |       entryTrace: true | ||||||
|  |       loadedLibs: true | ||||||
|  |       maxLibs: 256 | ||||||
|  |       maxHashBytesPerContainer: 64_000_000 | ||||||
|  |       maxDepth: 48 | ||||||
|  |   admission: | ||||||
|  |     enforce: true | ||||||
|  |     failOpenNamespaces: ["dev", "test"] | ||||||
|  |     verify: | ||||||
|  |       imageSignature: true | ||||||
|  |       sbomReferrer: true | ||||||
|  |       scannerPolicyPass: true | ||||||
|  |     cacheTtlSeconds: 300 | ||||||
|  |     resolveTags: true          # do remote digest resolution for tag-only images | ||||||
|  |   limits: | ||||||
|  |     eventsPerSecond: 50 | ||||||
|  |     burst: 200 | ||||||
|  |     perNodeQueue: 10_000 | ||||||
|  |   security: | ||||||
|  |     mounts: | ||||||
|  |       containerdSock: "/run/containerd/containerd.sock:ro" | ||||||
|  |       proc: "/proc:/host/proc:ro" | ||||||
|  |       runtimeState: "/var/lib/containerd:ro" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 7) Security posture | ||||||
|  |  | ||||||
|  | * **AuthN/Z**: Authority OpToks (DPoP preferred) to backend; webhook does **not** require client auth from API server (K8s handles). | ||||||
|  | * **Least privileges**: read‑only host mounts; optional `CAP_SYS_PTRACE`; **no** host networking; **no** write mounts. | ||||||
|  | * **Isolation**: never exec untrusted code; nsenter only to **read** `/proc/<pid>`. | ||||||
|  | * **Data minimization**: do not exfiltrate env vars or command arguments unless policy explicitly enables diagnostic mode. | ||||||
|  | * **Rate limiting**: per‑node caps; per‑tenant caps at backend. | ||||||
|  | * **Hard caps**: bytes hashed, files inspected, depth of shell parsing. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 8) Metrics, logs, tracing | ||||||
|  |  | ||||||
|  | **Observer** | ||||||
|  |  | ||||||
|  | * `zastava.events_emitted_total{kind}` | ||||||
|  | * `zastava.proc_maps_samples_total{result}` | ||||||
|  | * `zastava.entrytrace_depth{p99}` | ||||||
|  | * `zastava.hash_bytes_total` | ||||||
|  | * `zastava.buffer_drops_total` | ||||||
|  |  | ||||||
|  | **Webhook** | ||||||
|  |  | ||||||
|  | * `zastava.admission_requests_total{decision}` | ||||||
|  | * `zastava.admission_latency_seconds` | ||||||
|  | * `zastava.cache_hits_total` | ||||||
|  | * `zastava.backend_failures_total` | ||||||
|  |  | ||||||
|  | **Logs** (structured): node, pod, image digest, decision, reasons. | ||||||
|  | **Tracing**: spans for observe→batch→post; webhook request→resolve→respond. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 9) Performance & scale targets | ||||||
|  |  | ||||||
|  | * **Observer**: ≤ **30 ms** to sample `/proc/<pid>/maps` and compute quick hashes for ≤ 64 files; ≤ **200 ms** for full library set (256 libs). | ||||||
|  | * **Webhook**: P95 ≤ **8 ms** with warm cache; ≤ **50 ms** with one backend round‑trip. | ||||||
|  | * **Throughput**: 1k admission requests/min/replica; 5k runtime events/min/node with batching. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 10) Drift detection model | ||||||
|  |  | ||||||
|  | **Signals** | ||||||
|  |  | ||||||
|  | * **Process drift**: terminal program differs from **EntryTrace** baseline. | ||||||
|  | * **Library drift**: loaded DSOs not present in **Usage** SBOM view. | ||||||
|  | * **Filesystem drift**: new executable files under `/usr/local/bin`, `/opt`, `/app` with **mtime** after image creation. | ||||||
|  | * **Network drift** (optional): listening sockets on unexpected ports (from policy). | ||||||
|  |  | ||||||
|  | **Action** | ||||||
|  |  | ||||||
|  | * Emit `DRIFT` event with evidence; backend can **auto‑queue** a delta scan; policy may **escalate** to alert/block (Admission cannot block already‑running pods; rely on K8s policies/PodSecurity or operator action). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 11) Test matrix | ||||||
|  |  | ||||||
|  | * **Engines**: containerd, CRI‑O, Docker; ensure PID resolution and rootfs mapping. | ||||||
|  | * **EntryTrace**: bash features (case, if, run‑parts, `.`/`source`), language launchers (python/node/java). | ||||||
|  | * **Procfs**: multiple arches, musl/glibc images; static binaries (maps minimal). | ||||||
|  | * **Admission**: unsigned images, missing SBOM referrers, tag‑only images, digest resolution, backend latency, cache TTL. | ||||||
|  | * **Perf/soak**: 500 Pods/node churn; webhook under HPA growth. | ||||||
|  | * **Security**: attempt privilege escalation disabled, read‑only mounts enforced, rate‑limit abuse. | ||||||
|  | * **Failure injection**: backend down (observer buffers, webhook fail‑open/closed), registry throttling, containerd socket unavailable. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 12) Failure modes & responses | ||||||
|  |  | ||||||
|  | | Condition                       | Observer behavior                              | Webhook behavior                                       | | ||||||
|  | | ------------------------------- | ---------------------------------------------- | ------------------------------------------------------ | | ||||||
|  | | Backend unreachable             | Buffer to disk; drop after cap; emit metric    | **Fail‑open/closed** per namespace config              | | ||||||
|  | | PID vanished mid‑sample         | Retry once; emit partial evidence              | N/A                                                    | | ||||||
|  | | CRI socket missing              | Fallback to K8s events only (reduced fidelity) | N/A                                                    | | ||||||
|  | | Registry digest resolve blocked | Defer to backend; mark `resolve=unknown`       | Deny or allow per `resolveTags` & `failOpenNamespaces` | | ||||||
|  | | Excessive events                | Apply local rate limit, coalesce               | N/A                                                    | | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 13) Deployment notes (K8s) | ||||||
|  |  | ||||||
|  | **DaemonSet (snippet):** | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | apiVersion: apps/v1 | ||||||
|  | kind: DaemonSet | ||||||
|  | metadata: { name: zastava-observer, namespace: stellaops } | ||||||
|  | spec: | ||||||
|  |   template: | ||||||
|  |     spec: | ||||||
|  |       serviceAccountName: zastava | ||||||
|  |       hostPID: true | ||||||
|  |       containers: | ||||||
|  |       - name: observer | ||||||
|  |         image: stellaops/zastava-observer:2.3 | ||||||
|  |         securityContext: | ||||||
|  |           runAsUser: 0 | ||||||
|  |           readOnlyRootFilesystem: true | ||||||
|  |           allowPrivilegeEscalation: false | ||||||
|  |           capabilities: { add: ["SYS_PTRACE","DAC_READ_SEARCH"] } | ||||||
|  |         volumeMounts: | ||||||
|  |         - { name: proc, mountPath: /host/proc, readOnly: true } | ||||||
|  |         - { name: containerd-sock, mountPath: /run/containerd/containerd.sock, readOnly: true } | ||||||
|  |         - { name: containerd-state, mountPath: /var/lib/containerd, readOnly: true } | ||||||
|  |       volumes: | ||||||
|  |       - { name: proc, hostPath: { path: /proc } } | ||||||
|  |       - { name: containerd-sock, hostPath: { path: /run/containerd/containerd.sock } } | ||||||
|  |       - { name: containerd-state, hostPath: { path: /var/lib/containerd } } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **Webhook (snippet):** | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | apiVersion: admissionregistration.k8s.io/v1 | ||||||
|  | kind: ValidatingWebhookConfiguration | ||||||
|  | webhooks: | ||||||
|  | - name: gate.zastava.stella-ops.org | ||||||
|  |   admissionReviewVersions: ["v1"] | ||||||
|  |   sideEffects: None | ||||||
|  |   failurePolicy: Ignore   # or Fail | ||||||
|  |   rules: | ||||||
|  |   - operations: ["CREATE","UPDATE"] | ||||||
|  |     apiGroups: [""] | ||||||
|  |     apiVersions: ["v1"] | ||||||
|  |     resources: ["pods"] | ||||||
|  |   clientConfig: | ||||||
|  |     service: | ||||||
|  |       namespace: stellaops | ||||||
|  |       name: zastava-webhook | ||||||
|  |       path: /admit | ||||||
|  |     caBundle: <base64 CA> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 14) Implementation notes | ||||||
|  |  | ||||||
|  | * **Language**: Rust (observer) for low‑latency `/proc` parsing; Go/.NET viable too. Webhook can be .NET 10 for parity with backend. | ||||||
|  | * **CRI drivers**: pluggable (`containerd`, `cri-o`, `docker`). Prefer CRI over parsing logs. | ||||||
|  | * **Shell parser**: re‑use Scanner.EntryTrace grammar for consistent results (compile to WASM if observer is Rust/Go). | ||||||
|  | * **Hashing**: `BLAKE3` for speed locally, then convert to `sha256` (or compute `sha256` directly when budget allows). | ||||||
|  | * **Resilience**: never block container start; observer is **passive**; only webhook decides allow/deny. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 15) Roadmap | ||||||
|  |  | ||||||
|  | * **eBPF** option for syscall/library load tracing (kernel‑level, opt‑in). | ||||||
|  | * **Windows containers** support (ETW providers, loaded modules). | ||||||
|  | * **Network posture** checks: listening ports vs policy. | ||||||
|  | * **Live **used‑by‑entrypoint** synthesis**: send compact bitset diff to backend to tighten Usage view. | ||||||
|  | * **Admission dry‑run** dashboards (simulate block lists before enforcing). | ||||||
|  |  | ||||||
| @@ -33,15 +33,24 @@ Everything here is open‑source and versioned — when you check out a git ta | |||||||
|  |  | ||||||
| ### Reference & concepts | ### Reference & concepts | ||||||
| - **05 – [System Requirements Specification](05_SYSTEM_REQUIREMENTS_SPEC.md)** | - **05 – [System Requirements Specification](05_SYSTEM_REQUIREMENTS_SPEC.md)** | ||||||
| - **07 – [High‑Level Architecture](40_ARCHITECTURE_OVERVIEW.md)** | - **07 – [High‑Level Architecture](07_HIGH_LEVEL_ARCHITECTURE.md)** | ||||||
| - **08 – Module Specifications**   | - **08 – Module Architecture Dossiers**   | ||||||
|   - [README](08_MODULE_SPECIFICATIONS/README.md)   |   - [Scanner](ARCHITECTURE_SCANNER.md)   | ||||||
|   - [`backend_api.md`](08_MODULE_SPECIFICATIONS/backend_api.md)   |   - [Feedser](ARCHITECTURE_FEEDSER.md)   | ||||||
|   - [`zastava_scanner.md`](08_MODULE_SPECIFICATIONS/zastava_scanner.md)   |   - [Vexer](ARCHITECTURE_VEXER.md)   | ||||||
|   - [`registry_scanner.md`](08_MODULE_SPECIFICATIONS/registry_scanner.md)   |   - [Signer](ARCHITECTURE_SIGNER.md)   | ||||||
|   - [`nightly_scheduler.md`](08_MODULE_SPECIFICATIONS/nightly_scheduler.md) |   - [Attestor](ARCHITECTURE_ATTESTOR.md)   | ||||||
|  |   - [Authority](ARCHITECTURE_AUTHORITY.md)   | ||||||
|  |   - [CLI](ARCHITECTURE_CLI.md)   | ||||||
|  |   - [Web UI](ARCHITECTURE_UI.md)   | ||||||
|  |   - [Zastava Runtime](ARCHITECTURE_ZASTAVA.md)   | ||||||
|  |   - [Release & Operations](ARCHITECTURE_DEVOPS.md) | ||||||
| - **09 – [API & CLI Reference](09_API_CLI_REFERENCE.md)** | - **09 – [API & CLI Reference](09_API_CLI_REFERENCE.md)** | ||||||
| - **10 – [Plug‑in SDK Guide](10_PLUGIN_SDK_GUIDE.md)** | - **10 – [Plug‑in SDK Guide](10_PLUGIN_SDK_GUIDE.md)** | ||||||
|  | - **10 – [Feedser CLI Quickstart](10_FEEDSER_CLI_QUICKSTART.md)** | ||||||
|  | - **30 – [Vexer Connector Packaging Guide](dev/30_VEXER_CONNECTOR_GUIDE.md)** | ||||||
|  | - **30 – Developer Templates**   | ||||||
|  |   - [Vexer Connector Skeleton](dev/templates/vexer-connector/) | ||||||
| - **11 – [Authority Service](11_AUTHORITY.md)** | - **11 – [Authority Service](11_AUTHORITY.md)** | ||||||
| - **11 – [Data Schemas](11_DATA_SCHEMAS.md)** | - **11 – [Data Schemas](11_DATA_SCHEMAS.md)** | ||||||
| - **12 – [Performance Workbook](12_PERFORMANCE_WORKBOOK.md)** | - **12 – [Performance Workbook](12_PERFORMANCE_WORKBOOK.md)** | ||||||
| @@ -57,7 +66,7 @@ Everything here is open‑source and versioned — when you check out a git ta | |||||||
| - **21 – [Install Guide](21_INSTALL_GUIDE.md)** | - **21 – [Install Guide](21_INSTALL_GUIDE.md)** | ||||||
| - **22 – [CI/CD Recipes Library](ci/20_CI_RECIPES.md)** | - **22 – [CI/CD Recipes Library](ci/20_CI_RECIPES.md)** | ||||||
| - **23 – [FAQ](23_FAQ_MATRIX.md)** | - **23 – [FAQ](23_FAQ_MATRIX.md)** | ||||||
| - **24 – [Offline Update Kit Admin Guide](24_OUK_ADMIN_GUIDE.md)** | - **24 – [Offline Update Kit Admin Guide](24_OFFLINE_KIT.md)** | ||||||
| - **25 – [Feedser Apple Connector Operations](ops/feedser-apple-operations.md)** | - **25 – [Feedser Apple Connector Operations](ops/feedser-apple-operations.md)** | ||||||
| - **26 – [Authority Key Rotation Playbook](ops/authority-key-rotation.md)** | - **26 – [Authority Key Rotation Playbook](ops/authority-key-rotation.md)** | ||||||
| - **27 – [Feedser CCCS Connector Operations](ops/feedser-cccs-operations.md)** | - **27 – [Feedser CCCS Connector Operations](ops/feedser-cccs-operations.md)** | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| | ID | Status | Owner(s) | Depends on | Description | Exit Criteria | | | ID | Status | Owner(s) | Depends on | Description | Exit Criteria | | ||||||
| |----|--------|----------|------------|-------------|---------------| | |----|--------|----------|------------|-------------|---------------| | ||||||
|  | | DOC7.README-INDEX | DONE (2025-10-17) | Docs Guild | — | Refresh index docs (docs/README.md + root README) after architecture dossier split and Offline Kit overhaul. | ✅ ToC reflects new component architecture docs; ✅ root README highlights updated doc set; ✅ Offline Kit guide linked correctly. | | ||||||
| | DOC4.AUTH-PDG | REVIEW | Docs Guild, Plugin Team | PLG6.DOC | Copy-edit `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, export lifecycle diagram, add LDAP RFC cross-link. | ✅ PR merged with polish; ✅ Diagram committed; ✅ Slack handoff posted. | | | DOC4.AUTH-PDG | REVIEW | Docs Guild, Plugin Team | PLG6.DOC | Copy-edit `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`, export lifecycle diagram, add LDAP RFC cross-link. | ✅ PR merged with polish; ✅ Diagram committed; ✅ Slack handoff posted. | | ||||||
| | DOC1.AUTH | DONE (2025-10-12) | Docs Guild, Authority Core | CORE5B.DOC | Draft `docs/11_AUTHORITY.md` covering architecture, configuration, bootstrap flows. | ✅ Architecture + config sections approved by Core; ✅ Samples reference latest options; ✅ Offline note added. | | | DOC1.AUTH | DONE (2025-10-12) | Docs Guild, Authority Core | CORE5B.DOC | Draft `docs/11_AUTHORITY.md` covering architecture, configuration, bootstrap flows. | ✅ Architecture + config sections approved by Core; ✅ Samples reference latest options; ✅ Offline note added. | | ||||||
| | DOC3.Feedser-Authority | DONE (2025-10-12) | Docs Guild, DevEx | FSR4 | Polish operator/runbook sections (DOC3/DOC5) to document Feedser authority rollout, bypass logging, and enforcement checklist. | ✅ DOC3/DOC5 updated with audit runbook references; ✅ enforcement deadline highlighted; ✅ Docs guild sign-off. | | | DOC3.Feedser-Authority | DONE (2025-10-12) | Docs Guild, DevEx | FSR4 | Polish operator/runbook sections (DOC3/DOC5) to document Feedser authority rollout, bypass logging, and enforcement checklist. | ✅ DOC3/DOC5 updated with audit runbook references; ✅ enforcement deadline highlighted; ✅ Docs guild sign-off. | | ||||||
|   | |||||||
							
								
								
									
										220
									
								
								docs/dev/30_VEXER_CONNECTOR_GUIDE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								docs/dev/30_VEXER_CONNECTOR_GUIDE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,220 @@ | |||||||
|  | # Vexer Connector Packaging Guide | ||||||
|  |  | ||||||
|  | > **Audience:** teams implementing new Vexer provider plug‑ins (CSAF feeds, | ||||||
|  | > OpenVEX attestations, etc.)   | ||||||
|  | > **Prerequisites:** read `docs/ARCHITECTURE_VEXER.md` and the module | ||||||
|  | > `AGENTS.md` in `src/StellaOps.Vexer.Connectors.Abstractions/`. | ||||||
|  |  | ||||||
|  | The Vexer connector SDK gives you: | ||||||
|  |  | ||||||
|  | - `VexConnectorBase` – deterministic logging, SHA‑256 helpers, time provider. | ||||||
|  | - `VexConnectorOptionsBinder` – strongly typed YAML/JSON configuration binding. | ||||||
|  | - `IVexConnectorOptionsValidator<T>` – custom validation hooks (offline defaults, auth invariants). | ||||||
|  | - `VexConnectorDescriptor` & metadata helpers for consistent telemetry. | ||||||
|  |  | ||||||
|  | This guide explains how to package a connector so the Vexer Worker/WebService | ||||||
|  | can load it via the plugin host. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 1. Project layout | ||||||
|  |  | ||||||
|  | Start from the template under | ||||||
|  | `docs/dev/templates/vexer-connector/`. It contains: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | Vexer.MyConnector/ | ||||||
|  | ├── src/ | ||||||
|  | │   ├── Vexer.MyConnector.csproj | ||||||
|  | │   ├── MyConnectorOptions.cs | ||||||
|  | │   ├── MyConnector.cs | ||||||
|  | │   └── MyConnectorPlugin.cs | ||||||
|  | └── manifest/ | ||||||
|  |     └── connector.manifest.yaml | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Key points: | ||||||
|  |  | ||||||
|  | - Target `net10.0`, enable `TreatWarningsAsErrors`, reference the | ||||||
|  |   `StellaOps.Vexer.Connectors.Abstractions` project (or NuGet once published). | ||||||
|  | - Keep project ID prefix `StellaOps.Vexer.Connectors.<Provider>` so the | ||||||
|  |   plugin loader can discover it with the default search pattern. | ||||||
|  |  | ||||||
|  | ### 1.1 csproj snippet | ||||||
|  |  | ||||||
|  | ```xml | ||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>net10.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|  |     <ImplicitUsings>enable</ImplicitUsings> | ||||||
|  |     <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||||
|  |   </PropertyGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\..\src\StellaOps.Vexer.Connectors.Abstractions\StellaOps.Vexer.Connectors.Abstractions.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Adjust the `ProjectReference` for your checkout (or switch to a NuGet package | ||||||
|  | once published). | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 2. Implement the connector | ||||||
|  |  | ||||||
|  | 1. **Options model** – create an options POCO with data-annotation attributes. | ||||||
|  |    Bind it via `VexConnectorOptionsBinder.Bind<TOptions>` in your connector | ||||||
|  |    constructor or `ValidateAsync`. | ||||||
|  | 2. **Validator** – implement `IVexConnectorOptionsValidator<TOptions>` to add | ||||||
|  |    complex checks (e.g., ensure both `clientId` and `clientSecret` are present). | ||||||
|  | 3. **Connector** – inherit from `VexConnectorBase`. Implement: | ||||||
|  |    - `ValidateAsync` – run binder/validators, log configuration summary. | ||||||
|  |    - `FetchAsync` – stream raw documents to `context.RawSink`. | ||||||
|  |    - `NormalizeAsync` – convert raw documents into `VexClaimBatch` via | ||||||
|  |      format-specific normalizers (`context.Normalizers`). | ||||||
|  | 4. **Plugin adapter** – expose the connector via a plugin entry point so the | ||||||
|  |    host can instantiate it. | ||||||
|  |  | ||||||
|  | ### 2.1 Options binding example | ||||||
|  |  | ||||||
|  | ```csharp | ||||||
|  | public sealed class MyConnectorOptions | ||||||
|  | { | ||||||
|  |     [Required] | ||||||
|  |     [Url] | ||||||
|  |     public string CatalogUri { get; set; } = default!; | ||||||
|  |  | ||||||
|  |     [Required] | ||||||
|  |     public string ApiKey { get; set; } = default!; | ||||||
|  |  | ||||||
|  |     [Range(1, 64)] | ||||||
|  |     public int MaxParallelRequests { get; set; } = 4; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | public sealed class MyConnectorOptionsValidator : IVexConnectorOptionsValidator<MyConnectorOptions> | ||||||
|  | { | ||||||
|  |     public void Validate(VexConnectorDescriptor descriptor, MyConnectorOptions options, IList<string> errors) | ||||||
|  |     { | ||||||
|  |         if (!options.CatalogUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |         { | ||||||
|  |             errors.Add("CatalogUri must use HTTPS."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Bind inside the connector: | ||||||
|  |  | ||||||
|  | ```csharp | ||||||
|  | private readonly MyConnectorOptions _options; | ||||||
|  |  | ||||||
|  | public MyConnector(VexConnectorDescriptor descriptor, ILogger<MyConnector> logger, TimeProvider timeProvider) | ||||||
|  |     : base(descriptor, logger, timeProvider) | ||||||
|  | { | ||||||
|  |     // `settings` comes from the orchestrator; validators registered via DI. | ||||||
|  |     _options = VexConnectorOptionsBinder.Bind<MyConnectorOptions>( | ||||||
|  |         descriptor, | ||||||
|  |         VexConnectorSettings.Empty, | ||||||
|  |         validators: new[] { new MyConnectorOptionsValidator() }); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Replace `VexConnectorSettings.Empty` with the actual settings from context | ||||||
|  | inside `ValidateAsync`. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 3. Plugin adapter & manifest | ||||||
|  |  | ||||||
|  | Create a simple plugin class that implements | ||||||
|  | `StellaOps.Plugin.IConnectorPlugin`. The Worker/WebService plugin host uses | ||||||
|  | this contract today. | ||||||
|  |  | ||||||
|  | ```csharp | ||||||
|  | public sealed class MyConnectorPlugin : IConnectorPlugin | ||||||
|  | { | ||||||
|  |     private static readonly VexConnectorDescriptor Descriptor = | ||||||
|  |         new("vexer:my-provider", VexProviderKind.Vendor, "My Provider VEX"); | ||||||
|  |  | ||||||
|  |     public string Name => Descriptor.DisplayName; | ||||||
|  |  | ||||||
|  |     public bool IsAvailable(IServiceProvider services) => true; // inject feature flags if needed | ||||||
|  |  | ||||||
|  |     public IFeedConnector Create(IServiceProvider services) | ||||||
|  |     { | ||||||
|  |         var logger = services.GetRequiredService<ILogger<MyConnector>>(); | ||||||
|  |         var timeProvider = services.GetRequiredService<TimeProvider>(); | ||||||
|  |         return new MyConnector(Descriptor, logger, timeProvider); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | > **Note:** the Vexer Worker currently instantiates connectors through the | ||||||
|  | > shared `IConnectorPlugin` contract. Once a dedicated Vexer plugin interface | ||||||
|  | > lands you simply swap the base interface; the descriptor/connector code | ||||||
|  | > remains unchanged. | ||||||
|  |  | ||||||
|  | Provide a manifest describing the assembly for operational tooling: | ||||||
|  |  | ||||||
|  | ```yaml | ||||||
|  | # manifest/connector.manifest.yaml | ||||||
|  | id: vexer-my-provider | ||||||
|  | assembly: StellaOps.Vexer.Connectors.MyProvider.dll | ||||||
|  | entryPoint: StellaOps.Vexer.Connectors.MyProvider.MyConnectorPlugin | ||||||
|  | description: > | ||||||
|  |   Official VEX feed for ExampleCorp products (CSAF JSON, daily updates). | ||||||
|  | tags: | ||||||
|  |   - vexer | ||||||
|  |   - csaf | ||||||
|  |   - vendor | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Store manifests under `/opt/stella/vexer/plugins/<connector>/manifest/` in | ||||||
|  | production so the deployment tooling can inventory and verify plug‑ins. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 4. Packaging workflow | ||||||
|  |  | ||||||
|  | 1. `dotnet publish -c Release` → copy the published DLLs to | ||||||
|  |    `/opt/stella/vexer/plugins/<Provider>/`. | ||||||
|  | 2. Place `connector.manifest.yaml` next to the binaries. | ||||||
|  | 3. Restart the Vexer Worker or WebService (hot reload not supported yet). | ||||||
|  | 4. Verify logs: `VEX-ConnectorLoader` should list the connector descriptor. | ||||||
|  |  | ||||||
|  | ### 4.1 Offline kits | ||||||
|  |  | ||||||
|  | - Add the connector folder (binaries + manifest) to the Offline Kit bundle. | ||||||
|  | - Include a `settings.sample.yaml` demonstrating offline-friendly defaults. | ||||||
|  | - Document any external dependencies (e.g., SHA mirrors) in the manifest `notes` | ||||||
|  |   field. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 5. Testing checklist | ||||||
|  |  | ||||||
|  | - Unit tests around options binding & validators. | ||||||
|  | - Integration tests (future `StellaOps.Vexer.Connectors.Abstractions.Tests`) | ||||||
|  |   verifying deterministic logging scopes: | ||||||
|  |   `logger.BeginScope` should produce `vex.connector.id`, `vex.connector.kind`, | ||||||
|  |   and `vex.connector.operation`. | ||||||
|  | - Deterministic SHA tests: repeated `CreateRawDocument` calls with identical | ||||||
|  |   content must return the same digest. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | ## 6. Reference template | ||||||
|  |  | ||||||
|  | See `docs/dev/templates/vexer-connector/` for the full quick‑start including: | ||||||
|  |  | ||||||
|  | - Sample options class + validator. | ||||||
|  | - Connector implementation inheriting from `VexConnectorBase`. | ||||||
|  | - Plugin adapter + manifest. | ||||||
|  |  | ||||||
|  | Copy the directory, rename namespaces/IDs, then iterate on provider-specific | ||||||
|  | logic. | ||||||
|  |  | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | *Last updated: 2025-10-17* | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | id: vexer-my-provider | ||||||
|  | assembly: StellaOps.Vexer.Connectors.MyProvider.dll | ||||||
|  | entryPoint: StellaOps.Vexer.Connectors.MyProvider.MyConnectorPlugin | ||||||
|  | description: | | ||||||
|  |   Example connector template. Replace metadata before shipping. | ||||||
|  | tags: | ||||||
|  |   - vexer | ||||||
|  |   - template | ||||||
							
								
								
									
										72
									
								
								docs/dev/templates/vexer-connector/src/MyConnector.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								docs/dev/templates/vexer-connector/src/MyConnector.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Collections.Immutable; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | using StellaOps.Vexer.Connectors.Abstractions; | ||||||
|  | using StellaOps.Vexer.Core; | ||||||
|  |  | ||||||
|  | namespace StellaOps.Vexer.Connectors.MyProvider; | ||||||
|  |  | ||||||
|  | public sealed class MyConnector : VexConnectorBase | ||||||
|  | { | ||||||
|  |     private readonly IEnumerable<IVexConnectorOptionsValidator<MyConnectorOptions>> _validators; | ||||||
|  |     private MyConnectorOptions? _options; | ||||||
|  |  | ||||||
|  |     public MyConnector(VexConnectorDescriptor descriptor, ILogger<MyConnector> logger, TimeProvider timeProvider, IEnumerable<IVexConnectorOptionsValidator<MyConnectorOptions>> validators) | ||||||
|  |         : base(descriptor, logger, timeProvider) | ||||||
|  |     { | ||||||
|  |         _validators = validators; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override ValueTask ValidateAsync(VexConnectorSettings settings, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         _options = VexConnectorOptionsBinder.Bind( | ||||||
|  |             Descriptor, | ||||||
|  |             settings, | ||||||
|  |             validators: _validators); | ||||||
|  |  | ||||||
|  |         LogConnectorEvent(LogLevel.Information, "validate", "MyConnector configuration loaded.", | ||||||
|  |             new Dictionary<string, object?> | ||||||
|  |             { | ||||||
|  |                 ["catalogUri"] = _options.CatalogUri, | ||||||
|  |                 ["maxParallelRequests"] = _options.MaxParallelRequests, | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |         return ValueTask.CompletedTask; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override IAsyncEnumerable<VexRawDocument> FetchAsync(VexConnectorContext context, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         if (_options is null) | ||||||
|  |         { | ||||||
|  |             throw new InvalidOperationException("Connector not validated."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return FetchInternalAsync(context, cancellationToken); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async IAsyncEnumerable<VexRawDocument> FetchInternalAsync(VexConnectorContext context, [EnumeratorCancellation] CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         LogConnectorEvent(LogLevel.Information, "fetch", "Fetching catalog window..."); | ||||||
|  |  | ||||||
|  |         // Replace with real HTTP logic. | ||||||
|  |         await Task.Delay(10, cancellationToken); | ||||||
|  |  | ||||||
|  |         var metadata = BuildMetadata(builder => builder | ||||||
|  |             .Add("sourceUri", _options!.CatalogUri) | ||||||
|  |             .Add("window", context.Since?.ToString("O") ?? "full")); | ||||||
|  |  | ||||||
|  |         yield return CreateRawDocument( | ||||||
|  |             VexDocumentFormat.CsafJson, | ||||||
|  |             new Uri($"{_options.CatalogUri.TrimEnd('/')}/sample.json"), | ||||||
|  |             new byte[] { 0x7B, 0x7D }, | ||||||
|  |             metadata); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override ValueTask<VexClaimBatch> NormalizeAsync(VexRawDocument document, CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         var claims = ImmutableArray<VexClaim>.Empty; | ||||||
|  |         var diagnostics = ImmutableDictionary<string, string>.Empty; | ||||||
|  |         return ValueTask.FromResult(new VexClaimBatch(document, claims, diagnostics)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								docs/dev/templates/vexer-connector/src/MyConnectorOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								docs/dev/templates/vexer-connector/src/MyConnectorOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  |  | ||||||
|  | namespace StellaOps.Vexer.Connectors.MyProvider; | ||||||
|  |  | ||||||
|  | public sealed class MyConnectorOptions | ||||||
|  | { | ||||||
|  |     [Required] | ||||||
|  |     [Url] | ||||||
|  |     public string CatalogUri { get; set; } = default!; | ||||||
|  |  | ||||||
|  |     [Required] | ||||||
|  |     public string ApiKey { get; set; } = default!; | ||||||
|  |  | ||||||
|  |     [Range(1, 32)] | ||||||
|  |     public int MaxParallelRequests { get; set; } = 4; | ||||||
|  | } | ||||||
| @@ -0,0 +1,15 @@ | |||||||
|  | using System.Collections.Generic; | ||||||
|  | using StellaOps.Vexer.Connectors.Abstractions; | ||||||
|  |  | ||||||
|  | namespace StellaOps.Vexer.Connectors.MyProvider; | ||||||
|  |  | ||||||
|  | public sealed class MyConnectorOptionsValidator : IVexConnectorOptionsValidator<MyConnectorOptions> | ||||||
|  | { | ||||||
|  |     public void Validate(VexConnectorDescriptor descriptor, MyConnectorOptions options, IList<string> errors) | ||||||
|  |     { | ||||||
|  |         if (!options.CatalogUri.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) | ||||||
|  |         { | ||||||
|  |             errors.Add("CatalogUri must use HTTPS."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								docs/dev/templates/vexer-connector/src/MyConnectorPlugin.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								docs/dev/templates/vexer-connector/src/MyConnectorPlugin.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.Logging; | ||||||
|  | using StellaOps.Plugin; | ||||||
|  | using StellaOps.Vexer.Connectors.Abstractions; | ||||||
|  | using StellaOps.Vexer.Core; | ||||||
|  |  | ||||||
|  | namespace StellaOps.Vexer.Connectors.MyProvider; | ||||||
|  |  | ||||||
|  | public sealed class MyConnectorPlugin : IConnectorPlugin | ||||||
|  | { | ||||||
|  |     private static readonly VexConnectorDescriptor Descriptor = new( | ||||||
|  |         id: "vexer:my-provider", | ||||||
|  |         kind: VexProviderKind.Vendor, | ||||||
|  |         displayName: "My Provider VEX"); | ||||||
|  |  | ||||||
|  |     public string Name => Descriptor.DisplayName; | ||||||
|  |  | ||||||
|  |     public bool IsAvailable(IServiceProvider services) => true; | ||||||
|  |  | ||||||
|  |     public IFeedConnector Create(IServiceProvider services) | ||||||
|  |     { | ||||||
|  |         var logger = services.GetRequiredService<ILogger<MyConnector>>(); | ||||||
|  |         var timeProvider = services.GetRequiredService<TimeProvider>(); | ||||||
|  |         var validators = services.GetServices<IVexConnectorOptionsValidator<MyConnectorOptions>>(); | ||||||
|  |         return new MyConnector(Descriptor, logger, timeProvider, validators); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | <Project Sdk="Microsoft.NET.Sdk"> | ||||||
|  |   <PropertyGroup> | ||||||
|  |     <TargetFramework>net10.0</TargetFramework> | ||||||
|  |     <Nullable>enable</Nullable> | ||||||
|  |     <ImplicitUsings>enable</ImplicitUsings> | ||||||
|  |     <TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||||||
|  |   </PropertyGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <!-- Adjust the relative path when copying this template into a repo --> | ||||||
|  |     <ProjectReference Include="..\..\..\..\src\StellaOps.Vexer.Connectors.Abstractions\StellaOps.Vexer.Connectors.Abstractions.csproj" /> | ||||||
|  |   </ItemGroup> | ||||||
|  | </Project> | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| # Feedser CERT-Bund Connector Operations | # Feedser CERT-Bund Connector Operations | ||||||
|  |  | ||||||
| _Last updated: 2025-10-15_ | _Last updated: 2025-10-17_ | ||||||
|  |  | ||||||
| Germany’s Federal Office for Information Security (BSI) operates the Warn- und Informationsdienst (WID) portal. The Feedser CERT-Bund connector (`source:cert-bund:*`) ingests the public RSS feed, hydrates the portal’s JSON detail endpoint, and maps the result into canonical advisories while preserving the original German content. | Germany’s Federal Office for Information Security (BSI) operates the Warn- und Informationsdienst (WID) portal. The Feedser CERT-Bund connector (`source:cert-bund:*`) ingests the public RSS feed, hydrates the portal’s JSON detail endpoint, and maps the result into canonical advisories while preserving the original German content. | ||||||
|  |  | ||||||
| @@ -96,18 +96,30 @@ curl -s -b cookies.txt \ | |||||||
|  |  | ||||||
| Iterate `page` until the response `content` array is empty. Pages 0–9 currently cover 2014→present. Persist JSON responses (plus SHA256) for Offline Kit parity. | Iterate `page` until the response `content` array is empty. Pages 0–9 currently cover 2014→present. Persist JSON responses (plus SHA256) for Offline Kit parity. | ||||||
|  |  | ||||||
|  | > **Shortcut** – run `python tools/certbund_offline_snapshot.py --output seed-data/cert-bund` | ||||||
|  | > to bootstrap the session, capture the paginated search responses, and regenerate | ||||||
|  | > the manifest/checksum files automatically. Supply `--cookie-file` and `--xsrf-token` | ||||||
|  | > if the portal requires a browser-derived session (see options via `--help`). | ||||||
|  |  | ||||||
| ### 3.3 Export bundles | ### 3.3 Export bundles | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -s -b cookies.txt \ | python tools/certbund_offline_snapshot.py \ | ||||||
|      -H "Accept: application/json" \ |   --output seed-data/cert-bund \ | ||||||
|      -H "X-XSRF-TOKEN: ${XSRF}" \ |   --start-year 2014 \ | ||||||
|      "https://wid.cert-bund.de/portal/api/securityadvisory/export?format=json&from=2020-01-01" \ |   --end-year "$(date -u +%Y)" | ||||||
|      > certbund-2020-2025.json |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Split long ranges per year and record provenance (`from`, `to`, SHA, capturedAt). Feedser can ingest these JSON payloads directly when operating offline. | The helper stores yearly exports under `seed-data/cert-bund/export/`, | ||||||
| Task `FEEDCONN-CERTBUND-02-009` tracks turning this workflow into a shipped Offline Kit artefact with manifests and documentation updates—coordinate with the Docs guild before publishing. | captures paginated search snapshots in `seed-data/cert-bund/search/`, | ||||||
|  | and generates the manifest + SHA files in `seed-data/cert-bund/manifest/`. | ||||||
|  | Split ranges according to your compliance window (default: one file per | ||||||
|  | calendar year). Feedser can ingest these JSON payloads directly when | ||||||
|  | operating offline. | ||||||
|  |  | ||||||
|  | > When automatic bootstrap fails (e.g. portal introduces CAPTCHA), run the | ||||||
|  | > manual `curl` flow above, then rerun the helper with `--skip-fetch` to | ||||||
|  | > rebuild the manifest from the existing files. | ||||||
|  |  | ||||||
| ### 3.4 Connector-driven catch-up | ### 3.4 Connector-driven catch-up | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user