Files
git.stella-ops.org/docs/provenance/inline-dsse.md
master 8355e2ff75
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat: Add initial implementation of Vulnerability Resolver Jobs
- 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.
2025-11-18 07:52:15 +02:00

273 lines
9.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Inline DSSE Provenance
> **Status:** Draft aligns with the November2025 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 StellaOps 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.