feat: Initialize Zastava Webhook service with TLS and Authority authentication
- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint. - Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately. - Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly. - Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
This commit is contained in:
@@ -3,12 +3,47 @@
|
||||
Platform services publish strongly typed events; the JSON Schemas in this directory define those envelopes. File names follow `<event-name>@<version>.json` so producers and consumers can negotiate contracts explicitly.
|
||||
|
||||
## Catalog
|
||||
- `scanner.report.ready@1.json` — emitted by Scanner.WebService once a signed report is persisted. Consumers: Notify, UI timeline.
|
||||
- `scanner.report.ready@1.json` — emitted by Scanner.WebService once a signed report is persisted (payload embeds the canonical report plus DSSE envelope). Consumers: Notify, UI timeline.
|
||||
- `scanner.scan.completed@1.json` — emitted alongside the signed report to capture scan outcomes/summary data for downstream automation. Consumers: Notify, Scheduler backfills, UI timelines.
|
||||
- `scheduler.rescan.delta@1.json` — emitted by Scheduler when BOM-Index diffs require fresh scans. Consumers: Notify, Policy Engine.
|
||||
- `attestor.logged@1.json` — emitted by Attestor after storing the Rekor inclusion proof. Consumers: UI attestation panel, Governance exports.
|
||||
|
||||
Additive payload changes (new optional fields) can stay within the same version. Any breaking change (removing a field, tightening validation, altering semantics) must increment the `@<version>` suffix and update downstream consumers.
|
||||
|
||||
## Envelope structure
|
||||
All event envelopes share the same deterministic header. Use the following table as the quick reference when emitting or parsing events:
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `eventId` | `uuid` | Must be globally unique per occurrence; producers log duplicates as fatal. |
|
||||
| `kind` | `string` | Fixed per schema (e.g., `scanner.report.ready`). Downstream services reject unknown kinds or versions. |
|
||||
| `tenant` | `string` | Multi‑tenant isolation key; mirror the value recorded in queue/Mongo metadata. |
|
||||
| `ts` | `date-time` | RFC 3339 UTC timestamp. Use monotonic clocks or atomic offsets so ordering survives retries. |
|
||||
| `scope` | `object` | Optional block used when the event concerns a specific image or repository. See schema for required fields (e.g., `repo`, `digest`). |
|
||||
| `payload` | `object` | Event-specific body. Schemas allow additional properties so producers can add optional hints (e.g., `reportId`, `quietedFindingCount`) without breaking consumers. For scanner events, payloads embed both the canonical report document and the DSSE envelope so consumers can reuse signatures without recomputing them. See `docs/runtime/SCANNER_RUNTIME_READINESS.md` for the runtime consumer checklist covering these hints. |
|
||||
|
||||
When adding new optional fields, document the behaviour in the schema’s `description` block and update the consumer checklist in the next sprint sync.
|
||||
|
||||
## Canonical samples & validation
|
||||
Reference payloads live under `docs/events/samples/`, mirroring the schema version (`<event-name>@<version>.sample.json`). They illustrate common field combinations, including the optional attributes that downstream teams rely on for UI affordances and audit trails. Scanner samples reuse the exact DSSE envelope checked into `samples/api/reports/report-sample.dsse.json`, and a unit test (`ReportSamplesTests`) guards that the payload/base64 remain canonical.
|
||||
|
||||
Run the following loop offline to validate both schemas and samples:
|
||||
|
||||
```bash
|
||||
# Validate schemas (same check as CI)
|
||||
for schema in docs/events/*.json; do
|
||||
npx ajv compile -c ajv-formats -s "$schema"
|
||||
done
|
||||
|
||||
# Validate canonical samples against their schemas
|
||||
for sample in docs/events/samples/*.sample.json; do
|
||||
schema="docs/events/$(basename "${sample%.sample.json}").json"
|
||||
npx ajv validate -c ajv-formats -s "$schema" -d "$sample"
|
||||
done
|
||||
```
|
||||
|
||||
Consumers can copy the samples into integration tests to guarantee backwards compatibility. When emitting new event versions, include a matching sample and update this README so air-gapped operators stay in sync.
|
||||
|
||||
## CI validation
|
||||
The Docs CI workflow (`.gitea/workflows/docs.yml`) installs `ajv-cli` and compiles every schema on pull requests. Run the same check locally before opening a PR:
|
||||
|
||||
@@ -25,6 +60,6 @@ If a schema references additional files, include `-r` flags so CI and local runs
|
||||
## Working with schemas
|
||||
- Producers should validate outbound payloads using the matching schema during unit tests.
|
||||
- Consumers should pin to a specific version and log when encountering unknown versions to catch missing migrations early.
|
||||
- Store real payload samples under `samples/events/` (mirrors the schema version) to aid contract testing.
|
||||
- Store real payload samples under `docs/events/samples/` (mirrors the schema version) and mirror them into `samples/events/` when you need fixtures in integration repositories.
|
||||
|
||||
Contact the Platform Events group in Docs Guild if you need help shaping a new event or version strategy.
|
||||
|
||||
21
docs/events/samples/attestor.logged@1.sample.json
Normal file
21
docs/events/samples/attestor.logged@1.sample.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"eventId": "1fdcaa1a-7a27-4154-8bac-cf813d8f4f6f",
|
||||
"kind": "attestor.logged",
|
||||
"tenant": "tenant-acme-solar",
|
||||
"ts": "2025-10-18T15:45:27+00:00",
|
||||
"payload": {
|
||||
"artifactSha256": "sha256:8927d9151ad3f44e61a9c647511f9a31af2b4d245e7e031fe5cb4a0e8211c5d9",
|
||||
"dsseEnvelopeDigest": "sha256:51c1dd189d5f16cfe87e82841d67b4fbc27d6fa9f5a09af0cd7e18945fb4c2a9",
|
||||
"rekor": {
|
||||
"index": 563421,
|
||||
"url": "https://rekor.example/api/v1/log/entries/d6d0f897e7244edc9cb0bb2c68b05c96",
|
||||
"uuid": "d6d0f897e7244edc9cb0bb2c68b05c96"
|
||||
},
|
||||
"signer": "cosign-stellaops",
|
||||
"subject": {
|
||||
"name": "scanner/report/sha256-0f0a8de5c1f93d6716b7249f6f4ea3a8",
|
||||
"type": "report"
|
||||
}
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
70
docs/events/samples/scanner.report.ready@1.sample.json
Normal file
70
docs/events/samples/scanner.report.ready@1.sample.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"eventId": "6d2d1b77-f3c3-4f70-8a9d-6f2d0c8801ab",
|
||||
"kind": "scanner.report.ready",
|
||||
"tenant": "tenant-alpha",
|
||||
"ts": "2025-10-19T12:34:56+00:00",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface",
|
||||
"labels": {},
|
||||
"attributes": {}
|
||||
},
|
||||
"payload": {
|
||||
"delta": {
|
||||
"kev": ["CVE-2024-9999"],
|
||||
"newCritical": 1
|
||||
},
|
||||
"dsse": {
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=",
|
||||
"payloadType": "application/vnd.stellaops.report\u002Bjson",
|
||||
"signatures": [{
|
||||
"algorithm": "hs256",
|
||||
"keyId": "test-key",
|
||||
"signature": "signature-value"
|
||||
}]
|
||||
},
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"links": {
|
||||
"ui": "https://scanner.example/ui/reports/report-abc"
|
||||
},
|
||||
"quietedFindingCount": 0,
|
||||
"report": {
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"issues": [],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "blocked",
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "fail"
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
78
docs/events/samples/scanner.scan.completed@1.sample.json
Normal file
78
docs/events/samples/scanner.scan.completed@1.sample.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"eventId": "08a6de24-4a94-4d14-8432-9d14f36f6da3",
|
||||
"kind": "scanner.scan.completed",
|
||||
"tenant": "tenant-alpha",
|
||||
"ts": "2025-10-19T12:34:56+00:00",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface",
|
||||
"labels": {},
|
||||
"attributes": {}
|
||||
},
|
||||
"payload": {
|
||||
"delta": {
|
||||
"kev": ["CVE-2024-9999"],
|
||||
"newCritical": 1
|
||||
},
|
||||
"digest": "sha256:feedface",
|
||||
"dsse": {
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=",
|
||||
"payloadType": "application/vnd.stellaops.report\u002Bjson",
|
||||
"signatures": [{
|
||||
"algorithm": "hs256",
|
||||
"keyId": "test-key",
|
||||
"signature": "signature-value"
|
||||
}]
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"cve": "CVE-2024-9999",
|
||||
"id": "finding-1",
|
||||
"reachability": "runtime",
|
||||
"severity": "Critical"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"report": {
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"issues": [],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "blocked",
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "fail"
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
20
docs/events/samples/scheduler.rescan.delta@1.sample.json
Normal file
20
docs/events/samples/scheduler.rescan.delta@1.sample.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"eventId": "51d0ef8d-3a17-4af3-b2d7-4ad3db3d9d2c",
|
||||
"kind": "scheduler.rescan.delta",
|
||||
"tenant": "tenant-acme-solar",
|
||||
"ts": "2025-10-18T15:40:11+00:00",
|
||||
"payload": {
|
||||
"impactedDigests": [
|
||||
"sha256:0f0a8de5c1f93d6716b7249f6f4ea3a8db451dc3f3c3ff823f53c9cbde5d5e8a",
|
||||
"sha256:ab921f9679dd8d0832f3710a4df75dbadbd58c2d95f26a4d4efb2fa8c3d9b4ce"
|
||||
],
|
||||
"reason": "policy-change:scoring/v2",
|
||||
"scheduleId": "rescan-weekly-critical",
|
||||
"summary": {
|
||||
"newCritical": 0,
|
||||
"newHigh": 1,
|
||||
"total": 4
|
||||
}
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
@@ -21,14 +21,28 @@
|
||||
"type": "object",
|
||||
"required": ["verdict", "delta", "links"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"generatedAt": {"type": "string", "format": "date-time"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {"type": "array", "items": {"type": "string"}}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
@@ -37,6 +51,30 @@
|
||||
"rekor": {"type": "string", "format": "uri"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"quietedFindingCount": {"type": "integer", "minimum": 0},
|
||||
"report": {"type": "object"},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
|
||||
97
docs/events/scanner.scan.completed@1.json
Normal file
97
docs/events/scanner.scan.completed@1.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scanner.scan.completed@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["eventId", "kind", "tenant", "ts", "scope", "payload"],
|
||||
"properties": {
|
||||
"eventId": {"type": "string", "format": "uuid"},
|
||||
"kind": {"const": "scanner.scan.completed"},
|
||||
"tenant": {"type": "string"},
|
||||
"ts": {"type": "string", "format": "date-time"},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"required": ["repo", "digest"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string"},
|
||||
"repo": {"type": "string"},
|
||||
"digest": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["reportId", "digest", "verdict", "summary"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"digest": {"type": "string"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"policy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"revisionId": {"type": "string"},
|
||||
"digest": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"findings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"severity": {"type": "string"},
|
||||
"cve": {"type": "string"},
|
||||
"purl": {"type": "string"},
|
||||
"reachability": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"report": {"type": "object"},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
Reference in New Issue
Block a user