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:
@@ -149,14 +149,114 @@ Client then generates SBOM **only** for the `missing` layers and re‑posts `/sc
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Policy Endpoints
|
||||
### 2.3 Policy Endpoints *(preview feature flag: `scanner.features.enablePolicyPreview`)*
|
||||
|
||||
| Method | Path | Purpose |
|
||||
| ------ | ------------------ | ------------------------------------ |
|
||||
| `GET` | `/policy/export` | Download live YAML ruleset |
|
||||
| `POST` | `/policy/import` | Upload YAML or Rego; replaces active |
|
||||
| `POST` | `/policy/validate` | Lint only; returns 400 on error |
|
||||
| `GET` | `/policy/history` | Paginated change log (audit trail) |
|
||||
All policy APIs require **`scanner.reports`** scope (or anonymous access while auth is disabled).
|
||||
|
||||
**Fetch schema**
|
||||
|
||||
```
|
||||
GET /api/v1/policy/schema
|
||||
Authorization: Bearer <token>
|
||||
Accept: application/schema+json
|
||||
```
|
||||
|
||||
Returns the embedded `policy-schema@1` JSON schema used by the binder.
|
||||
|
||||
**Run diagnostics**
|
||||
|
||||
```
|
||||
POST /api/v1/policy/diagnostics
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"policy": {
|
||||
"format": "yaml",
|
||||
"actor": "cli",
|
||||
"description": "dev override",
|
||||
"content": "version: \"1.0\"\nrules:\n - name: Quiet Dev\n environments: [dev]\n action:\n type: ignore\n justification: dev waiver\n"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"version": "1.0",
|
||||
"ruleCount": 1,
|
||||
"errorCount": 0,
|
||||
"warningCount": 1,
|
||||
"generatedAt": "2025-10-19T03:25:14.112Z",
|
||||
"issues": [
|
||||
{ "code": "policy.rule.quiet.missing_vex", "message": "Quiet flag ignored: rule must specify requireVex justifications.", "severity": "Warning", "path": "$.rules[0]" }
|
||||
],
|
||||
"recommendations": [
|
||||
"Review policy warnings and ensure intentional overrides are documented."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`success` is `false` when blocking issues remain; recommendations aggregate YAML ignore rules, VEX include/exclude hints, and vendor precedence guidance.
|
||||
|
||||
**Preview impact**
|
||||
|
||||
```
|
||||
POST /api/v1/policy/preview
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"imageDigest": "sha256:abc123",
|
||||
"findings": [
|
||||
{ "id": "finding-1", "severity": "Critical", "source": "NVD" }
|
||||
],
|
||||
"policy": {
|
||||
"format": "yaml",
|
||||
"content": "version: \"1.0\"\nrules:\n - name: Block Critical\n severity: [Critical]\n action: block\n"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 200**:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"policyDigest": "9c5e...",
|
||||
"revisionId": "preview",
|
||||
"changed": 1,
|
||||
"diffs": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"baseline": {"findingId": "finding-1", "status": "Pass"},
|
||||
"projected": {
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"ruleName": "Block Critical",
|
||||
"ruleAction": "Block",
|
||||
"score": 5.0,
|
||||
"configVersion": "1.0",
|
||||
"inputs": {"severityWeight": 5.0}
|
||||
},
|
||||
"changed": true
|
||||
}
|
||||
],
|
||||
"issues": []
|
||||
}
|
||||
```
|
||||
|
||||
- Provide `policy` to preview staged changes; omit it to compare against the active snapshot.
|
||||
- Baseline verdicts are optional; when omitted, the API synthesises pass baselines before computing diffs.
|
||||
- Quieted verdicts include `quietedBy` and `quiet` flags; score inputs now surface reachability/vendor trust weights (`reachability.*`, `trustWeight.*`).
|
||||
|
||||
**OpenAPI**: the full API document (including these endpoints) is exposed at `/openapi/v1.json` and can be fetched for tooling or contract regeneration.
|
||||
|
||||
### 2.4 Scanner – Queue a Scan Job *(SP9 milestone)*
|
||||
|
||||
@@ -238,6 +338,96 @@ Accept: application/json
|
||||
|
||||
Statuses: `Pending`, `Running`, `Succeeded`, `Failed`, `Cancelled`.
|
||||
|
||||
### 2.6 Scanner – Stream Progress (SSE / JSONL)
|
||||
|
||||
```
|
||||
GET /api/v1/scans/{scanId}/events?format=sse|jsonl
|
||||
Authorization: Bearer <token with scanner.scans.read>
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
When `format` is omitted the endpoint emits **Server-Sent Events** (SSE). Specify `format=jsonl` to receive newline-delimited JSON (`application/x-ndjson`). Response headers include `Cache-Control: no-store` and `X-Accel-Buffering: no` so intermediaries avoid buffering the stream.
|
||||
|
||||
**SSE frame** (default):
|
||||
|
||||
```
|
||||
id: 1
|
||||
event: pending
|
||||
data: {"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
|
||||
```
|
||||
|
||||
**JSONL frame** (`format=jsonl`):
|
||||
|
||||
```json
|
||||
{"scanId":"2f6c17f9b3f548e2a28b9c412f4d63f8","sequence":1,"state":"Pending","message":"queued","timestamp":"2025-10-19T03:12:45.118Z","correlationId":"2f6c17f9b3f548e2a28b9c412f4d63f8:0001","data":{"force":false,"meta.pipeline":"github"}}
|
||||
```
|
||||
|
||||
- `sequence` is monotonic starting at `1`.
|
||||
- `correlationId` is deterministic (`{scanId}:{sequence:0000}`) unless a custom identifier is supplied by the publisher.
|
||||
- `timestamp` is ISO‑8601 UTC with millisecond precision, ensuring deterministic ordering for consumers.
|
||||
- The stream completes when the client disconnects or the coordinator stops publishing events.
|
||||
|
||||
### 2.7 Scanner – Assemble Report (Signed Envelope)
|
||||
|
||||
```
|
||||
POST /api/v1/reports
|
||||
Authorization: Bearer <token with scanner.reports>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
Request body mirrors policy preview inputs (image digest plus findings). The service evaluates the active policy snapshot, assembles a verdict, and signs the canonical report payload.
|
||||
|
||||
**Response 200**:
|
||||
|
||||
```json
|
||||
{
|
||||
"report": {
|
||||
"reportId": "report-3def5f362aa475ef14b6",
|
||||
"imageDigest": "sha256:deadbeef",
|
||||
"verdict": "blocked",
|
||||
"policy": { "revisionId": "rev-1", "digest": "27d2ec2b34feedc304fc564d252ecee1c8fa14ea581a5ff5c1ea8963313d5c8d" },
|
||||
"summary": { "total": 1, "blocked": 1, "warned": 0, "ignored": 0, "quieted": 0 },
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"ruleName": "Block Critical",
|
||||
"ruleAction": "Block",
|
||||
"score": 40.5,
|
||||
"configVersion": "1.0",
|
||||
"inputs": {
|
||||
"reachabilityWeight": 0.45,
|
||||
"baseScore": 40.5,
|
||||
"severityWeight": 90,
|
||||
"trustWeight": 1,
|
||||
"trustWeight.NVD": 1,
|
||||
"reachability.runtime": 0.45
|
||||
},
|
||||
"quiet": false,
|
||||
"sourceTrust": "NVD",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
],
|
||||
"issues": []
|
||||
},
|
||||
"dsse": {
|
||||
"payloadType": "application/vnd.stellaops.report+json",
|
||||
"payload": "<base64 canonical report>",
|
||||
"signatures": [
|
||||
{
|
||||
"keyId": "scanner-report-signing",
|
||||
"algorithm": "hs256",
|
||||
"signature": "<base64 signature>"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The `report` object omits null fields and is deterministic (ISO timestamps, sorted keys).
|
||||
- `dsse` follows the DSSE (Dead Simple Signing Envelope) shape; `payload` is the canonical UTF-8 JSON and `signatures[0].signature` is the base64 HMAC/Ed25519 value depending on configuration.
|
||||
- A runnable sample envelope is available at `samples/api/reports/report-sample.dsse.json` for tooling tests or signature verification.
|
||||
|
||||
**Response 404** – `application/problem+json` payload with type `https://stellaops.org/problems/not-found` when the scan identifier is unknown.
|
||||
|
||||
> **Tip** – poll `Location` from the submission call until `status` transitions away from `Pending`/`Running`.
|
||||
@@ -332,6 +522,7 @@ See `docs/dev/32_AUTH_CLIENT_GUIDE.md` for recommended profiles (online vs. air-
|
||||
| `stellaops-cli auth revoke export` | Export the Authority revocation bundle | `--output <directory>` (defaults to CWD) | Writes `revocation-bundle.json`, `.json.jws`, and `.json.sha256`; verifies the digest locally and includes key metadata in the log summary. |
|
||||
| `stellaops-cli auth revoke verify` | Validate a revocation bundle offline | `--bundle <path>` `--signature <path>` `--key <path>`<br>`--verbose` | Verifies detached JWS signatures, reports the computed SHA-256, and can fall back to cached JWKS when `--key` is omitted. |
|
||||
| `stellaops-cli config show` | Display resolved configuration | — | Masks secret values; helpful for air‑gapped installs |
|
||||
| `stellaops-cli runtime policy test` | Ask Scanner.WebService for runtime verdicts (Webhook parity) | `--image/-i <digest>` (repeatable, comma/space lists supported)<br>`--file/-f <path>`<br>`--namespace/--ns <name>`<br>`--label/-l key=value` (repeatable)<br>`--json` | Posts to `POST /api/v1/scanner/policy/runtime`, deduplicates image digests, and prints TTL + per-image verdict/signed/SBOM status. Accepts newline/whitespace-delimited stdin when piped; `--json` emits the raw response without additional logging. |
|
||||
|
||||
When running on an interactive terminal without explicit override flags, the CLI uses Spectre.Console prompts to let you choose per-run ORAS/offline bundle behaviour.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user