Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created project for StellaOps.Scanner.Analyzers.Native.Tests with necessary dependencies. - Documented roles and guidelines in AGENTS.md for Scheduler module. - Implemented IResolverJobService interface and InMemoryResolverJobService for handling resolver jobs. - Added ResolverBacklogNotifier and ResolverBacklogService for monitoring job metrics. - Developed API endpoints for managing resolver jobs and retrieving metrics. - Defined models for resolver job requests and responses. - Integrated dependency injection for resolver job services. - Implemented ImpactIndexSnapshot for persisting impact index data. - Introduced SignalsScoringOptions for configurable scoring weights in reachability scoring. - Added unit tests for ReachabilityScoringService and RuntimeFactsIngestionService. - Created dotnet-filter.sh script to handle command-line arguments for dotnet. - Established nuget-prime project for managing package downloads.
273 lines
9.2 KiB
Markdown
273 lines
9.2 KiB
Markdown
# Inline DSSE Provenance
|
||
|
||
> **Status:** Draft – aligns with the November 2025 advisory “store DSSE attestation refs inline on every SBOM/VEX event node.”
|
||
> **Owners:** Authority Guild · Feedser Guild · Platform Guild · Docs Guild.
|
||
|
||
This document defines how Stella Ops records provenance for SBOM, VEX, scan, and derived events: every event node in the Mongo event graph includes DSSE + Rekor references and verification metadata so audits and replay become first-class queries.
|
||
|
||
---
|
||
|
||
## 1. Event patch (Mongo schema)
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "evt_...",
|
||
"kind": "SBOM|VEX|SCAN|INGEST|DERIVED",
|
||
"subject": {
|
||
"purl": "pkg:nuget/example@1.2.3",
|
||
"digest": { "sha256": "..." },
|
||
"version": "1.2.3"
|
||
},
|
||
"provenance": {
|
||
"dsse": {
|
||
"envelopeDigest": "sha256:...",
|
||
"payloadType": "application/vnd.in-toto+json",
|
||
"key": {
|
||
"keyId": "cosign:SHA256-PKIX:ABC...",
|
||
"issuer": "fulcio",
|
||
"algo": "ECDSA"
|
||
},
|
||
"rekor": {
|
||
"logIndex": 1234567,
|
||
"uuid": "b3f0...",
|
||
"integratedTime": 1731081600,
|
||
"mirrorSeq": 987654 // optional
|
||
},
|
||
"chain": [
|
||
{ "type": "build", "id": "att:build#...", "digest": "sha256:..." },
|
||
{ "type": "sbom", "id": "att:sbom#...", "digest": "sha256:..." }
|
||
]
|
||
}
|
||
},
|
||
"trust": {
|
||
"verified": true,
|
||
"verifier": "Authority@stella",
|
||
"witnesses": 1,
|
||
"policyScore": 0.92
|
||
},
|
||
"ts": "2025-11-11T12:00:00Z"
|
||
}
|
||
```
|
||
|
||
### Key fields
|
||
|
||
| Field | Description |
|
||
|-------|-------------|
|
||
| `provenance.dsse.envelopeDigest` | SHA-256 of the DSSE envelope (not payload). |
|
||
| `provenance.dsse.payloadType` | Usually `application/vnd.in-toto+json`. |
|
||
| `provenance.dsse.key` | Key fingerprint / issuer / algorithm. |
|
||
| `provenance.dsse.rekor` | Rekor transparency log metadata (index, UUID, integrated time). |
|
||
| `provenance.dsse.chain` | Optional chain of dependent attestations (build → sbom → scan). |
|
||
| `trust.*` | Result of local verification (DSSE signature, Rekor proof, policy). |
|
||
|
||
---
|
||
|
||
## 2. Write path (ingest flow)
|
||
|
||
1. **Obtain provenance metadata** for each attested artifact (build, SBOM, VEX, scan). The CI script (`scripts/publish_attestation_with_provenance.sh`) captures `envelopeDigest`, Rekor `logIndex`/`uuid`, and key info.
|
||
2. **Authority/Feedser** verify the DSSE + Rekor proof (local cosign/rekor libs or the Signer service) and set `trust.verified = true`, `trust.verifier = "Authority@stella"`, `trust.witnesses = 1`.
|
||
3. **Attach** the provenance block before appending the event to Mongo, using `StellaOps.Provenance.Mongo` helpers.
|
||
4. **Backfill** historical events by resolving known subjects → attestation digests and running an update script.
|
||
|
||
### 2.1 Supplying metadata from Concelier statements
|
||
|
||
Concelier ingestion jobs can now inline provenance when they create advisory statements. Add an `AdvisoryProvenance` entry with `kind = "dsse"` (or `dsse-metadata` / `attestation-dsse`) and set `value` to the same JSON emitted by the CI snippet. `AdvisoryEventLog` and `AdvisoryMergeService` automatically parse that entry, hydrate `AdvisoryStatementInput.Provenance/Trust`, and persist the metadata alongside the statement.
|
||
|
||
```json
|
||
{
|
||
"source": "attestor",
|
||
"kind": "dsse",
|
||
"value": "{ \"dsse\": { \"envelopeDigest\": \"sha256:…\", \"payloadType\": \"application/vnd.in-toto+json\" }, \"trust\": { \"verified\": true, \"verifier\": \"Authority@stella\" } }",
|
||
"recordedAt": "2025-11-10T00:00:00Z"
|
||
}
|
||
```
|
||
|
||
Providing the metadata during ingestion keeps new statements self-contained and reduces the surface that the `/events/statements/{statementId}/provenance` endpoint needs to backfill later.
|
||
|
||
Reference helper: `src/__Libraries/StellaOps.Provenance.Mongo/ProvenanceMongoExtensions.cs`.
|
||
|
||
---
|
||
|
||
### 2.2 Advisory AI structured chunk schema (GHSA/Cisco parity)
|
||
|
||
Advisory AI consumes the canonical `Advisory` aggregate and emits structured chunks that mirror GHSA GraphQL and Cisco PSIRT provenance anchors. The response contract is:
|
||
|
||
```jsonc
|
||
{
|
||
"advisoryKey": "CVE-2025-0001",
|
||
"fingerprint": "<sha256 of canonical advisory>",
|
||
"total": 3,
|
||
"truncated": false,
|
||
"entries": [
|
||
{
|
||
"type": "workaround", // sorted by (type, observationPath, documentId)
|
||
"chunkId": "c0ffee12", // sha256(advisory.observationId + observationPath)[:16]
|
||
"content": { /* structured field */ },
|
||
"provenance": {
|
||
"documentId": "tenant-a:chunk:newest", // Mongo _id of backing observation
|
||
"observationPath": "/references/0", // JSON Pointer into the observation
|
||
"source": "nvd",
|
||
"kind": "workaround",
|
||
"value": "tenant-a:chunk:newest",
|
||
"recordedAt": "2025-01-07T00:00:00Z",
|
||
"fieldMask": ["/references/0"]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Determinism requirements:
|
||
|
||
- Order entries by `(type, observationPath, documentId)` to keep cache keys stable across nodes.
|
||
- Always include the advisory `fingerprint` in cache keys and responses.
|
||
- Preserve observation-level provenance by emitting both `documentId` and `observationPath` under `provenance`.
|
||
|
||
These anchors let Attestor/Console deep-link evidence and allow offline mirrors to prove origin without merging transforms.
|
||
|
||
---
|
||
|
||
## 3. CI/CD snippet
|
||
|
||
See `scripts/publish_attestation_with_provenance.sh`:
|
||
|
||
```bash
|
||
rekor-cli upload --rekor_server "$REKOR_URL" \
|
||
--artifact "$ATTEST_PATH" --type dsse --format json > rekor-upload.json
|
||
LOG_INDEX=$(jq '.LogIndex' rekor-upload.json)
|
||
UUID=$(jq -r '.UUID' rekor-upload.json)
|
||
ENVELOPE_SHA256=$(sha256sum "$ATTEST_PATH" | awk '{print $1}')
|
||
cat > provenance-meta.json <<EOF
|
||
{
|
||
"subject": { "imageRef": "$IMAGE_REF", "digest": { "sha256": "$IMAGE_DIGEST" } },
|
||
"dsse": {
|
||
"envelopeDigest": "sha256:$ENVELOPE_SHA256",
|
||
"payloadType": "application/vnd.in-toto+json",
|
||
"key": { "keyId": "$KEY_ID", "issuer": "$KEY_ISSUER", "algo": "$KEY_ALGO" },
|
||
"rekor": { "logIndex": $LOG_INDEX, "uuid": "$UUID", "integratedTime": $(jq '.IntegratedTime' rekor-upload.json) }
|
||
}
|
||
}
|
||
EOF
|
||
```
|
||
|
||
Feedser ingests this JSON and maps it to `DsseProvenance` + `TrustInfo`.
|
||
|
||
---
|
||
|
||
## 4. Mongo indexes
|
||
|
||
Create indexes to keep provenance queries fast (`mongosh`):
|
||
|
||
```javascript
|
||
db.events.createIndex(
|
||
{ "subject.digest.sha256": 1, "kind": 1, "provenance.dsse.rekor.logIndex": 1 },
|
||
{ name: "events_by_subject_kind_provenance" }
|
||
);
|
||
|
||
db.events.createIndex(
|
||
{ "kind": 1, "trust.verified": 1, "provenance.dsse.rekor.logIndex": 1 },
|
||
{ name: "events_unproven_by_kind" }
|
||
);
|
||
|
||
db.events.createIndex(
|
||
{ "provenance.dsse.rekor.logIndex": 1 },
|
||
{ name: "events_by_rekor_logindex" }
|
||
);
|
||
```
|
||
|
||
Corresponding C# helper: `MongoIndexes.EnsureEventIndexesAsync`.
|
||
|
||
---
|
||
|
||
## 5. Query recipes
|
||
|
||
* **All proven VEX for an image digest:**
|
||
|
||
```javascript
|
||
db.events.find({
|
||
kind: "VEX",
|
||
"subject.digest.sha256": "<digest>",
|
||
"provenance.dsse.rekor.logIndex": { $exists: true },
|
||
"trust.verified": true
|
||
})
|
||
```
|
||
|
||
* **Compliance gap (unverified data used for decisions):**
|
||
|
||
```javascript
|
||
db.events.aggregate([
|
||
{ $match: { kind: { $in: ["VEX","SBOM","SCAN"] } } },
|
||
{ $match: {
|
||
$or: [
|
||
{ "trust.verified": { $ne: true } },
|
||
{ "provenance.dsse.rekor.logIndex": { $exists: false } }
|
||
]
|
||
}
|
||
},
|
||
{ $group: { _id: "$kind", count: { $sum: 1 } } }
|
||
])
|
||
```
|
||
|
||
* **Replay slice:** filter for events where `provenance.dsse.chain` covers build → sbom → scan and export referenced attestation digests.
|
||
|
||
---
|
||
|
||
## 6. Policy gates
|
||
|
||
Examples:
|
||
|
||
```yaml
|
||
rules:
|
||
- id: GATE-PROVEN-VEX
|
||
when:
|
||
all:
|
||
- kind: "VEX"
|
||
- trust.verified: true
|
||
- key.keyId in VendorAllowlist
|
||
- rekor.integratedTime <= releaseFreeze
|
||
then:
|
||
decision: ALLOW
|
||
|
||
- id: BLOCK-UNPROVEN
|
||
when:
|
||
any:
|
||
- trust.verified != true
|
||
- provenance.dsse.rekor.logIndex missing
|
||
then:
|
||
decision: FAIL
|
||
reason: "Unproven evidence influences decision; require Rekor-backed attestation."
|
||
```
|
||
|
||
---
|
||
|
||
## 7. UI nudges
|
||
|
||
* **Provenance chip** on findings/events: `Verified • Rekor#1234567 • KeyID:cosign:...` (click → inclusion proof & DSSE preview).
|
||
* Facet filter: `Provenance = Verified / Missing / Key-Policy-Mismatch`.
|
||
|
||
---
|
||
|
||
## 8. Implementation tasks
|
||
|
||
| Task ID | Scope |
|
||
|---------|-------|
|
||
| `PROV-INLINE-401-028` | Extend Authority/Feedser write-paths to attach `provenance.dsse` + `trust` blocks using `StellaOps.Provenance.Mongo`. |
|
||
| `PROV-BACKFILL-401-029` | Backfill historical events with DSSE/Rekor refs based on existing attestation digests. |
|
||
| `PROV-INDEX-401-030` | Create Mongo indexes and expose helper queries for audits. |
|
||
|
||
Keep this document updated when new attestation types or mirror/witness policies land.
|
||
|
||
---
|
||
|
||
## 9. Feedser API for provenance updates
|
||
|
||
Feedser exposes a lightweight endpoint for attaching provenance after an event is recorded:
|
||
|
||
```
|
||
POST /events/statements/{statementId}/provenance
|
||
Headers: X-Stella-Tenant, Authorization (if Authority is enabled)
|
||
Body: { "dsse": { ... }, "trust": { ... } }
|
||
```
|
||
|
||
The body matches the JSON emitted by `publish_attestation_with_provenance.sh`. Feedser validates the payload, ensures `trust.verified = true`, and then calls `AttachStatementProvenanceAsync` so the DSSE metadata lands inline on the target statement. Clients receive HTTP 202 on success, 400 on malformed input, and 404 if the statement id is unknown.
|