feat: Implement console session management with tenant and profile handling
- Add ConsoleSessionStore for managing console session state including tenants, profile, and token information. - Create OperatorContextService to manage operator context for orchestrator actions. - Implement OperatorMetadataInterceptor to enrich HTTP requests with operator context metadata. - Develop ConsoleProfileComponent to display user profile and session details, including tenant information and access tokens. - Add corresponding HTML and SCSS for ConsoleProfileComponent to enhance UI presentation. - Write unit tests for ConsoleProfileComponent to ensure correct rendering and functionality.
This commit is contained in:
@@ -64,6 +64,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify policy scope configuration
|
||||
run: python3 scripts/verify-policy-scopes.py
|
||||
|
||||
- name: Validate NuGet restore source ordering
|
||||
run: python3 ops/devops/validate_restore_sources.py
|
||||
|
||||
|
||||
@@ -139,6 +139,14 @@ jobs:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.4.0
|
||||
|
||||
- name: Install Syft
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SYFT_VERSION="v1.21.0"
|
||||
curl -fsSL "https://github.com/anchore/syft/releases/download/${SYFT_VERSION}/syft_${SYFT_VERSION#v}_linux_amd64.tar.gz" -o /tmp/syft.tgz
|
||||
tar -xzf /tmp/syft.tgz -C /tmp
|
||||
sudo install -m 0755 /tmp/syft /usr/local/bin/syft
|
||||
|
||||
- name: Determine release metadata
|
||||
id: meta
|
||||
run: |
|
||||
@@ -182,6 +190,10 @@ jobs:
|
||||
echo "calendar=$CALENDAR_INPUT" >> "$GITHUB_OUTPUT"
|
||||
echo "push=$PUSH_FLAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Enforce CLI parity gate
|
||||
run: |
|
||||
python3 ops/devops/check_cli_parity.py
|
||||
|
||||
- name: Log in to registry
|
||||
if: steps.meta.outputs.push == 'true'
|
||||
uses: docker/login-action@v3
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,5 +31,4 @@ src/StellaOps.Web/node_modules/**/*
|
||||
src/StellaOps.Web/.angular/**/*
|
||||
**/node_modules/**/*
|
||||
node_modules
|
||||
node_modules
|
||||
node_modules
|
||||
tmp/**/*
|
||||
|
||||
5
.venv/pyvenv.cfg
Normal file
5
.venv/pyvenv.cfg
Normal file
@@ -0,0 +1,5 @@
|
||||
home = /usr/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.12.3
|
||||
executable = /usr/bin/python3.12
|
||||
command = /usr/bin/python3 -m venv /mnt/e/dev/git.stella-ops.org/.venv
|
||||
@@ -31,7 +31,7 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.Storage.Mongo/TASKS.md | TODO | Concelier Storage Guild | CONCELIER-STORE-AOC-19-002 | Create idempotency unique index backed by migration scripts. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.Storage.Mongo/TASKS.md | TODO | Concelier Storage Guild | CONCELIER-STORE-AOC-19-003 | Deliver append-only migration/backfill plan with supersedes chaining. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.Storage.Mongo/TASKS.md | TODO | Concelier Storage Guild, DevOps Guild | CONCELIER-STORE-AOC-19-004 | Document validator deployment steps for online/offline clusters. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.WebService/TASKS.md | TODO | Concelier WebService Guild | CONCELIER-WEB-AOC-19-001 | Implement raw advisory ingestion endpoints with AOC guard and verifier. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.WebService/TASKS.md | DONE (2025-10-28) | Concelier WebService Guild | CONCELIER-WEB-AOC-19-001 | Implement raw advisory ingestion endpoints with AOC guard and verifier. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.WebService/TASKS.md | TODO | Concelier WebService Guild, Observability Guild | CONCELIER-WEB-AOC-19-002 | Emit AOC observability metrics, traces, and structured logs. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.WebService/TASKS.md | TODO | QA Guild | CONCELIER-WEB-AOC-19-003 | Add schema/guard unit tests covering AOC error codes. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Concelier.WebService/TASKS.md | TODO | Concelier WebService Guild, QA Guild | CONCELIER-WEB-AOC-19-004 | Build integration suite validating deterministic ingest under load. |
|
||||
@@ -50,7 +50,7 @@ This file describe implementation of Stella Ops (docs/README.md). Implementation
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Excititor.WebService/TASKS.md | TODO | Excititor WebService Guild, QA Guild | EXCITITOR-WEB-AOC-19-004 | Validate large VEX ingest runs and CLI verification parity. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Excititor.Worker/TASKS.md | TODO | Excititor Worker Guild | EXCITITOR-WORKER-AOC-19-001 | Rewire worker to persist raw VEX docs with guard enforcement. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Excititor.Worker/TASKS.md | TODO | Excititor Worker Guild | EXCITITOR-WORKER-AOC-19-002 | Enforce signature/checksum verification prior to raw writes. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Excititor.Worker/TASKS.md | TODO | QA Guild | EXCITITOR-WORKER-AOC-19-003 | Expand worker tests for deterministic batching and restart safety. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Excititor.Worker/TASKS.md | DONE (2025-10-28) | QA Guild | EXCITITOR-WORKER-AOC-19-003 | Expand worker tests for deterministic batching and restart safety. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Policy/TASKS.md | TODO | Policy Guild | POLICY-AOC-19-001 | Add lint preventing ingestion modules from referencing Policy-only helpers. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Policy/TASKS.md | TODO | Policy Guild, Security Guild | POLICY-AOC-19-002 | Enforce Policy-only writes to `effective_finding_*` collections. |
|
||||
| Sprint 19 | Aggregation-Only Contract Enforcement | src/StellaOps.Policy/TASKS.md | TODO | Policy Guild | POLICY-AOC-19-003 | Update Policy readers to consume only raw document fields. |
|
||||
|
||||
@@ -626,6 +626,8 @@ See `docs/dev/32_AUTH_CLIENT_GUIDE.md` for recommended profiles (online vs. air-
|
||||
| `stellaops-cli offline kit pull` | Download the latest offline kit bundle and manifest | `--bundle-id <id>` (optional)<br>`--destination <dir>`<br>`--overwrite`<br>`--no-resume` | Streams the bundle + manifest from the configured mirror/backend, resumes interrupted downloads, verifies SHA-256, and writes signatures plus a `.metadata.json` manifest alongside the artefacts. |
|
||||
| `stellaops-cli offline kit import` | Upload an offline kit bundle to the backend | `<bundle.tgz>` (argument)<br>`--manifest <path>`<br>`--bundle-signature <path>`<br>`--manifest-signature <path>` | Validates digests when metadata is present, then posts multipart payloads to `POST /api/offline-kit/import`; logs the submitted import ID/status for air-gapped rollout tracking. |
|
||||
| `stellaops-cli offline kit status` | Display imported offline kit details | `--json` | Shows bundle id/kind, captured/imported timestamps, digests, and component versions; `--json` emits machine-readable output for scripting. |
|
||||
| `stellaops-cli sources ingest --dry-run` | Dry-run guard validation for individual payloads | `--source <id>`<br>`--input <path\|uri>`<br>`--tenant <id>`<br>`--format table\|json`<br>`--output <file>` | Normalises gzip/base64 payloads, invokes `api/aoc/ingest/dry-run`, and maps guard failures to deterministic `ERR_AOC_00x` exit codes. |
|
||||
| `stellaops-cli aoc verify` | Replay AOC guardrails over stored documents | `--since <ISO8601\|duration>`<br>`--limit <count>`<br>`--sources <list>`<br>`--codes <ERR_AOC_00x,...>`<br>`--format table\|json`<br>`--export <file>` | Summarises checked counts/violations, supports JSON evidence exports, and returns `0`, `11…17`, `18`, `70`, or `71` depending on guard outcomes. |
|
||||
| `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/policy revision plus per-image columns for signed state, SBOM referrers, quieted-by metadata, confidence, Rekor attestation (uuid + verified flag), and recently observed build IDs (shortened for readability). Accepts newline/whitespace-delimited stdin when piped; `--json` emits the raw response without additional logging. |
|
||||
|
||||
|
||||
@@ -167,8 +167,8 @@ rely on environment variables for ephemeral runners.
|
||||
dotnet run --project src/StellaOps.Cli -- db export --format json
|
||||
|
||||
# Trivy DB (delta example)
|
||||
dotnet run --project src/StellaOps.Cli -- db export --format trivy-db --delta
|
||||
```
|
||||
dotnet run --project src/StellaOps.Cli -- db export --format trivy-db --delta
|
||||
```
|
||||
|
||||
Concelier always produces a deterministic OCI layout. The first run after a clean
|
||||
bootstrap emits a **full** baseline; subsequent `--delta` runs reuse the previous
|
||||
@@ -190,19 +190,58 @@ rely on environment variables for ephemeral runners.
|
||||
|
||||
jq -r '.mode,.baseExportId' "$delta/metadata.json"
|
||||
|
||||
base_manifest=$(jq -r '.manifests[0].digest' "$base/index.json")
|
||||
delta_manifest=$(jq -r '.manifests[0].digest' "$delta/index.json")
|
||||
printf 'baseline manifest: %s\ndelta manifest: %s\n' "$base_manifest" "$delta_manifest"
|
||||
base_manifest=$(jq -r '.manifests[0].digest' "$base/index.json")
|
||||
delta_manifest=$(jq -r '.manifests[0].digest' "$delta/index.json")
|
||||
printf 'baseline manifest: %s\ndelta manifest: %s\n' "$base_manifest" "$delta_manifest"
|
||||
|
||||
layer_digest=$(jq -r '.layers[0].digest' "$base/blobs/sha256/${base_manifest#sha256:}")
|
||||
cmp "$base/blobs/sha256/${layer_digest#sha256:}" \
|
||||
"$delta/blobs/sha256/${layer_digest#sha256:}"
|
||||
```
|
||||
layer_digest=$(jq -r '.layers[0].digest' "$base/blobs/sha256/${base_manifest#sha256:}")
|
||||
cmp "$base/blobs/sha256/${layer_digest#sha256:}" \
|
||||
"$delta/blobs/sha256/${layer_digest#sha256:}"
|
||||
```
|
||||
|
||||
`cmp` returning exit code `0` confirms the delta export reuses the baseline’s
|
||||
`db.tar.gz` layer instead of rebuilding it.
|
||||
|
||||
4. **Manage scanners (optional)**
|
||||
4. **Verify guard compliance**
|
||||
|
||||
```bash
|
||||
export STELLA_TENANT="${STELLA_TENANT:-tenant-a}"
|
||||
|
||||
dotnet run --project src/StellaOps.Cli -- aoc verify \
|
||||
--since 24h \
|
||||
--format table \
|
||||
--tenant "$STELLA_TENANT"
|
||||
|
||||
# Optional: capture JSON evidence for pipelines/audits
|
||||
dotnet run --project src/StellaOps.Cli -- aoc verify \
|
||||
--since 7d \
|
||||
--limit 100 \
|
||||
--format json \
|
||||
--export artifacts/aoc-verify.json \
|
||||
--tenant "$STELLA_TENANT"
|
||||
```
|
||||
|
||||
The CLI exits with `0` when no violations are detected. Guard failures map
|
||||
to `ERR_AOC_00x` codes (`11…17`), while truncated results return `18`. Use
|
||||
`--sources`/`--codes` to focus on noisy connectors and feed the exported JSON
|
||||
into dashboards or evidence lockers for compliance reviews.
|
||||
|
||||
5. **Pre-flight individual payloads**
|
||||
|
||||
```bash
|
||||
stella sources ingest --dry-run \
|
||||
--source redhat \
|
||||
--input ./fixtures/redhat/RHSA-2025-9999.json \
|
||||
--tenant "$STELLA_TENANT" \
|
||||
--format json \
|
||||
--output artifacts/redhat-dry-run.json
|
||||
```
|
||||
|
||||
Exit code `0` confirms the candidate document is AOC compliant. Any guard
|
||||
violation is emitted as deterministic `ERR_AOC_00x` exit codes (`11…17`);
|
||||
reuse the exported JSON in PRs or incident timelines to show offending paths.
|
||||
|
||||
6. **Manage scanners (optional)**
|
||||
|
||||
```bash
|
||||
dotnet run --project src/StellaOps.Cli -- scanner download --channel stable
|
||||
|
||||
@@ -1,54 +1,75 @@
|
||||
# StellaOps Authority Service
|
||||
|
||||
> **Status:** Drafted 2025-10-12 (CORE5B.DOC / DOC1.AUTH) – aligns with Authority revocation store, JWKS rotation, and bootstrap endpoints delivered in Sprint 1.
|
||||
|
||||
## 1. Purpose
|
||||
The **StellaOps Authority** service issues OAuth2/OIDC tokens for every StellaOps module (Concelier, Backend, Agent, Zastava) and exposes the policy controls required in sovereign/offline environments. Authority is built as a minimal ASP.NET host that:
|
||||
|
||||
- brokers password, client-credentials, and device-code flows through pluggable identity providers;
|
||||
- persists access/refresh/device tokens in MongoDB with deterministic schemas for replay analysis and air-gapped audit copies;
|
||||
- distributes revocation bundles and JWKS material so downstream services can enforce lockouts without direct database access;
|
||||
- offers bootstrap APIs for first-run provisioning and key rotation without redeploying binaries.
|
||||
|
||||
Authority is deployed alongside Concelier in air-gapped environments and never requires outbound internet access. All trusted metadata (OpenIddict discovery, JWKS, revocation bundles) is cacheable, signed, and reproducible.
|
||||
|
||||
## 2. Component Architecture
|
||||
Authority is composed of five cooperating subsystems:
|
||||
|
||||
1. **Minimal API host** – configures OpenIddict endpoints (`/token`, `/authorize`, `/revoke`, `/jwks`) and structured logging/telemetry. Rate limiting hooks (`AuthorityRateLimiter`) wrap every request.
|
||||
2. **Plugin host** – loads `StellaOps.Authority.Plugin.*.dll` assemblies, applies capability metadata, and exposes password/client provisioning surfaces through dependency injection.
|
||||
3. **Mongo storage** – persists tokens, revocations, bootstrap invites, and plugin state in deterministic collections indexed for offline sync (`authority_tokens`, `authority_revocations`, etc.).
|
||||
4. **Cryptography layer** – `StellaOps.Cryptography` abstractions manage password hashing, signing keys, JWKS export, and detached JWS generation.
|
||||
5. **Offline ops APIs** – internal endpoints under `/internal/*` provide administrative flows (bootstrap users/clients, revocation export) guarded by API keys and deterministic audit events.
|
||||
|
||||
A high-level sequence for password logins:
|
||||
|
||||
```
|
||||
Client -> /token (password grant)
|
||||
-> Rate limiter & audit hooks
|
||||
-> Plugin credential store (Argon2id verification)
|
||||
-> Token persistence (Mongo authority_tokens)
|
||||
-> Response (access/refresh tokens + deterministic claims)
|
||||
```
|
||||
|
||||
## 3. Token Lifecycle & Persistence
|
||||
Authority persists every issued token in MongoDB so operators can audit or revoke without scanning distributed caches.
|
||||
|
||||
- **Collection:** `authority_tokens`
|
||||
- **Key fields:**
|
||||
# StellaOps Authority Service
|
||||
|
||||
> **Status:** Drafted 2025-10-12 (CORE5B.DOC / DOC1.AUTH) – aligns with Authority revocation store, JWKS rotation, and bootstrap endpoints delivered in Sprint 1.
|
||||
|
||||
## 1. Purpose
|
||||
The **StellaOps Authority** service issues OAuth2/OIDC tokens for every StellaOps module (Concelier, Backend, Agent, Zastava) and exposes the policy controls required in sovereign/offline environments. Authority is built as a minimal ASP.NET host that:
|
||||
|
||||
- brokers password, client-credentials, and device-code flows through pluggable identity providers;
|
||||
- persists access/refresh/device tokens in MongoDB with deterministic schemas for replay analysis and air-gapped audit copies;
|
||||
- distributes revocation bundles and JWKS material so downstream services can enforce lockouts without direct database access;
|
||||
- offers bootstrap APIs for first-run provisioning and key rotation without redeploying binaries.
|
||||
|
||||
Authority is deployed alongside Concelier in air-gapped environments and never requires outbound internet access. All trusted metadata (OpenIddict discovery, JWKS, revocation bundles) is cacheable, signed, and reproducible.
|
||||
|
||||
## 2. Component Architecture
|
||||
Authority is composed of five cooperating subsystems:
|
||||
|
||||
1. **Minimal API host** – configures OpenIddict endpoints (`/token`, `/authorize`, `/revoke`, `/jwks`), publishes the OpenAPI contract at `/.well-known/openapi`, and enables structured logging/telemetry. Rate limiting hooks (`AuthorityRateLimiter`) wrap every request.
|
||||
2. **Plugin host** – loads `StellaOps.Authority.Plugin.*.dll` assemblies, applies capability metadata, and exposes password/client provisioning surfaces through dependency injection.
|
||||
3. **Mongo storage** – persists tokens, revocations, bootstrap invites, and plugin state in deterministic collections indexed for offline sync (`authority_tokens`, `authority_revocations`, etc.).
|
||||
4. **Cryptography layer** – `StellaOps.Cryptography` abstractions manage password hashing, signing keys, JWKS export, and detached JWS generation.
|
||||
5. **Offline ops APIs** – internal endpoints under `/internal/*` provide administrative flows (bootstrap users/clients, revocation export) guarded by API keys and deterministic audit events.
|
||||
|
||||
A high-level sequence for password logins:
|
||||
|
||||
```
|
||||
Client -> /token (password grant)
|
||||
-> Rate limiter & audit hooks
|
||||
-> Plugin credential store (Argon2id verification)
|
||||
-> Token persistence (Mongo authority_tokens)
|
||||
-> Response (access/refresh tokens + deterministic claims)
|
||||
```
|
||||
|
||||
## 3. Token Lifecycle & Persistence
|
||||
Authority persists every issued token in MongoDB so operators can audit or revoke without scanning distributed caches.
|
||||
|
||||
- **Collection:** `authority_tokens`
|
||||
- **Key fields:**
|
||||
- `tokenId`, `type` (`access_token`, `refresh_token`, `device_code`, `authorization_code`)
|
||||
- `subjectId`, `clientId`, ordered `scope` array
|
||||
- `tenant` (lower-cased tenant hint from the issuing client, omitted for global clients)
|
||||
|
||||
### Console OIDC client
|
||||
|
||||
- **Client ID**: `console-web`
|
||||
- **Grants**: `authorization_code` (PKCE required), `refresh_token`
|
||||
- **Audience**: `console`
|
||||
- **Scopes**: `openid`, `profile`, `email`, `advisory:read`, `vex:read`, `aoc:verify`, `findings:read`, `orch:read`, `vuln:read`
|
||||
- **Redirect URIs** (defaults): `https://console.stella-ops.local/oidc/callback`
|
||||
- **Post-logout redirect**: `https://console.stella-ops.local/`
|
||||
- **Tokens**: Access tokens inherit the global 2 minute lifetime; refresh tokens remain short-lived (30 days) and can be exchanged silently via `/token`.
|
||||
- **Roles**: Assign Authority role `Orch.Viewer` (exposed to tenants as `role/orch-viewer`) when operators need read-only access to Orchestrator telemetry via Console dashboards. Policy Studio ships dedicated roles (`role/policy-author`, `role/policy-reviewer`, `role/policy-approver`, `role/policy-operator`, `role/policy-auditor`) that align with the new `policy:*` scope family; issue them per tenant so audit trails remain scoped.
|
||||
|
||||
Configuration sample (`etc/authority.yaml.sample`) seeds the client with a confidential secret so Console can negotiate the code exchange on the backend while browsers execute the PKCE dance.
|
||||
|
||||
### Console Authority endpoints
|
||||
|
||||
- `/console/tenants` — Requires `authority:tenants.read`; returns the tenant catalogue for the authenticated principal. Requests lacking the `X-Stella-Tenant` header are rejected (`tenant_header_missing`) and logged.
|
||||
- `/console/profile` — Requires `ui.read`; exposes subject metadata (roles, scopes, audiences) and indicates whether the session is within the five-minute fresh-auth window.
|
||||
- `/console/token/introspect` — Requires `ui.read`; introspects the active access token so the SPA can prompt for re-authentication before privileged actions.
|
||||
|
||||
All endpoints demand DPoP-bound tokens and propagate structured audit events (`authority.console.*`). Gateways must forward the `X-Stella-Tenant` header derived from the access token; downstream services rely on the same value for isolation. Keep Console access tokens short-lived (default 15 minutes) and enforce the fresh-auth window for admin actions (`ui.admin`, `authority:*`, `policy:activate`, `exceptions:approve`).
|
||||
- `status` (`valid`, `revoked`, `expired`), `createdAt`, optional `expiresAt`
|
||||
- `revokedAt`, machine-readable `revokedReason`, optional `revokedReasonDescription`
|
||||
- `revokedMetadata` (string dictionary for plugin-specific context)
|
||||
- **Persistence flow:** `PersistTokensHandler` stamps missing JWT IDs, normalises scopes, and stores every principal emitted by OpenIddict.
|
||||
- **Revocation flow:** `AuthorityTokenStore.UpdateStatusAsync` flips status, records the reason metadata, and is invoked by token revocation handlers and plugin provisioning events (e.g., disabling a user).
|
||||
- **Expiry maintenance:** `AuthorityTokenStore.DeleteExpiredAsync` prunes non-revoked tokens past their `expiresAt` timestamp. Operators should schedule this in maintenance windows if large volumes of tokens are issued.
|
||||
|
||||
### Expectations for resource servers
|
||||
Resource servers (Concelier WebService, Backend, Agent) **must not** assume in-memory caches are authoritative. They should:
|
||||
|
||||
- **Expiry maintenance:** `AuthorityTokenStore.DeleteExpiredAsync` prunes non-revoked tokens past their `expiresAt` timestamp. Operators should schedule this in maintenance windows if large volumes of tokens are issued.
|
||||
|
||||
### Expectations for resource servers
|
||||
Resource servers (Concelier WebService, Backend, Agent) **must not** assume in-memory caches are authoritative. They should:
|
||||
|
||||
- cache `/jwks` and `/revocations/export` responses within configured lifetimes;
|
||||
- honour `revokedReason` metadata when shaping audit trails;
|
||||
- treat `status != "valid"` or missing tokens as immediate denial conditions.
|
||||
@@ -59,7 +80,11 @@ Resource servers (Concelier WebService, Backend, Agent) **must not** assume in-m
|
||||
- Client provisioning (bootstrap or plug-in) accepts a `tenant` hint. Authority normalises the value (`trim().ToLowerInvariant()`) and persists it alongside the registration. Clients without an explicit tenant remain global.
|
||||
- Issued principals include the `stellaops:tenant` claim. `PersistTokensHandler` mirrors this claim into `authority_tokens.tenant`, enabling per-tenant revocation and reporting.
|
||||
- Rate limiter metadata now tags requests with `authority.tenant`, unlocking per-tenant throughput metrics and diagnostic filters. Audit events (`authority.client_credentials.grant`, `authority.password.grant`, bootstrap flows) surface the tenant and login attempt documents index on `{tenant, occurredAt}` for quick queries.
|
||||
- Client credentials that request `advisory:ingest`, `advisory:read`, `vex:ingest`, `vex:read`, or `aoc:verify` now fail fast when the client registration lacks a tenant hint. Issued tokens are re-validated against persisted tenant metadata, and Authority rejects any cross-tenant replay (`invalid_client`/`invalid_token`), ensuring aggregation-only workloads remain tenant-scoped.
|
||||
- Client credentials that request `advisory:ingest`, `advisory:read`, `vex:ingest`, `vex:read`, `signals:read`, `signals:write`, `signals:admin`, or `aoc:verify` now fail fast when the client registration lacks a tenant hint. Issued tokens are re-validated against persisted tenant metadata, and Authority rejects any cross-tenant replay (`invalid_client`/`invalid_token`), ensuring aggregation-only workloads remain tenant-scoped.
|
||||
- Client credentials that request `export.viewer`, `export.operator`, or `export.admin` must provide a tenant hint. Requests for `export.admin` also need accompanying `export_reason` and `export_ticket` parameters; Authority returns `invalid_request` when either value is missing and records the denial in token audit events.
|
||||
- Policy Studio scopes (`policy:author`, `policy:review`, `policy:approve`, `policy:operate`, `policy:audit`, `policy:simulate`, `policy:run`, `policy:activate`) require a tenant assignment; Authority rejects tokens missing the hint with `invalid_client` and records `scope.invalid` metadata for auditing.
|
||||
- **AOC pairing guardrails** – Tokens that request `advisory:read`, `vex:read`, or any `signals:*` scope must also request `aoc:verify`. Authority rejects mismatches with `invalid_scope` (`Scope 'aoc:verify' is required when requesting advisory/vex read scopes.` or `Scope 'aoc:verify' is required when requesting signals scopes.`) so automation surfaces deterministic errors.
|
||||
- **Signals ingestion guardrails** – Sensors and services requesting `signals:write`/`signals:admin` must also request `aoc:verify`; Authority records the `authority.aoc_scope_violation` tag when the pairing is missing so operators can trace failing sensors immediately.
|
||||
- Password grant flows reuse the client registration's tenant and enforce the configured scope allow-list. Requested scopes outside that list (or mismatched tenants) trigger `invalid_scope`/`invalid_client` failures, ensuring cross-tenant access is denied before token issuance.
|
||||
|
||||
### Default service scopes
|
||||
@@ -71,10 +96,15 @@ Resource servers (Concelier WebService, Backend, Agent) **must not** assume in-m
|
||||
| `aoc-verifier` | Aggregation-only contract verification | `aoc:verify`, `advisory:read`, `vex:read` | `dpop` | `tenant-default` |
|
||||
| `cartographer-service` | Graph snapshot construction | `graph:write`, `graph:read` | `dpop` | `tenant-default` |
|
||||
| `graph-api` | Graph Explorer gateway/API | `graph:read`, `graph:export`, `graph:simulate` | `dpop` | `tenant-default` |
|
||||
| `export-center-operator` | Export Center operator automation | `export.viewer`, `export.operator` | `dpop` | `tenant-default` |
|
||||
| `export-center-admin` | Export Center administrative automation | `export.viewer`, `export.operator`, `export.admin` | `dpop` | `tenant-default` |
|
||||
| `vuln-explorer-ui` | Vuln Explorer UI/API | `vuln:read` | `dpop` | `tenant-default` |
|
||||
| `signals-uploader` | Reachability sensor ingestion | `signals:write`, `signals:read`, `aoc:verify` | `dpop` | `tenant-default` |
|
||||
|
||||
> **Secret hygiene (2025‑10‑27):** The repository includes a convenience `etc/authority.yaml` for compose/helm smoke tests. Every entry’s `secretFile` points to `etc/secrets/*.secret`, which ship with `*-change-me` placeholders—replace them with strong values (and wire them through your vault/secret manager) before issuing tokens in CI, staging, or production.
|
||||
|
||||
For factory provisioning, issue sensors the **SignalsUploader** role template (`signals:write`, `signals:read`, `aoc:verify`). Authority rejects ingestion tokens that omit `aoc:verify`, preserving aggregation-only contract guarantees for reachability signals.
|
||||
|
||||
These registrations are provided as examples in `etc/authority.yaml.sample`. Clone them per tenant (for example `concelier-tenant-a`, `concelier-tenant-b`) so tokens remain tenant-scoped by construction.
|
||||
|
||||
Graph Explorer introduces dedicated scopes: `graph:write` for Cartographer build jobs, `graph:read` for query/read operations, `graph:export` for long-running export downloads, and `graph:simulate` for what-if overlays. Assign only the scopes a client actually needs to preserve least privilege—UI-facing clients should typically request read/export access, while background services (Cartographer, Scheduler) require write privileges.
|
||||
@@ -86,79 +116,86 @@ Graph Explorer introduces dedicated scopes: `graph:write` for Cartographer build
|
||||
- **SDK alignment** – Use the generated `StellaOpsScopes` constants in service code to request graph scopes. Hard-coded strings risk falling out of sync as additional graph capabilities are added.
|
||||
- **DPOP for automation** – Maintain sender-constrained (`dpop`) flows for Cartographer and Scheduler to limit reuse of access tokens if a build host is compromised. For UI-facing tokens, pair `graph:read`/`graph:export` with short lifetimes and enforce refresh-token rotation at the gateway.
|
||||
|
||||
#### Export Center scope guardrails
|
||||
|
||||
- **Viewer vs operator** – `export.viewer` grants read-only access to export profiles, manifests, and bundles. Automation that schedules or reruns exports should request `export.operator` (and typically `export.viewer`). Tenant hints remain mandatory; Authority refuses tokens without them.
|
||||
- **Administrative mutations** – Changes to retention policies, encryption key references, or schedule defaults require `export.admin`. When requesting tokens with this scope, clients must supply `export_reason` and `export_ticket` parameters; Authority persists the values for audit records and rejects missing metadata with `invalid_request`.
|
||||
- **Operational hygiene** – Rotate `export.admin` credentials infrequently and run them through fresh-auth workflows where possible. Prefer distributing verification tooling with `export.viewer` tokens for day-to-day bundle validation.
|
||||
|
||||
#### Vuln Explorer permalinks
|
||||
|
||||
- **Scope** – `vuln:read` authorises Vuln Explorer to fetch advisory/linkset evidence and issue shareable links. Assign it only to front-end/API clients that must render vulnerability details.
|
||||
- **Signed links** – `POST /permalinks/vuln` (requires `vuln:read`) accepts `{ "tenant": "tenant-a", "resourceKind": "vulnerability", "state": { ... }, "expiresInSeconds": 86400 }` and returns a JWT (`token`) plus `issuedAt`/`expiresAt`. The token embeds the tenant, requested state, and `vuln:read` scope and is signed with the same Authority signing keys published via `/jwks`.
|
||||
- **Validation** – Resource servers verify the permalink using cached JWKS: check signature, ensure the tenant matches the current request context, honour the expiry, and enforce the contained `vuln:read` scope. The payload’s `resource.state` block is opaque JSON so UIs can round-trip filters/search terms without new schema changes.
|
||||
|
||||
## 4. Revocation Pipeline
|
||||
Authority centralises revocation in `authority_revocations` with deterministic categories:
|
||||
|
||||
| Category | Meaning | Required fields |
|
||||
| --- | --- | --- |
|
||||
| `token` | Specific OAuth token revoked early. | `revocationId` (token id), `tokenType`, optional `clientId`, `subjectId` |
|
||||
| `subject` | All tokens for a subject disabled. | `revocationId` (= subject id) |
|
||||
| `client` | OAuth client registration revoked. | `revocationId` (= client id) |
|
||||
| `key` | Signing/JWE key withdrawn. | `revocationId` (= key id) |
|
||||
|
||||
`RevocationBundleBuilder` flattens Mongo documents into canonical JSON, sorts entries by (`category`, `revocationId`, `revokedAt`), and signs exports using detached JWS (RFC 7797) with cosign-compatible headers.
|
||||
|
||||
**Export surfaces** (deterministic output, suitable for Offline Kit):
|
||||
|
||||
- CLI: `stella auth revoke export --output ./out` writes `revocation-bundle.json`, `.jws`, `.sha256`.
|
||||
- Verification: `stella auth revoke verify --bundle <path> --signature <path> --key <path>` validates detached JWS signatures before distribution, selecting the crypto provider advertised in the detached header (see `docs/security/revocation-bundle.md`).
|
||||
- API: `GET /internal/revocations/export` (requires bootstrap API key) returns the same payload.
|
||||
- Verification: `stella auth revoke verify` validates schema, digest, and detached JWS using cached JWKS or offline keys, automatically preferring the hinted provider (libsodium builds honour `provider=libsodium`; other builds fall back to the managed provider).
|
||||
|
||||
**Consumer guidance:**
|
||||
|
||||
1. Mirror `revocation-bundle.json*` alongside Concelier exports. Offline agents fetch both over the existing update channel.
|
||||
2. Use bundle `sequence` and `bundleId` to detect replay or monotonicity regressions. Ignore bundles with older sequence numbers unless `bundleId` changes and `issuedAt` advances.
|
||||
3. Treat `revokedReason` taxonomy as machine-friendly codes (`compromised`, `rotation`, `policy`, `lifecycle`). Translating to human-readable logs is the consumer’s responsibility.
|
||||
|
||||
## 5. Signing Keys & JWKS Rotation
|
||||
Authority signs revocation bundles and publishes JWKS entries via the new signing manager:
|
||||
|
||||
- **Configuration (`authority.yaml`):**
|
||||
```yaml
|
||||
signing:
|
||||
enabled: true
|
||||
algorithm: ES256 # Defaults to ES256
|
||||
keySource: file # Loader identifier (file, vault, etc.)
|
||||
provider: default # Optional preferred crypto provider
|
||||
activeKeyId: authority-signing-dev
|
||||
keyPath: "../certificates/authority-signing-dev.pem"
|
||||
additionalKeys:
|
||||
- keyId: authority-signing-dev-2024
|
||||
path: "../certificates/authority-signing-dev-2024.pem"
|
||||
source: "file"
|
||||
```
|
||||
- **Sources:** The default loader supports PEM files relative to the content root; additional loaders can be registered via `IAuthoritySigningKeySource`.
|
||||
- **Providers:** Keys are registered against the `ICryptoProviderRegistry`, so alternative implementations (HSM, libsodium) can be plugged in without changing host code.
|
||||
- **JWKS output:** `GET /jwks` lists every signing key with `status` metadata (`active`, `retired`). Old keys remain until operators remove them from configuration, allowing verification of historical bundles/tokens.
|
||||
|
||||
### Rotation SOP (no downtime)
|
||||
1. Generate a new P-256 private key (PEM) on an offline workstation and place it where the Authority host can read it (e.g., `../certificates/authority-signing-2025.pem`).
|
||||
2. Call the authenticated admin API:
|
||||
```bash
|
||||
curl -sS -X POST https://authority.example.com/internal/signing/rotate \
|
||||
-H "x-stellaops-bootstrap-key: ${BOOTSTRAP_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"keyId": "authority-signing-2025",
|
||||
"location": "../certificates/authority-signing-2025.pem",
|
||||
"source": "file"
|
||||
}'
|
||||
```
|
||||
3. Verify the response reports the previous key as retired and fetch `/jwks` to confirm the new `kid` appears with `status: "active"`.
|
||||
4. Persist the old key path in `signing.additionalKeys` (the rotation API updates in-memory options; rewrite the YAML to match so restarts remain consistent).
|
||||
5. If you prefer automation, trigger the `.gitea/workflows/authority-key-rotation.yml` workflow with the new `keyId`/`keyPath`; it wraps `ops/authority/key-rotation.sh` and reads environment-specific secrets. The older key will be marked `retired` and appended to `signing.additionalKeys`.
|
||||
6. Re-run `stella auth revoke export` so revocation bundles are signed with the new key. Downstream caches should refresh JWKS within their configured lifetime (`StellaOpsAuthorityOptions.Signing` + client cache tolerance).
|
||||
|
||||
The rotation API leverages the same cryptography abstractions as revocation signing; no restart is required and the previous key is marked `retired` but kept available for verification.
|
||||
|
||||
## 6. Bootstrap & Administrative Endpoints
|
||||
|
||||
## 4. Revocation Pipeline
|
||||
Authority centralises revocation in `authority_revocations` with deterministic categories:
|
||||
|
||||
| Category | Meaning | Required fields |
|
||||
| --- | --- | --- |
|
||||
| `token` | Specific OAuth token revoked early. | `revocationId` (token id), `tokenType`, optional `clientId`, `subjectId` |
|
||||
| `subject` | All tokens for a subject disabled. | `revocationId` (= subject id) |
|
||||
| `client` | OAuth client registration revoked. | `revocationId` (= client id) |
|
||||
| `key` | Signing/JWE key withdrawn. | `revocationId` (= key id) |
|
||||
|
||||
`RevocationBundleBuilder` flattens Mongo documents into canonical JSON, sorts entries by (`category`, `revocationId`, `revokedAt`), and signs exports using detached JWS (RFC 7797) with cosign-compatible headers.
|
||||
|
||||
**Export surfaces** (deterministic output, suitable for Offline Kit):
|
||||
|
||||
- CLI: `stella auth revoke export --output ./out` writes `revocation-bundle.json`, `.jws`, `.sha256`.
|
||||
- Verification: `stella auth revoke verify --bundle <path> --signature <path> --key <path>` validates detached JWS signatures before distribution, selecting the crypto provider advertised in the detached header (see `docs/security/revocation-bundle.md`).
|
||||
- API: `GET /internal/revocations/export` (requires bootstrap API key) returns the same payload.
|
||||
- Verification: `stella auth revoke verify` validates schema, digest, and detached JWS using cached JWKS or offline keys, automatically preferring the hinted provider (libsodium builds honour `provider=libsodium`; other builds fall back to the managed provider).
|
||||
|
||||
**Consumer guidance:**
|
||||
|
||||
1. Mirror `revocation-bundle.json*` alongside Concelier exports. Offline agents fetch both over the existing update channel.
|
||||
2. Use bundle `sequence` and `bundleId` to detect replay or monotonicity regressions. Ignore bundles with older sequence numbers unless `bundleId` changes and `issuedAt` advances.
|
||||
3. Treat `revokedReason` taxonomy as machine-friendly codes (`compromised`, `rotation`, `policy`, `lifecycle`). Translating to human-readable logs is the consumer’s responsibility.
|
||||
|
||||
## 5. Signing Keys & JWKS Rotation
|
||||
Authority signs revocation bundles and publishes JWKS entries via the new signing manager:
|
||||
|
||||
- **Configuration (`authority.yaml`):**
|
||||
```yaml
|
||||
signing:
|
||||
enabled: true
|
||||
algorithm: ES256 # Defaults to ES256
|
||||
keySource: file # Loader identifier (file, vault, etc.)
|
||||
provider: default # Optional preferred crypto provider
|
||||
activeKeyId: authority-signing-dev
|
||||
keyPath: "../certificates/authority-signing-dev.pem"
|
||||
additionalKeys:
|
||||
- keyId: authority-signing-dev-2024
|
||||
path: "../certificates/authority-signing-dev-2024.pem"
|
||||
source: "file"
|
||||
```
|
||||
- **Sources:** The default loader supports PEM files relative to the content root; additional loaders can be registered via `IAuthoritySigningKeySource`.
|
||||
- **Providers:** Keys are registered against the `ICryptoProviderRegistry`, so alternative implementations (HSM, libsodium) can be plugged in without changing host code.
|
||||
- **OpenAPI discovery:** `GET /.well-known/openapi` returns the published authentication contract (JSON by default, YAML when requested). Responses include `X-StellaOps-Service`, `X-StellaOps-Api-Version`, `X-StellaOps-Build-Version`, plus grant and scope headers, and honour conditional requests via `ETag`/`If-None-Match`.
|
||||
- **JWKS output:** `GET /jwks` lists every signing key with `status` metadata (`active`, `retired`). Old keys remain until operators remove them from configuration, allowing verification of historical bundles/tokens.
|
||||
|
||||
### Rotation SOP (no downtime)
|
||||
1. Generate a new P-256 private key (PEM) on an offline workstation and place it where the Authority host can read it (e.g., `../certificates/authority-signing-2025.pem`).
|
||||
2. Call the authenticated admin API:
|
||||
```bash
|
||||
curl -sS -X POST https://authority.example.com/internal/signing/rotate \
|
||||
-H "x-stellaops-bootstrap-key: ${BOOTSTRAP_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"keyId": "authority-signing-2025",
|
||||
"location": "../certificates/authority-signing-2025.pem",
|
||||
"source": "file"
|
||||
}'
|
||||
```
|
||||
3. Verify the response reports the previous key as retired and fetch `/jwks` to confirm the new `kid` appears with `status: "active"`.
|
||||
4. Persist the old key path in `signing.additionalKeys` (the rotation API updates in-memory options; rewrite the YAML to match so restarts remain consistent).
|
||||
5. If you prefer automation, trigger the `.gitea/workflows/authority-key-rotation.yml` workflow with the new `keyId`/`keyPath`; it wraps `ops/authority/key-rotation.sh` and reads environment-specific secrets. The older key will be marked `retired` and appended to `signing.additionalKeys`.
|
||||
6. Re-run `stella auth revoke export` so revocation bundles are signed with the new key. Downstream caches should refresh JWKS within their configured lifetime (`StellaOpsAuthorityOptions.Signing` + client cache tolerance).
|
||||
|
||||
The rotation API leverages the same cryptography abstractions as revocation signing; no restart is required and the previous key is marked `retired` but kept available for verification.
|
||||
|
||||
## 6. Bootstrap & Administrative Endpoints
|
||||
Administrative APIs live under `/internal/*` and require the bootstrap API key plus rate-limiter compliance.
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
@@ -194,30 +231,53 @@ For environments with multiple tenants, repeat the call per tenant-specific clie
|
||||
|
||||
- Issue a dedicated client (e.g. `aoc-verifier`) with the scopes `aoc:verify`, `advisory:read`, and `vex:read` for each tenant that runs guard checks. Authority refuses to mint tokens for these scopes unless the client registration provides a tenant hint.
|
||||
- The CLI (`stella aoc verify --tenant <tenant>`) and Console verification panel both call `/aoc/verify` on Concelier and Excititor. Tokens that omit the tenant claim or present a tenant that does not match the stored registration are rejected with `invalid_client`/`invalid_token`.
|
||||
- Audit: `authority.client_credentials.grant` entries record `scope.invalid="aoc:verify"` when requests are rejected because the tenant hint is missing or mismatched.
|
||||
|
||||
### Exception approvals & routing
|
||||
|
||||
- New scopes `exceptions:read`, `exceptions:write`, and `exceptions:approve` govern access to the exception lifecycle. Map these via tenant roles (`exceptions-service`, `exceptions-approver`) as described in `/docs/security/authority-scopes.md`.
|
||||
- Configure approval routing in `authority.yaml` with declarative templates. Each template exposes an `authorityRouteId` for downstream services (Policy Engine, Console) and an optional `requireMfa` flag:
|
||||
|
||||
```yaml
|
||||
exceptions:
|
||||
routingTemplates:
|
||||
- id: "secops"
|
||||
authorityRouteId: "approvals/secops"
|
||||
requireMfa: true
|
||||
description: "Security Operations approval chain"
|
||||
- id: "governance"
|
||||
authorityRouteId: "approvals/governance"
|
||||
requireMfa: false
|
||||
description: "Non-production waiver review"
|
||||
```
|
||||
|
||||
- Clients requesting exception scopes must include a tenant assignment. Authority rejects client-credential flows that request `exceptions:*` with `invalid_client` and logs `scope.invalid="exceptions:write"` (or the requested scope) in `authority.client_credentials.grant` audit events when the tenant hint is missing.
|
||||
- When any configured routing template sets `requireMfa: true`, user-facing tokens that contain `exceptions:approve` must be acquired through an MFA-capable identity provider. Password/OIDC flows that lack MFA support are rejected with `authority.password.grant` audit events where `reason="Exception approval scope requires an MFA-capable identity provider."`
|
||||
- Update interactive clients (Console) to request `exceptions:read` by default and elevate to `exceptions:approve` only inside fresh-auth workflows for approvers. Documented examples live in `etc/authority.yaml.sample`.
|
||||
- Verification responses map guard failures to `ERR_AOC_00x` codes and Authority emits `authority.client_credentials.grant` + `authority.token.validate_access` audit records containing the tenant and scopes so operators can trace who executed a run.
|
||||
- For air-gapped or offline replicas, pre-issue verification tokens per tenant and rotate them alongside ingest credentials; the guard endpoints never mutate data and remain safe to expose through the offline kit schedule.
|
||||
|
||||
## 7. Configuration Reference
|
||||
|
||||
| Section | Key | Description | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Root | `issuer` | Absolute HTTPS issuer advertised to clients. | Required. Loopback HTTP allowed only for development. |
|
||||
| Tokens | `accessTokenLifetime`, `refreshTokenLifetime`, etc. | Lifetimes for each grant (access, refresh, device, authorization code, identity). | Enforced during issuance; persisted on each token document. |
|
||||
| Storage | `storage.connectionString` | MongoDB connection string. | Required even for tests; offline kits ship snapshots for seeding. |
|
||||
| Signing | `signing.enabled` | Enable JWKS/revocation signing. | Disable only for development. |
|
||||
| Signing | `signing.algorithm` | Signing algorithm identifier. | Currently ES256; additional curves can be wired through crypto providers. |
|
||||
| Signing | `signing.keySource` | Loader identifier (`file`, `vault`, custom). | Determines which `IAuthoritySigningKeySource` resolves keys. |
|
||||
| Signing | `signing.keyPath` | Relative/absolute path understood by the loader. | Stored as-is; rotation request should keep it in sync with filesystem layout. |
|
||||
| Signing | `signing.activeKeyId` | Active JWKS / revocation signing key id. | Exposed as `kid` in JWKS and bundles. |
|
||||
| Signing | `signing.additionalKeys[].keyId` | Retired key identifier retained for verification. | Manager updates this automatically after rotation; keep YAML aligned. |
|
||||
| Signing | `signing.additionalKeys[].source` | Loader identifier per retired key. | Defaults to `signing.keySource` if omitted. |
|
||||
| Security | `security.rateLimiting` | Fixed-window limits for `/token`, `/authorize`, `/internal/*`. | See `docs/security/rate-limits.md` for tuning. |
|
||||
| Bootstrap | `bootstrap.apiKey` | Shared secret required for `/internal/*`. | Only required when `bootstrap.enabled` is true. |
|
||||
|
||||
### 7.1 Sender-constrained clients (DPoP & mTLS)
|
||||
|
||||
Authority now understands two flavours of sender-constrained OAuth clients:
|
||||
|
||||
|
||||
| Section | Key | Description | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Root | `issuer` | Absolute HTTPS issuer advertised to clients. | Required. Loopback HTTP allowed only for development. |
|
||||
| Tokens | `accessTokenLifetime`, `refreshTokenLifetime`, etc. | Lifetimes for each grant (access, refresh, device, authorization code, identity). | Enforced during issuance; persisted on each token document. |
|
||||
| Storage | `storage.connectionString` | MongoDB connection string. | Required even for tests; offline kits ship snapshots for seeding. |
|
||||
| Signing | `signing.enabled` | Enable JWKS/revocation signing. | Disable only for development. |
|
||||
| Signing | `signing.algorithm` | Signing algorithm identifier. | Currently ES256; additional curves can be wired through crypto providers. |
|
||||
| Signing | `signing.keySource` | Loader identifier (`file`, `vault`, custom). | Determines which `IAuthoritySigningKeySource` resolves keys. |
|
||||
| Signing | `signing.keyPath` | Relative/absolute path understood by the loader. | Stored as-is; rotation request should keep it in sync with filesystem layout. |
|
||||
| Signing | `signing.activeKeyId` | Active JWKS / revocation signing key id. | Exposed as `kid` in JWKS and bundles. |
|
||||
| Signing | `signing.additionalKeys[].keyId` | Retired key identifier retained for verification. | Manager updates this automatically after rotation; keep YAML aligned. |
|
||||
| Signing | `signing.additionalKeys[].source` | Loader identifier per retired key. | Defaults to `signing.keySource` if omitted. |
|
||||
| Security | `security.rateLimiting` | Fixed-window limits for `/token`, `/authorize`, `/internal/*`. | See `docs/security/rate-limits.md` for tuning. |
|
||||
| Bootstrap | `bootstrap.apiKey` | Shared secret required for `/internal/*`. | Only required when `bootstrap.enabled` is true. |
|
||||
|
||||
### 7.1 Sender-constrained clients (DPoP & mTLS)
|
||||
|
||||
Authority now understands two flavours of sender-constrained OAuth clients:
|
||||
|
||||
- **DPoP proof-of-possession** – clients sign a `DPoP` header for `/token` requests. Authority validates the JWK thumbprint, HTTP method/URI, and replay window, then stamps the resulting access token with `cnf.jkt` so downstream services can verify the same key is reused.
|
||||
- Configure under `security.senderConstraints.dpop`. `allowedAlgorithms`, `proofLifetime`, and `replayWindow` are enforced at validation time.
|
||||
- `security.senderConstraints.dpop.nonce.enabled` enables nonce challenges for high-value audiences (`requiredAudiences`, normalised to case-insensitive strings). When a nonce is required but missing or expired, `/token` replies with `WWW-Authenticate: DPoP error="use_dpop_nonce"` (and, when available, a fresh `DPoP-Nonce` header). Clients must retry with the issued nonce embedded in the proof.
|
||||
@@ -256,8 +316,8 @@ Policy Engine v2 introduces dedicated scopes and a service identity that materia
|
||||
| Client | Scopes | Notes |
|
||||
| --- | --- | --- |
|
||||
| `policy-engine` (service) | `policy:run`, `findings:read`, `effective:write` | Must include `properties.serviceIdentity: policy-engine` and a tenant. Authority rejects `effective:write` tokens without the marker or tenant. |
|
||||
| `policy-cli` / automation | `policy:write`, `policy:submit`, `policy:run`, `findings:read` | Keep scopes minimal; only trusted automation should add `policy:approve`/`policy:activate`. |
|
||||
| UI/editor sessions | `policy:read`, `policy:write`, `policy:simulate` (+ reviewer/approver scopes as appropriate) | Issue tenant-specific clients so audit and rate limits remain scoped. |
|
||||
| `policy-cli` / automation | `policy:read`, `policy:author`, `policy:review`, `policy:simulate`, `findings:read` *(optionally add `policy:approve` / `policy:operate` / `policy:activate` for promotion pipelines)* | Keep scopes minimal; reroll CLI/CI tokens issued before 2025‑10‑27 so they drop legacy scope names and adopt the new set. |
|
||||
| UI/editor sessions | `policy:read`, `policy:author`, `policy:simulate` (+ reviewer/approver/operator scopes as appropriate) | Issue tenant-specific clients so audit and rate limits remain scoped. |
|
||||
|
||||
Sample YAML entry:
|
||||
|
||||
@@ -279,20 +339,42 @@ Sample YAML entry:
|
||||
Compliance checklist:
|
||||
|
||||
- [ ] `policy-engine` client includes `properties.serviceIdentity: policy-engine` and a tenant hint; logins missing either are rejected.
|
||||
- [ ] Non-service clients omit `effective:write` and receive only the scopes required for their role (`policy:write`, `policy:submit`, `policy:approve`, `policy:activate`, etc.).
|
||||
- [ ] Non-service clients omit `effective:write` and receive only the scopes required for their role (`policy:read`, `policy:author`, `policy:review`, `policy:approve`, `policy:operate`, `policy:simulate`, etc.).
|
||||
- [ ] Legacy tokens using `policy:write`/`policy:submit`/`policy:edit` are rotated to the new scope set before Production change freeze (see release migration note below).
|
||||
- [ ] Approval/activation workflows use identities distinct from authoring identities; tenants are provisioned per client to keep telemetry segregated.
|
||||
- [ ] Operators document reviewer assignments and incident procedures alongside `/docs/security/policy-governance.md` and archive policy evidence bundles (`stella policy bundle export`) with each release.
|
||||
|
||||
### 7.3 Orchestrator roles & scopes
|
||||
|
||||
| Role / Client | Scopes | Notes |
|
||||
| --- | --- | --- |
|
||||
| `Orch.Viewer` role | `orch:read` | Read-only access to Orchestrator dashboards, queues, and telemetry. |
|
||||
| `Orch.Operator` role | `orch:read`, `orch:operate` | Issue short-lived tokens for control actions (pause/resume, retry, sync). Token requests **must** include `operator_reason` (≤256 chars) and `operator_ticket` (≤128 chars); Authority rejects requests missing either value and records both in audit events. |
|
||||
|
||||
Token request example via client credentials:
|
||||
|
||||
```bash
|
||||
curl -u orch-operator:s3cr3t! \
|
||||
-d 'grant_type=client_credentials' \
|
||||
-d 'scope=orch:operate' \
|
||||
-d 'operator_reason=resume source after maintenance' \
|
||||
-d 'operator_ticket=INC-2045' \
|
||||
https://authority.example.com/token
|
||||
```
|
||||
|
||||
Tokens lacking `operator_reason` or `operator_ticket` receive `invalid_request`; audit events (`authority.client_credentials.grant`) surface the supplied values under `request.reason` and `request.ticket` for downstream review.
|
||||
CLI clients set these parameters via `Authority.OperatorReason` / `Authority.OperatorTicket` (environment variables `STELLAOPS_ORCH_REASON` and `STELLAOPS_ORCH_TICKET`).
|
||||
|
||||
## 8. Offline & Sovereign Operation
|
||||
- **No outbound dependencies:** Authority only contacts MongoDB and local plugins. Discovery and JWKS are cached by clients with offline tolerances (`AllowOfflineCacheFallback`, `OfflineCacheTolerance`). Operators should mirror these responses for air-gapped use.
|
||||
- **Structured logging:** Every revocation export, signing rotation, bootstrap action, and token issuance emits structured logs with `traceId`, `client_id`, `subjectId`, and `network.remoteIp` where applicable. Mirror logs to your SIEM to retain audit trails without central connectivity.
|
||||
- **Determinism:** Sorting rules in token and revocation exports guarantee byte-for-byte identical artefacts given the same datastore state. Hashes and signatures remain stable across machines.
|
||||
|
||||
## 9. Operational Checklist
|
||||
- [ ] Protect the bootstrap API key and disable bootstrap endpoints (`bootstrap.enabled: false`) once initial setup is complete.
|
||||
- [ ] Schedule `stella auth revoke export` (or `/internal/revocations/export`) at the same cadence as Concelier exports so bundles remain in lockstep.
|
||||
- [ ] Rotate signing keys before expiration; keep at least one retired key until all cached bundles/tokens signed with it have expired.
|
||||
- [ ] Monitor `/health` and `/ready` plus rate-limiter metrics to detect plugin outages early.
|
||||
- [ ] Ensure downstream services cache JWKS and revocation bundles within tolerances; stale caches risk accepting revoked tokens.
|
||||
|
||||
For plug-in specific requirements, refer to **[Authority Plug-in Developer Guide](dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md)**. For revocation bundle validation workflow, see **[Authority Revocation Bundle](security/revocation-bundle.md)**.
|
||||
|
||||
## 9. Operational Checklist
|
||||
- [ ] Protect the bootstrap API key and disable bootstrap endpoints (`bootstrap.enabled: false`) once initial setup is complete.
|
||||
- [ ] Schedule `stella auth revoke export` (or `/internal/revocations/export`) at the same cadence as Concelier exports so bundles remain in lockstep.
|
||||
- [ ] Rotate signing keys before expiration; keep at least one retired key until all cached bundles/tokens signed with it have expired.
|
||||
- [ ] Monitor `/health` and `/ready` plus rate-limiter metrics to detect plugin outages early.
|
||||
- [ ] Ensure downstream services cache JWKS and revocation bundles within tolerances; stale caches risk accepting revoked tokens.
|
||||
|
||||
For plug-in specific requirements, refer to **[Authority Plug-in Developer Guide](dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md)**. For revocation bundle validation workflow, see **[Authority Revocation Bundle](security/revocation-bundle.md)**.
|
||||
|
||||
@@ -68,6 +68,13 @@ Outputs:
|
||||
- `telemetry/telemetry-offline-bundle.tar.gz` + `.sha256` — packaged OTLP collector assets for environments without upstream access
|
||||
- `plugins/scanner/analyzers/lang/StellaOps.Scanner.Analyzers.Lang.Python/*.sig` (+ `.sha256`) — Cosign signatures for the Python analyzer DLL and manifest
|
||||
|
||||
### Policy Gateway configuration bundle
|
||||
|
||||
- Copy `etc/policy-gateway.yaml` (or the `*.sample` template if you expect operators to override values) into `config/policy-gateway/policy-gateway.yaml` within the staging tree.
|
||||
- Include the gateway DPoP private key under `secrets/policy-gateway/policy-gateway-dpop.pem` and reference the location inside the manifest notes. Set the permissions explicitly (`chmod 600 secrets/policy-gateway/policy-gateway-dpop.pem`) so only the kit importer can read it; the importer will refuse keys that are broader.
|
||||
- Document the gateway base URL and activation verification steps in `docs/policy/gateway.md` (bundled alongside the kit). Operators can use those curl snippets to smoke-test pack CRUD once the Offline Kit is imported.
|
||||
- Ensure the Prometheus snapshot captured during packaging contains `policy_gateway_activation_requests_total` so auditors can reconcile activation attempts performed via the gateway during the validation window.
|
||||
|
||||
Provide `--cosign-key` / `--cosign-identity-token` (and optional `--cosign-password`) to generate Cosign signatures for both the tarball and manifest.
|
||||
|
||||
---
|
||||
@@ -189,6 +196,30 @@ The CLI validates recorded digests (when `.metadata.json` is present) before str
|
||||
* Old feeds are kept until the new bundle is fully verified.
|
||||
* Import time on a SATA SSD: ≈ 25 s for a 300 MB kit.
|
||||
|
||||
### 2.1 Validator + idempotency enablement (air-gap)
|
||||
|
||||
The Offline Kit carries the same helper scripts under `scripts/`:
|
||||
|
||||
1. **Duplicate audit:** run
|
||||
```bash
|
||||
mongo concelier ops/devops/scripts/check-advisory-raw-duplicates.js --eval 'var LIMIT=200;'
|
||||
```
|
||||
to verify no `(vendor, upstream_id, content_hash, tenant)` conflicts remain before enabling the idempotency index.
|
||||
2. **Apply validators:** execute `mongo concelier ops/devops/scripts/apply-aoc-validators.js` (and the Excititor equivalent) with `validationLevel: "moderate"` in maintenance mode.
|
||||
3. **Restart Concelier** so migrations `20251028_advisory_raw_idempotency_index` and `20251028_advisory_supersedes_backfill` run automatically. After the restart:
|
||||
- Confirm `db.advisory` resolves to a view on `advisory_backup_20251028`.
|
||||
- Spot-check a few `advisory_raw` entries to ensure `supersedes` chains are populated deterministically.
|
||||
4. **Smoke test:** run `stella sources ingest --dry-run --fixture advisory` (bundled fixtures) to confirm ingestion succeeds post-guard and the CLI reports zero violations.
|
||||
|
||||
### Authority scope sanity check
|
||||
|
||||
Offline installs rely on the bundled `etc/authority.yaml.sample`. Before promoting the kit, confirm the sample clients keep the Aggregation-Only guardrails:
|
||||
|
||||
- `aoc-verifier` requests `aoc:verify`, `advisory:read`, and `vex:read`.
|
||||
- `signals-uploader` requests `signals:write`, `signals:read`, and `aoc:verify`.
|
||||
|
||||
Authority now rejects tokens that request `advisory:read`, `vex:read`, or any `signals:*` scope without `aoc:verify`; the sample has been updated to match. If you maintain tenant-specific overlays, mirror the same pairing so air-gapped automation fails deterministically with `invalid_scope` when misconfigured.
|
||||
|
||||
**Quick smoke test:** before import, verify the tarball carries the Go analyzer plug-in:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -103,6 +103,18 @@ src/
|
||||
* `whoami` — short auth display.
|
||||
* `version` — CLI + protocol versions; release channel.
|
||||
|
||||
### 2.9 Aggregation-only guard helpers
|
||||
|
||||
* `sources ingest --dry-run --source <id> --input <path|uri> [--tenant ... --format table|json --output file]`
|
||||
|
||||
* Normalises documents (handles gzip/base64), posts them to the backend `aoc/ingest/dry-run` route, and exits non-zero when guard violations are detected.
|
||||
* Defaults to table output with ANSI colour; `--json`/`--output` produce deterministic JSON for CI pipelines.
|
||||
|
||||
* `aoc verify [--since <ISO8601|duration>] [--limit <count>] [--sources list] [--codes list] [--format table|json] [--export file] [--tenant id] [--no-color]`
|
||||
|
||||
* Replays guard checks against stored raw documents. Maps backend `ERR_AOC_00x` codes onto deterministic exit codes so CI can block regressions.
|
||||
* Supports pagination hints (`--limit`, `--since`), tenant scoping via `--tenant` or `STELLA_TENANT`, and JSON exports for evidence lockers.
|
||||
|
||||
---
|
||||
|
||||
## 3) AuthN: Authority + DPoP
|
||||
@@ -154,6 +166,10 @@ CLI rejects verbs if required scopes are missing.
|
||||
| 6 | Rate limited / quota exceeded |
|
||||
| 7 | Backend unavailable (retryable) |
|
||||
| 9 | Invalid arguments |
|
||||
| 11–17 | Aggregation-only guard violation (`ERR_AOC_00x`) |
|
||||
| 18 | Verification truncated (increase `--limit`) |
|
||||
| 70 | Transport/authentication failure |
|
||||
| 71 | CLI usage error (missing tenant, invalid cursor) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -79,10 +79,19 @@ Everything here is open‑source and versioned — when you check out a git ta
|
||||
- **68 – [Policy Observability](observability/policy.md)**
|
||||
- **69 – [Console Observability](observability/ui-telemetry.md)**
|
||||
- **70 – [Policy Governance & Least Privilege](security/policy-governance.md)**
|
||||
- **70a – [Policy Gateway](policy/gateway.md)**
|
||||
- **71 – [Policy Examples](examples/policies/README.md)**
|
||||
- **72 – [Policy FAQ](faq/policy-faq.md)**
|
||||
- **73 – [Policy Run DTOs](../src/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md)**
|
||||
- **30 – [Fixture Maintenance](dev/fixtures.md)**
|
||||
- **74 – [Export Center Overview](export-center/overview.md)**
|
||||
- **75 – [Export Center Architecture](export-center/architecture.md)**
|
||||
- **76 – [Export Center Profiles](export-center/profiles.md)**
|
||||
- **77 – [Export Center API Reference](export-center/api.md)**
|
||||
- **78 – [Export Center CLI Guide](export-center/cli.md)**
|
||||
- **79 – [Export Center Trivy Adapters](export-center/trivy-adapter.md)**
|
||||
- **80 – [Export Center Mirror Bundles](export-center/mirror-bundles.md)**
|
||||
- **81 – [Export Center Provenance & Signing](export-center/provenance-and-signing.md)**
|
||||
|
||||
### User & operator guides
|
||||
- **14 – [Glossary](14_GLOSSARY_OF_TERMS.md)**
|
||||
@@ -118,6 +127,14 @@ Everything here is open‑source and versioned — when you check out a git ta
|
||||
- **42 – [Telemetry Storage Deployment](ops/telemetry-storage.md)**
|
||||
- **43 – [Authority Scopes & Tenancy](security/authority-scopes.md)**
|
||||
- **44 – [Container Deployment (AOC)](deploy/containers.md)**
|
||||
- **45 – [Export Center Operations Runbook](operations/export-runbook.md)**
|
||||
|
||||
### Notifications Studio
|
||||
- **81 – [Notifications Overview](notifications/overview.md)**
|
||||
- **82 – [Notifications Architecture](notifications/architecture.md)**
|
||||
- **83 – [Notifications Rules](notifications/rules.md)**
|
||||
- **84 – [Notifications Templates](notifications/templates.md)**
|
||||
- **85 – [Notifications Digests](notifications/digests.md)**
|
||||
|
||||
### Legal & licence
|
||||
- **32 – [Legal & Quota FAQ](29_LEGAL_FAQ_QUOTA.md)**
|
||||
|
||||
@@ -157,11 +157,11 @@
|
||||
| DOCS-CONSOLE-23-013 | DONE (2025-10-28) | Docs Guild, Observability Guild | TELEMETRY-CONSOLE-23-001, CONSOLE-QA-23-403 | Write `/docs/observability/ui-telemetry.md` cataloguing metrics/logs/traces, dashboards, alerts, and feature flags. | Doc merged with instrumentation tables, dashboard screenshots, checklist appended. |
|
||||
| DOCS-CONSOLE-23-014 | DONE (2025-10-28) | Docs Guild, Console Guild, CLI Guild | CONSOLE-DOC-23-502 | Maintain `/docs/cli-vs-ui-parity.md` matrix and integrate CI check guidance. | Matrix published with parity status, CI workflow documented, compliance checklist appended. |
|
||||
> 2025-10-28: Install Docker guide references pending CLI commands (`stella downloads manifest`, `stella downloads mirror`, `stella console status`). Update once CLI parity lands.
|
||||
| DOCS-CONSOLE-23-015 | TODO | Docs Guild, Architecture Guild | CONSOLE-CORE-23-001, WEB-CONSOLE-23-001 | Produce `/docs/architecture/console.md` describing frontend packages, data flow diagrams, SSE design, performance budgets. | Architecture doc merged with diagrams + compliance checklist; reviewers approve. |
|
||||
| DOCS-CONSOLE-23-015 | DONE (2025-10-27) | Docs Guild, Architecture Guild | CONSOLE-CORE-23-001, WEB-CONSOLE-23-001 | Produce `/docs/architecture/console.md` describing frontend packages, data flow diagrams, SSE design, performance budgets. | Architecture doc merged with diagrams + compliance checklist; reviewers approve. |
|
||||
| DOCS-CONSOLE-23-016 | DONE (2025-10-28) | Docs Guild, Accessibility Guild | CONSOLE-QA-23-402, CONSOLE-FEAT-23-102 | Refresh `/docs/accessibility.md` with Console-specific keyboard flows, color tokens, testing tools, and compliance checklist updates. | Accessibility doc updated; audits referenced; checklist appended. |
|
||||
> 2025-10-28: Added guide covering keyboard matrix, screen reader behaviour, colour/focus tokens, testing workflow, offline guidance, and compliance checklist.
|
||||
| DOCS-CONSOLE-23-017 | TODO | Docs Guild, Console Guild | CONSOLE-FEAT-23-101..109 | Create `/docs/examples/ui-tours.md` providing triage, audit, policy rollout walkthroughs with annotated screenshots and GIFs. | UI tours doc merged; media assets stored; compliance checklist appended. |
|
||||
| DOCS-CONSOLE-23-018 | TODO | Docs Guild, Security Guild | DOCS-CONSOLE-23-012 | Execute console security compliance checklist and capture Security Guild sign-off in Sprint 23 log. | Checklist completed; findings addressed or tickets filed; sign-off noted in updates file. |
|
||||
| DOCS-CONSOLE-23-017 | DONE (2025-10-27) | Docs Guild, Console Guild | CONSOLE-FEAT-23-101..109 | Create `/docs/examples/ui-tours.md` providing triage, audit, policy rollout walkthroughs with annotated screenshots and GIFs. | UI tours doc merged; capture instructions + asset placeholders committed; compliance checklist appended. |
|
||||
| DOCS-CONSOLE-23-018 | DONE (2025-10-27) | Docs Guild, Security Guild | DOCS-CONSOLE-23-012 | Execute console security compliance checklist and capture Security Guild sign-off in Sprint 23 log. | Checklist completed; findings addressed or tickets filed; sign-off noted in updates file. |
|
||||
| DOCS-LNM-22-006 | DONE (2025-10-27) | Docs Guild, Architecture Guild | CONCELIER-LNM-21-001..005, EXCITITOR-LNM-21-001..005 | Refresh `/docs/architecture/conseiller.md` and `/docs/architecture/excitator.md` describing observation/linkset pipelines and event contracts. | Architecture docs updated with observation/linkset flow + event tables; revisit once service implementations land. |
|
||||
> Follow-up: align diagrams/examples after `CONCELIER-LNM-21` & `EXCITITOR-LNM-21` work merges (currently TODO).
|
||||
| DOCS-LNM-22-007 | TODO | Docs Guild, Observability Guild | CONCELIER-LNM-21-005, EXCITITOR-LNM-21-005, DEVOPS-LNM-22-002 | Publish `/docs/observability/aggregation.md` with metrics/traces/logs/SLOs. | Observability doc merged; dashboards referenced; checklist appended. |
|
||||
@@ -229,16 +229,20 @@
|
||||
|
||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DOCS-EXPORT-35-001 | TODO | Docs Guild | EXPORT-SVC-35-001..006 | Author `/docs/export-center/overview.md` covering purpose, profiles, security, AOC alignment, surfaces, ending with imposed rule statement. | Doc merged with diagrams/examples; imposed rule line present; index updated. |
|
||||
| DOCS-EXPORT-35-002 | TODO | Docs Guild | EXPORT-SVC-35-002..005 | Publish `/docs/export-center/architecture.md` describing planner, adapters, manifests, signing, distribution flows, restating imposed rule. | Architecture doc merged; sequence diagrams included; rule statement appended. |
|
||||
| DOCS-EXPORT-35-003 | TODO | Docs Guild | EXPORT-SVC-35-003..004 | Publish `/docs/export-center/profiles.md` detailing schema fields, examples, compatibility, and imposed rule reminder. | Profiles doc merged; JSON schemas linked; imposed rule noted. |
|
||||
| DOCS-EXPORT-36-004 | TODO | Docs Guild | EXPORT-SVC-36-001..004, WEB-EXPORT-36-001 | Publish `/docs/export-center/api.md` covering endpoints, payloads, errors, and mention imposed rule. | API doc merged; examples validated; rule included. |
|
||||
| DOCS-EXPORT-36-005 | TODO | Docs Guild | CLI-EXPORT-35-001, CLI-EXPORT-36-001 | Publish `/docs/export-center/cli.md` with command reference, CI scripts, verification steps, restating imposed rule. | CLI doc merged; script snippets tested; rule appended. |
|
||||
| DOCS-EXPORT-36-006 | TODO | Docs Guild | EXPORT-SVC-36-001, DEVOPS-EXPORT-36-001 | Publish `/docs/export-center/trivy-adapter.md` covering field mappings, compatibility matrix, and imposed rule reminder. | Doc merged; mapping tables validated; rule included. |
|
||||
| DOCS-EXPORT-37-001 | TODO | Docs Guild | EXPORT-SVC-37-001, DEVOPS-EXPORT-37-001 | Publish `/docs/export-center/mirror-bundles.md` describing filesystem/OCI layouts, delta/encryption, import guide, ending with imposed rule. | Doc merged; diagrams provided; verification steps tested; rule stated. |
|
||||
| DOCS-EXPORT-37-002 | TODO | Docs Guild | EXPORT-SVC-35-005, EXPORT-SVC-37-002 | Publish `/docs/export-center/provenance-and-signing.md` detailing manifests, attestation flow, verification, reiterating imposed rule. | Doc merged; signature examples validated; rule appended. |
|
||||
| DOCS-EXPORT-37-003 | TODO | Docs Guild | DEVOPS-EXPORT-37-001 | Publish `/docs/operations/export-runbook.md` covering failures, tuning, capacity planning, with imposed rule reminder. | Runbook merged; procedures validated; rule included. |
|
||||
| DOCS-EXPORT-35-001 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-35-001..006 | Author `/docs/export-center/overview.md` covering purpose, profiles, security, AOC alignment, surfaces, ending with imposed rule statement. | Doc merged with diagrams/examples; imposed rule line present; index updated. |
|
||||
| DOCS-EXPORT-35-002 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-35-002..005 | Publish `/docs/export-center/architecture.md` describing planner, adapters, manifests, signing, distribution flows, restating imposed rule. | Architecture doc merged; sequence diagrams included; rule statement appended. |
|
||||
| DOCS-EXPORT-35-003 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-35-003..004 | Publish `/docs/export-center/profiles.md` detailing schema fields, examples, compatibility, and imposed rule reminder. | Profiles doc merged; JSON schemas linked; imposed rule noted. |
|
||||
| DOCS-EXPORT-36-004 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-36-001..004, WEB-EXPORT-36-001 | Publish `/docs/export-center/api.md` covering endpoints, payloads, errors, and mention imposed rule. | API doc merged; examples validated; rule included. |
|
||||
| DOCS-EXPORT-36-005 | DONE (2025-10-29) | Docs Guild | CLI-EXPORT-35-001, CLI-EXPORT-36-001 | Publish `/docs/export-center/cli.md` with command reference, CI scripts, verification steps, restating imposed rule. | CLI doc merged; script snippets tested; rule appended. |
|
||||
| DOCS-EXPORT-36-006 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-36-001, DEVOPS-EXPORT-36-001 | Publish `/docs/export-center/trivy-adapter.md` covering field mappings, compatibility matrix, and imposed rule reminder. | Doc merged; mapping tables validated; rule included. |
|
||||
| DOCS-EXPORT-37-001 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-37-001, DEVOPS-EXPORT-37-001 | Publish `/docs/export-center/mirror-bundles.md` describing filesystem/OCI layouts, delta/encryption, import guide, ending with imposed rule. | Doc merged; diagrams provided; verification steps tested; rule stated. |
|
||||
| DOCS-EXPORT-37-002 | DONE (2025-10-29) | Docs Guild | EXPORT-SVC-35-005, EXPORT-SVC-37-002 | Publish `/docs/export-center/provenance-and-signing.md` detailing manifests, attestation flow, verification, reiterating imposed rule. | Doc merged; signature examples validated; rule appended. |
|
||||
| DOCS-EXPORT-37-003 | DONE (2025-10-29) | Docs Guild | DEVOPS-EXPORT-37-001 | Publish `/docs/operations/export-runbook.md` covering failures, tuning, capacity planning, with imposed rule reminder. | Runbook merged; procedures validated; rule included. |
|
||||
| DOCS-EXPORT-37-004 | TODO | Docs Guild | AUTH-EXPORT-37-001, EXPORT-SVC-37-002 | Publish `/docs/security/export-hardening.md` outlining RBAC, tenancy, encryption, redaction, restating imposed rule. | Security doc merged; checklist updated; rule appended. |
|
||||
| DOCS-EXPORT-37-101 | TODO | Docs Guild, DevEx/CLI Guild | CLI-EXPORT-37-001 | Refresh CLI verification sections once `stella export verify` lands (flags, exit codes, samples). | `docs/export-center/cli.md` & `docs/export-center/provenance-and-signing.md` updated with final command syntax; examples tested; rule reminder retained. |
|
||||
| DOCS-EXPORT-37-102 | TODO | Docs Guild, DevOps Guild | DEVOPS-EXPORT-37-001 | Embed export dashboards/alerts references into provenance/runbook docs after Grafana work ships. | Docs updated with dashboard IDs/alert notes; update logged; rule reminder present. |
|
||||
| DOCS-EXPORT-37-005 | TODO | Docs Guild, Exporter Service Guild | EXPORT-SVC-35-006, DEVOPS-EXPORT-36-001 | Validate Export Center docs against live Trivy/mirror bundles once implementation lands; refresh examples and CLI snippets accordingly. | Real bundle examples recorded; docs updated; verification steps confirmed with production artefacts. |
|
||||
> Note (2025-10-29): Blocked until exporter API (`EXPORT-SVC-35-006`) and Trivy/mirror adapters (`EXPORT-SVC-36-001`, `EXPORT-SVC-37-001`) ship. Requires access to CI smoke outputs (`DEVOPS-EXPORT-36-001`) for verification artifacts.
|
||||
|
||||
## Reachability v1
|
||||
|
||||
@@ -349,8 +353,8 @@
|
||||
|
||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DOCS-NOTIFY-38-001 | TODO | Docs Guild, Notifications Service Guild | NOTIFY-SVC-38-001..004 | Publish `/docs/notifications/overview.md` and `/docs/notifications/architecture.md`, each ending with imposed rule reminder. | Docs merged; diagrams verified; imposed rule appended. |
|
||||
| DOCS-NOTIFY-39-002 | TODO | Docs Guild, Notifications Service Guild | NOTIFY-SVC-39-001..004 | Publish `/docs/notifications/rules.md`, `/docs/notifications/templates.md`, `/docs/notifications/digests.md` with examples and imposed rule line. | Docs merged; examples validated; imposed rule appended. |
|
||||
| DOCS-NOTIFY-38-001 | DONE (2025-10-29) | Docs Guild, Notifications Service Guild | NOTIFY-SVC-38-001..004 | Publish `/docs/notifications/overview.md` and `/docs/notifications/architecture.md`, each ending with imposed rule reminder. | Docs merged; diagrams verified; imposed rule appended. |
|
||||
| DOCS-NOTIFY-39-002 | DONE (2025-10-29) | Docs Guild, Notifications Service Guild | NOTIFY-SVC-39-001..004 | Publish `/docs/notifications/rules.md`, `/docs/notifications/templates.md`, `/docs/notifications/digests.md` with examples and imposed rule line. | Docs merged; examples validated; imposed rule appended. |
|
||||
| DOCS-NOTIFY-40-001 | TODO | Docs Guild, Security Guild | AUTH-NOTIFY-38-001, NOTIFY-SVC-40-001..004 | Publish `/docs/notifications/channels.md`, `/docs/notifications/escalations.md`, `/docs/notifications/api.md`, `/docs/operations/notifier-runbook.md`, `/docs/security/notifications-hardening.md`; each ends with imposed rule line. | Docs merged; accessibility checks passed; imposed rule appended. |
|
||||
|
||||
## CLI Parity & Task Packs
|
||||
@@ -359,7 +363,7 @@
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DOCS-CLI-41-001 | TODO | Docs Guild, DevEx/CLI Guild | CLI-CORE-41-001 | Publish `/docs/cli/overview.md`, `/docs/cli/configuration.md`, `/docs/cli/output-and-exit-codes.md` with imposed rule statements. | Docs merged; examples verified; imposed rule appended. |
|
||||
| DOCS-CLI-42-001 | TODO | Docs Guild | DOCS-CLI-41-001, CLI-PARITY-41-001 | Publish `/docs/cli/parity-matrix.md` and command guides under `/docs/cli/commands/*.md` (policy, sbom, vuln, vex, advisory, export, orchestrator, notify, aoc, auth). | Guides merged; parity automation documented; imposed rule appended. |
|
||||
| DOCS-PACKS-43-001 | TODO | Docs Guild, Task Runner Guild | PACKS-REG-42-001, TASKRUN-42-001 | Publish `/docs/task-packs/spec.md`, `/docs/task-packs/authoring-guide.md`, `/docs/task-packs/registry.md`, `/docs/task-packs/runbook.md`, `/docs/security/pack-signing-and-rbac.md`, `/docs/operations/cli-release-and-packaging.md` with imposed rule statements. | Docs merged; tutorials validated; imposed rule appended; cross-links added. |
|
||||
| DOCS-PACKS-43-001 | DONE (2025-10-27) | Docs Guild, Task Runner Guild | PACKS-REG-42-001, TASKRUN-42-001 | Publish `/docs/task-packs/spec.md`, `/docs/task-packs/authoring-guide.md`, `/docs/task-packs/registry.md`, `/docs/task-packs/runbook.md`, `/docs/security/pack-signing-and-rbac.md`, `/docs/operations/cli-release-and-packaging.md` with imposed rule statements. | Docs merged; tutorials validated; imposed rule appended; cross-links added. |
|
||||
|
||||
## Containerized Distribution (Epic 13)
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ Net result: partners and internal teams integrate quickly without reverse‑engi
|
||||
|
||||
### 3.1 Source of truth and layout
|
||||
|
||||
* Each service owns a **module‑scoped OAS** file: `src/StellaOps.Api.OpenApi/<service>/openapi.yaml`.
|
||||
* An aggregate spec `src/StellaOps.Api.OpenApi/stella.yaml` is produced by build tooling that composes per‑service specs, resolves `$ref`s, and validates cross‑service schemas.
|
||||
* Each service owns a **module-scoped OAS** file: `src/StellaOps.Api.OpenApi/<service>/openapi.yaml`.
|
||||
* Authority authentication/token surface now lives at `src/StellaOps.Api.OpenApi/authority/openapi.yaml`, covering `/token`, `/introspect`, `/revoke`, and `/jwks` flows with examples and scope catalog metadata.
|
||||
* An aggregate spec `src/StellaOps.Api.OpenApi/stella.yaml` is produced by build tooling that composes per-service specs, resolves `$ref`s, and validates cross-service schemas.
|
||||
* JSON Schema dialect: 2020‑12 (OpenAPI 3.1). No vendor‑specific features for core models.
|
||||
* Every response and error has at least one **validated example**.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ This document is the canonical reference for the Policy Engine REST surface desc
|
||||
## 1 · Authentication & Headers
|
||||
|
||||
- **Auth:** Bearer tokens (`Authorization: Bearer <token>`) with the following scopes as applicable:
|
||||
- `policy:read`, `policy:write`, `policy:submit`, `policy:approve`, `policy:run`, `policy:activate`, `policy:archive`, `policy:simulate`, `policy:runs`
|
||||
- `policy:read`, `policy:author`, `policy:review`, `policy:approve`, `policy:operate`, `policy:run`, `policy:activate`, `policy:archive`, `policy:simulate`, `policy:runs`
|
||||
- `findings:read` (for effective findings APIs)
|
||||
- `effective:write` (service identity only; not exposed to clients)
|
||||
- **Service identity:** Authority marks the Policy Engine client with `properties.serviceIdentity: policy-engine`. Tokens missing this marker cannot obtain `effective:write`.
|
||||
@@ -53,7 +53,7 @@ All errors use HTTP semantics plus a structured payload:
|
||||
|
||||
```
|
||||
POST /api/policy/policies
|
||||
Scopes: policy:write
|
||||
Scopes: policy:author
|
||||
```
|
||||
|
||||
**Request**
|
||||
@@ -106,7 +106,7 @@ Returns full DSL, metadata, provenance, simulation artefact references.
|
||||
|
||||
```
|
||||
PUT /api/policy/policies/{policyId}/versions/{version}
|
||||
Scopes: policy:write
|
||||
Scopes: policy:author
|
||||
```
|
||||
|
||||
Body identical to create. Only permitted while `status=draft`.
|
||||
@@ -119,7 +119,7 @@ Body identical to create. Only permitted while `status=draft`.
|
||||
|
||||
```
|
||||
POST /api/policy/policies/{policyId}/versions/{version}:submit
|
||||
Scopes: policy:submit
|
||||
Scopes: policy:author
|
||||
```
|
||||
|
||||
**Request**
|
||||
@@ -196,7 +196,7 @@ Request includes `reason` and optional `incidentId`.
|
||||
|
||||
```
|
||||
POST /api/policy/policies/{policyId}/versions/{version}:compile
|
||||
Scopes: policy:write
|
||||
Scopes: policy:author
|
||||
```
|
||||
|
||||
**Response 200**
|
||||
@@ -221,7 +221,7 @@ Scopes: policy:write
|
||||
|
||||
```
|
||||
POST /api/policy/policies/{policyId}/lint
|
||||
Scopes: policy:write
|
||||
Scopes: policy:author
|
||||
```
|
||||
|
||||
Slim wrapper used by CLI; returns 204 on success or `ERR_POL_001` payload.
|
||||
|
||||
210
docs/architecture/console.md
Normal file
210
docs/architecture/console.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# StellaOps Console Architecture (Sprint 23)
|
||||
|
||||
> **Ownership:** Console Guild • Docs Guild
|
||||
> **Delivery scope:** `StellaOps.Web` Angular workspace, Console Web Gateway routes (`/console/*`), Downloads manifest surfacing, SSE fan-out for Scheduler & telemetry.
|
||||
> **Related docs:** [Console overview](../ui/console-overview.md), [Navigation](../ui/navigation.md), [Runs workspace](../ui/runs.md), [Downloads](../ui/downloads.md), [Console security posture](../security/console-security.md), [Console observability](../observability/ui-telemetry.md), [Deployment guide](../deploy/console.md)
|
||||
|
||||
This dossier describes the end-to-end architecture of the StellaOps Console as delivered in Sprint 23. It covers the Angular workspace layout, API/gateway integration points, live-update channels, performance budgets, offline workflows, and observability hooks needed to keep the console deterministic and air-gap friendly.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Mission & Boundaries
|
||||
|
||||
- Present an operator-grade UI that surfaces Concelier, Excititor, Policy Engine, Scheduler, Attestor, and SBOM Service data **without** mutating aggregation or policy state.
|
||||
- Enforce Authority-issued scopes and tenant claims on every call through the Console Web Gateway.
|
||||
- Deliver deterministic builds (< 1 MB initial bundle) that can be mirrored in Offline Kits, with runtime configuration loaded from `/config.json`.
|
||||
- Stream live status (ingestion deltas, scheduler progress, telemetry) via SSE with graceful degradation to polling when offline or throttled.
|
||||
- Maintain CLI parity by embedding `stella` commands alongside interactive actions.
|
||||
|
||||
Non-goals: authoring ingestion logic, mutating Policy overlays, exposing internal Mongo collections, or performing cryptographic signing in-browser.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Workspace & Packages
|
||||
|
||||
The console is implemented in `src/StellaOps.Web`, an Angular 17 workspace built on standalone components and Signals.
|
||||
|
||||
| Path | Purpose | Highlights |
|
||||
|------|---------|------------|
|
||||
| `src/app/core/auth` | DPoP + PKCE authentication, Authority session store, HTTP interceptors. | WebCrypto keygen (`crypto.subtle`), session metadata persisted in `sessionStorage`, DPoP nonce replay guard. |
|
||||
| `src/app/core/api` | Typed API clients for Console gateway (`/console/*`) and downstream services. | DTOs for Scanner, Notify, Concelier exporters; fetch-based clients with abort signals. |
|
||||
| `src/app/core/config` | Runtime configuration loader (`/config.json`), feature flag gating. | Supports air-gap overrides and injects API base URLs, Authority issuer/client. |
|
||||
| `src/app/features/*` | Route-level shells (auth bootstrap, scans detail, notifications inbox, Trivy DB settings). | Each feature is a standalone module with lazy loading and Angular Signals state. |
|
||||
| `src/app/testing` | Fixtures and harnesses used in unit tests and storybook-like previews. | Deterministic data used for Playwright and Jest scenarios. |
|
||||
|
||||
Workspace characteristics:
|
||||
|
||||
- **Toolchain:** Node 20.11+, npm 10.2+, Angular CLI 17.3. `npm run ci:install` primes dependencies without network audits; `scripts/verify-chromium.js` ensures headless Chromium availability for Karma.
|
||||
- **Build budgets:** `angular.json` enforces 500 KB warning / 1 MB error for initial bundle and 2 KB warning / 4 KB error per component stylesheet. Output hashing (`outputHashing: all`) keeps assets cache-safe.
|
||||
- **Testing:** Karma + Jasmine for unit tests, Playwright for e2e with dev server autotuning. CI (`DEVOPS-CONSOLE-23-001`) runs Lighthouse against the production bundle.
|
||||
- **Runtime config:** `/config.json` merged at bootstrap; gateways can rewrite it on the fly to avoid rebuilding for environment changes.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Runtime Topology & Data Flow
|
||||
|
||||
The console SPA relies on the Console Web Gateway to proxy tenant-scoped API calls to downstream services. Tenant isolation and Aggregation-Only guardrails are enforced at every hop.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Browser["Browser (Angular SPA)"]
|
||||
UI[Console Shell<br/>(Signals, Feature Modules)]
|
||||
SSE[EventSource / SSE Clients]
|
||||
end
|
||||
subgraph Gateway["Console Web Gateway"]
|
||||
Router[Minimal API / ASP.NET Core Router]
|
||||
StatusCache[Status Cache & Manifest signer]
|
||||
end
|
||||
Authority[Authority<br/>(DPoP + PKCE)]
|
||||
Concelier[Concelier.WebService]
|
||||
Excititor[Excititor.WebService]
|
||||
Scheduler[Scheduler.WebService]
|
||||
Policy[Policy Engine API]
|
||||
SBOM[SBOM Service]
|
||||
Attestor[Attestor API]
|
||||
Downloads[Downloads Manifest Store]
|
||||
|
||||
UI -->|/config.json| Gateway
|
||||
UI -->|/console/* (Bearer+DPoP)| Router
|
||||
SSE -->|/console/status/stream| Router
|
||||
Router --> Authority
|
||||
Router --> Concelier
|
||||
Router --> Excititor
|
||||
Router --> Scheduler
|
||||
Router --> Policy
|
||||
Router --> SBOM
|
||||
Router --> Attestor
|
||||
Router --> Downloads
|
||||
StatusCache -.-> Gateway
|
||||
Gateway -.-> UI
|
||||
```
|
||||
|
||||
Key interactions:
|
||||
|
||||
- **Auth bootstrap:** UI retrieves Authority metadata and exchanges an authorization code + PKCE verifier for a DPoP-bound token (`aud=console`, `tenant=<id>`). Tokens expire in 120 s; refresh tokens rotate, triggering new DPoP proofs.
|
||||
- **Tenant switch:** Picker issues `Authority /fresh-auth` when required, then refreshes UI caches (`ui.tenant.switch` log). Gateway injects `X-Stella-Tenant` headers downstream.
|
||||
- **Aggregation-only reads:** Gateway proxies `/console/advisories`, `/console/vex`, `/console/findings`, etc., without mutating Concelier or Policy data. Provenance badges and merge hashes come directly from upstream responses.
|
||||
- **Downloads parity:** `/console/downloads` merges DevOps signed manifest and Offline Kit metadata; UI renders digest, signature, and CLI parity command.
|
||||
- **Offline resilience:** Gateway exposes `/console/status` heartbeat. If unavailable, UI enters offline mode, disables SSE, and surfaces CLI fallbacks.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Live Updates & SSE Design
|
||||
|
||||
Live surfaces use HTTP/1.1 SSE with heartbeat frames to keep operators informed without polling storms.
|
||||
|
||||
| Endpoint | Payload | Source | Behaviour |
|
||||
|----------|---------|--------|-----------|
|
||||
| `/console/status/stream` | `statusChanged`, `ingestionDelta`, `attestorQueue`, `offlineBanner` events | Concelier WebService, Excititor WebService, Attestor metrics | 5 s heartbeat; gateway disables proxy buffering (`X-Accel-Buffering: no`) and sets `Cache-Control: no-store`. |
|
||||
| `/console/runs/{id}/stream` | `stateChanged`, `segmentProgress`, `deltaSummary`, `log` | Scheduler WebService SSE fan-out | Event payloads carry `traceId`, `runId`, `tenant`; UI reconnects with exponential backoff and resumes using `Last-Event-ID`. |
|
||||
| `/console/telemetry/stream` | `metricSample`, `alert`, `collectorStatus` | Observability aggregator | Gated by `ui.telemetry` scope; disabled when `CONSOLE_TELEMETRY_SSE_ENABLED=false`. |
|
||||
|
||||
Sequence overview:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant UI as Console SPA
|
||||
participant GW as Console Gateway
|
||||
participant SCHED as Scheduler WebService
|
||||
|
||||
UI->>GW: GET /console/runs/42/stream (Authorization + DPoP)
|
||||
GW->>SCHED: GET /runs/42/stream (X-Stella-Tenant)
|
||||
SCHED-->>GW: event: stateChanged data: {...}
|
||||
GW-->>UI: event: stateChanged data: {..., traceId}
|
||||
Note over UI,GW: Gateway injects retry-after + heartbeat every 15s
|
||||
UI-->>GW: (disconnect)
|
||||
UI->>GW: GET /console/runs/42/stream (Last-Event-ID: <seq>)
|
||||
GW->>SCHED: GET /runs/42/stream?since=<seq>
|
||||
```
|
||||
|
||||
Offline behaviour:
|
||||
|
||||
- If SSE fails three times within 60 s, UI falls back to polling (`/console/status`, `/console/runs/{id}`) every 30 s and shows an amber banner.
|
||||
- When `console.offlineMode=true`, SSE endpoints return `204` immediately; UI suppresses auto-reconnect to preserve resources.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Performance & Budgets
|
||||
|
||||
| Surface | Target | Enforcement |
|
||||
|---------|--------|-------------|
|
||||
| First meaningful paint (dashboard) | ≤ 2.5 s on 4 vCPU offline runner | Lighthouse CI gate (`DEVOPS-CONSOLE-23-001`), `ui_route_render_seconds` P95 alert. |
|
||||
| Route hydration (feature shells) | ≤ 1.5 s after token acquisition | Angular Signals + lazy loading; route-level budgets tracked via custom telemetry. |
|
||||
| Initial bundle size | Warn ≥ 500 KB, fail ≥ 1 MB | `angular.json` budgets; CI fails build on overflow. |
|
||||
| Component stylesheet | Warn ≥ 2 KB, fail ≥ 4 KB | `angular.json` budgets; ensures Tailwind utilities stay tree-shaken. |
|
||||
| SSE heartbeat | Every 15 s max | Gateway emits comment heartbeats; UI resets timers on each frame. |
|
||||
|
||||
Optimisation levers:
|
||||
|
||||
- Standalone components with `ChangeDetectionStrategy.OnPush` and Angular Signals avoid zone.js churn.
|
||||
- `fetch` + AbortController guard double fetches.
|
||||
- Assets served with immutable caching (`cache-control: public, max-age=31536000, immutable`) thanks to hashed filenames.
|
||||
- Compression (gzip/brotli) enabled at gateway; offline bundles include precompressed assets.
|
||||
- Command palette, tenants, and filters rely on IndexedDB caches to avoid refetching static metadata.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Offline & Configuration Workflows
|
||||
|
||||
- **Config manifest:** `/config.json` includes Authority issuer/client ID, gateway base URL, feature flags, telemetry endpoints, and offline hints. Operators can swap config by copying `src/config/config.sample.json` and editing before build, or by rewriting the response at gateway runtime.
|
||||
- **Deterministic install:** Documented in `src/StellaOps.Web/docs/DeterministicInstall.md`—`npm run ci:install` plus Chromium provisioning ensures offline runners reproduce builds.
|
||||
- **Offline Kit parity:** UI validates downloads manifest signatures (cosign) and surfaces snapshot timestamps per tenant. When offline, buttons switch to CLI snippets (`stella runs export`, `stella downloads sync`).
|
||||
- **Feature flags:** `CONSOLE_FEATURE_FLAGS` toggles modules (`runs`, `downloads`, `telemetry`); offline bundles include flag manifest so UI can render only supported panes.
|
||||
- **Snapshot awareness:** Global banner shows snapshot timestamp and disables actions needing Authority fresh-auth when running in sealed mode.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Security & Tenancy
|
||||
|
||||
- **DPoP + PKCE:** Every request carries `Authorization` + `DPoP` header and gateway enforces nonce replay protection. Private keys live in IndexedDB and never leave the browser.
|
||||
- **Scope enforcement:** Gateway checks scope claims before proxying (`ui.read`, `runs.manage`, `downloads.read`, etc.) and propagates denials as `Problem+JSON` with `ERR_*` codes.
|
||||
- **Tenant propagation:** `X-Stella-Tenant` header derived from token; downstream services reject mismatches. Tenant switches log `ui.tenant.switch` and require fresh-auth for privileged actions.
|
||||
- **CSP & headers:** Default CSP forbids third-party scripts, only allows same-origin `connect-src`. HSTS, Referrer-Policy `no-referrer`, and `Permissions-Policy` configured via gateway (`deploy/console.md`).
|
||||
- **Evidence handling:** Downloads never cache secrets; UI renders SHA-256 + signature references and steers users to CLI for sensitive exports.
|
||||
- See [Console security posture](../security/console-security.md) for full scope table and threat model alignment.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Observability & Telemetry
|
||||
|
||||
- **Metrics:** Prometheus scrape at `/metrics` (enabled when `CONSOLE_METRICS_ENABLED=true`). Key histograms/counters documented in [Console observability](../observability/ui-telemetry.md) (`ui_route_render_seconds`, `ui_tenant_switch_total`, `ui_download_manifest_refresh_seconds`).
|
||||
- **Logs:** Structured JSON with `traceId`, `tenant`, `action`. Categories include `ui.action`, `ui.tenant.switch`, `ui.security.anomaly`. Sampled per feature flag to balance volume.
|
||||
- **Traces:** Browser OTLP exporter ships spans to configured collector; gateway adds server-side spans so traces cross client/server boundary.
|
||||
- **Alerts:** Burn-rate rules for route latency, telemetry batch failures, download manifest refresh, and SSE stalls integrate with Notifier.
|
||||
- **Correlation:** SSE events carry `traceId` so operators can jump from UI to backend logs using shared correlation IDs.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Integration Points & Dependencies
|
||||
|
||||
| Service | Console dependency | Notes |
|
||||
|---------|-------------------|-------|
|
||||
| Authority | OIDC, DPoP tokens, tenant catalog, fresh-auth | Requires client `console-ui` with scopes listed in security guide. |
|
||||
| Concelier WebService | `/console/advisories`, feed health, export triggers | Gateway must enforce Aggregation-Only guardrails and surface merge hashes. |
|
||||
| Excititor WebService | `/console/vex`, consensus overlays | SSE ticker shows provider deltas. |
|
||||
| Policy Engine | Findings views, policy previews, simulation diffs | Console never writes overlays; uses `effective_finding_*` data via API. |
|
||||
| Scheduler WebService | Runs dashboard, SSE streams, queue metrics | Heartbeat drives status ticker; cancellation actions require `runs.manage`. |
|
||||
| SBOM Service | SBOM explorer tree, component lookup | Responses cached per tenant; offline bundles preload snapshots. |
|
||||
| Attestor | Attestation verification, evidence links | Console displays verification status and CLI parity commands. |
|
||||
| DevOps downloads pipeline | Signed manifest for `/console/downloads` | Manifest signatures validated with cosign key shipped in Offline Kit. |
|
||||
|
||||
---
|
||||
|
||||
## 10 · Compliance Checklist
|
||||
|
||||
- [ ] Frontend package map (core/auth/api/config + feature shells) documented with ownership and tooling details.
|
||||
- [ ] Data flow diagram captures SPA ↔ Gateway ↔ downstream services with tenant & scope enforcement notes.
|
||||
- [ ] SSE design documented (endpoints, payloads, heartbeat, retry/backoff, offline fallback).
|
||||
- [ ] Performance budgets (< 1 MB initial bundle, route hydration ≤ 1.5 s, SSE heartbeat) stated alongside enforcement mechanisms.
|
||||
- [ ] Offline workflows (`/config.json`, deterministic install, Offline Kit parity) described with operator guidance.
|
||||
- [ ] Security section references DPoP, scopes, CSP, evidence handling, and tenancy propagation.
|
||||
- [ ] Observability metrics/logs/traces coverage listed with alert hooks.
|
||||
- [ ] Integration dependencies table links Console responsibilities to upstream services.
|
||||
- [ ] Document cross-references validated (UI guides, security, observability, deployment).
|
||||
- [ ] Last updated timestamp refreshed after review.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 23).*
|
||||
|
||||
13
docs/assets/ui/tours/README.md
Normal file
13
docs/assets/ui/tours/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# UI Tours Media Assets
|
||||
|
||||
Store annotated screenshots and GIFs referenced by `/docs/examples/ui-tours.md` in this directory. Use the naming convention documented in the guide (e.g., `triage-step-01.png`, `triage-flow.gif`).
|
||||
|
||||
## Contribution checklist
|
||||
|
||||
- Capture at 1920×1080 resolution unless otherwise specified.
|
||||
- Add annotations using the shared Docs Guild template (narrow callouts, numbered badges).
|
||||
- Optimize images to stay below 2 MB (PNG) and 8 MB (GIF) while preserving legibility.
|
||||
- Record GIFs at ≤30 seconds using 12–15 fps for balance between smoothness and size.
|
||||
- Update the capture checklist in `docs/examples/ui-tours.md` when assets are added or replaced.
|
||||
- Commit binaries using Git LFS if size exceeds repository limits; otherwise store directly.
|
||||
- Include the console build hash in the asset metadata or caption, matching the Downloads manifest version.
|
||||
@@ -10,7 +10,7 @@
|
||||
| `IMAGE` | The image you are building & scanning | `acme/backend:sha-${COMMIT_SHA}` |
|
||||
| `SBOM_FILE` | Immutable SBOM name – `<image-ref>‑YYYYMMDDThhmmssZ.sbom.json` | `acme_backend_sha‑abc123‑20250804T153050Z.sbom.json` |
|
||||
|
||||
> **Authority graph scopes note (2025‑10‑27):** CI stages that spin up the Authority compose profile now rely on the checked-in `etc/authority.yaml`. Before running integration smoke jobs, inject real secrets for every `etc/secrets/*.secret` file (Cartographer, Graph API, Policy Engine, Concelier, Excititor). The repository defaults contain `*-change-me` placeholders and Authority will reject tokens if those secrets are not overridden.
|
||||
> **Authority graph scopes note (2025-10-27):** CI stages that spin up the Authority compose profile now rely on the checked-in `etc/authority.yaml`. Before running integration smoke jobs, inject real secrets for every `etc/secrets/*.secret` file (Cartographer, Graph API, Policy Engine, Concelier, Excititor). The repository defaults contain `*-change-me` placeholders and Authority will reject tokens if those secrets are not overridden. Reissue CI tokens that previously used `policy:write`/`policy:submit`/`policy:edit` scopes—new bundles must request `policy:read`, `policy:author`, `policy:review`, `policy:simulate`, and (`policy:approve`/`policy:operate`/`policy:activate` when pipelines promote policies).
|
||||
|
||||
```bash
|
||||
export STELLA_URL="stella-ops.ci.acme.example"
|
||||
@@ -264,6 +264,12 @@ python -m pip install --upgrade pip
|
||||
python -m pip install markdown pygments
|
||||
```
|
||||
|
||||
> **No `pip` available?** Some hardened Python builds (including the repo’s `tmp/docenv`
|
||||
> interpreter) ship without `pip`/`ensurepip`. In that case download the pure‑Python
|
||||
> sdists (e.g. `Markdown-3.x.tar.gz`, `pygments-2.x.tar.gz`) and extract their
|
||||
> packages directly into the virtualenv’s `lib/python*/site-packages/` folder.
|
||||
> This keeps the renderer working even when package managers are disabled.
|
||||
|
||||
**Offline tip.** Add the packages above to your artifact mirror (for example `ops/devops/offline-kit.json`) so runners can install them via `npm --offline` / `pip --no-index`.
|
||||
|
||||
### 4.2 Schema validation step
|
||||
|
||||
@@ -152,6 +152,8 @@ Replays the AOC guard against stored raw documents. By default it checks all adv
|
||||
| `--tenant <tenant-id>` | Overrides tenant context. Required for cross-tenant verifications when run by platform operators. |
|
||||
| `--no-color` | Disables ANSI colours. |
|
||||
|
||||
`table` mode prints a summary showing the active tenant, evaluated window, counts of checked advisories/VEX statements, the active limit, total writes/violations, and whether the page was truncated. Status is colour-coded as `ok`, `violations`, or `truncated`. When violations exist the detail table lists the code, total occurrences, first sample document (`source` + `documentId` + `contentHash`), and JSON pointer path.
|
||||
|
||||
### 3.4 Report structure (JSON)
|
||||
|
||||
```json
|
||||
@@ -182,7 +184,8 @@ Replays the AOC guard against stored raw documents. By default it checks all adv
|
||||
"metrics": {
|
||||
"ingestion_write_total": 557,
|
||||
"aoc_violation_total": 2
|
||||
}
|
||||
},
|
||||
"truncated": false
|
||||
}
|
||||
```
|
||||
|
||||
@@ -262,6 +265,24 @@ Use these codes in CI to map outcomes to build statuses or alert severities.
|
||||
|
||||
---
|
||||
|
||||
## 4 · `stella vuln observations` (Overlay paging)
|
||||
|
||||
`stella vuln observations` lists raw advisory observations for downstream overlays (Graph Explorer, Policy simulations, Console). Large tenants can now page through results deterministically.
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--limit <count>` | Caps the number of observations returned in a single call. Defaults to `200`; values above `500` are clamped server-side. |
|
||||
| `--cursor <token>` | Opaque continuation token produced by the previous page (`nextCursor` in JSON output). Pass it back to resume iteration. |
|
||||
|
||||
Additional notes:
|
||||
|
||||
- Table mode prints a hint when `hasMore` is `true`:
|
||||
`[yellow]More observations available. Continue with --cursor <token>[/]`.
|
||||
- JSON mode returns `nextCursor` and `hasMore` alongside the observation list so automation can loop until `hasMore` is `false`.
|
||||
- Supplying a non-positive limit falls back to the default (`200`). Invalid/expired cursors yield `400 Bad Request`; restart without `--cursor` to begin a fresh iteration.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Related references
|
||||
|
||||
- [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md)
|
||||
@@ -282,4 +303,14 @@ Use these codes in CI to map outcomes to build statuses or alert severities.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 19).*
|
||||
*Last updated: 2025-10-29 (Sprint 24).*
|
||||
|
||||
## 13. Authority configuration quick reference
|
||||
|
||||
| Setting | Purpose | How to set |
|
||||
|---------|---------|------------|
|
||||
| `StellaOps:Authority:OperatorReason` | Incident/change description recorded with `orch:operate` tokens. | CLI flag `--Authority:OperatorReason=...` or env `STELLAOPS_ORCH_REASON`. |
|
||||
| `StellaOps:Authority:OperatorTicket` | Change/incident ticket reference paired with orchestrator control actions. | CLI flag `--Authority:OperatorTicket=...` or env `STELLAOPS_ORCH_TICKET`. |
|
||||
|
||||
> Tokens requesting `orch:operate` will fail with `invalid_request` unless both values are present. Choose concise strings (≤256 chars for reason, ≤128 chars for ticket) and avoid sensitive data.
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> **Audience:** Policy authors, reviewers, operators, and CI engineers using the `stella` CLI to interact with Policy Engine.
|
||||
> **Supported from:** `stella` CLI ≥ 0.20.0 (Policy Engine v2 sprint line).
|
||||
> **Prerequisites:** Authority-issued bearer token with the scopes noted per command (export `STELLA_TOKEN` or pass `--token`).
|
||||
> **2025-10-27 scope update:** CLI/CI tokens issued prior to Sprint 23 (AUTH-POLICY-23-001) must drop `policy:write`/`policy:submit`/`policy:edit` and instead request `policy:read`, `policy:author`, `policy:review`, and `policy:simulate` (plus `policy:approve`/`policy:operate`/`policy:activate` for promotion pipelines).
|
||||
|
||||
---
|
||||
|
||||
@@ -129,6 +130,23 @@ stella policy activate P-7 --version 4 --run-now --priority high
|
||||
- Optional `--scheduled-at 2025-10-27T02:00:00Z`.
|
||||
- Requires `policy:activate` and `policy:run`.
|
||||
|
||||
**Options**
|
||||
|
||||
- `--version <number>` (required) – target revision to promote.
|
||||
- `--note <text>` – record an activation note alongside the approval.
|
||||
- `--run-now` – enqueue an immediate full run after activation.
|
||||
- `--scheduled-at <timestamp>` – schedule activation for a specific UTC time (ISO-8601 format).
|
||||
- `--priority <label>` – optional scheduling priority hint (`low`, `standard`, `high`).
|
||||
- `--rollback` – mark the activation as a rollback of a previously active version.
|
||||
- `--incident <id>` – associate the activation with an incident identifier.
|
||||
|
||||
**Exit codes**
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `0` | Activation completed (or policy already active). |
|
||||
| `75` | Activation recorded but awaiting a second approver. |
|
||||
|
||||
### 3.5 Archive / Rollback
|
||||
|
||||
```
|
||||
@@ -226,6 +244,8 @@ Replay downloads sealed bundle for deterministic verification.
|
||||
stella findings ls --policy P-7 \
|
||||
--sbom sbom:S-42 \
|
||||
--status affected --severity High,Critical \
|
||||
--since 2025-10-01T00:00:00Z \
|
||||
--page 2 --page-size 100 \
|
||||
--format table
|
||||
```
|
||||
|
||||
@@ -233,18 +253,25 @@ Common flags:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--page`, `--page-size` | Pagination (default page size 50). |
|
||||
| `--cursor` | Use cursor token from previous call. |
|
||||
| `--since` | ISO timestamp filter. |
|
||||
| `--sbom` | Repeatable filter for SBOM identifiers. |
|
||||
| `--status` | Repeatable filter (`affected`, `quieted`, `mitigated`, `not_affected`, etc.). |
|
||||
| `--severity` | Repeatable filter using normalized labels (`Critical`, `High`, `Medium`, `Low`, `Unknown`). |
|
||||
| `--since` | Return findings updated on/after the ISO-8601 timestamp. |
|
||||
| `--cursor` | Resume listing using the opaque token from a prior page. |
|
||||
| `--page`, `--page-size` | Page-based pagination (page >=1, size <=500; falls back to backend defaults). |
|
||||
| `--output` | Persist JSON payload to disk (implied JSON rendering). |
|
||||
| `--format` | `table` (default for TTY) or `json`. |
|
||||
|
||||
### 5.2 Fetch Explain
|
||||
|
||||
```
|
||||
stella findings explain --policy P-7 --finding P-7:S-42:pkg:npm/lodash@4.17.21:CVE-2021-23337 \
|
||||
stella findings explain --policy P-7 \
|
||||
P-7:S-42:pkg:npm/lodash@4.17.21:CVE-2021-23337 \
|
||||
--mode verbose \
|
||||
--format json --output explains/lodash.json
|
||||
```
|
||||
|
||||
Outputs ordered rule hits, inputs, and sealed-mode hints.
|
||||
Outputs ordered rule hits, inputs, evidence snapshots, and sealed-mode hints. Supported `--mode` values mirror API contracts (for example `summary`, `verbose`); omit to use backend default.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ This guide supplements existing deployment manuals with AOC-specific configurati
|
||||
### 1.1 MongoDB validators
|
||||
|
||||
- Apply JSON schema validators to `advisory_raw` and `vex_raw` collections before enabling AOC guards.
|
||||
- Before enabling validators or the idempotency index, run the duplicate audit helper to confirm no conflicting raw advisories remain:
|
||||
```bash
|
||||
mongo concelier ops/devops/scripts/check-advisory-raw-duplicates.js --eval 'var LIMIT=200;'
|
||||
```
|
||||
Resolve any reported rows prior to rollout.
|
||||
- Use the migration script provided in `ops/devops/scripts/apply-aoc-validators.js`:
|
||||
|
||||
```bash
|
||||
@@ -31,6 +36,26 @@ kubectl exec -n excititor deploy/excititor-mongo -- \
|
||||
2. Roll out Concelier/Excititor images with guard middleware enabled (`AOC_GUARD_ENABLED=true`).
|
||||
3. Run smoke tests (`stella sources ingest --dry-run` fixtures) before resuming production ingestion.
|
||||
|
||||
### 1.3 Supersedes backfill verification
|
||||
|
||||
1. **Duplicate audit:** Confirm `mongo concelier ops/devops/scripts/check-advisory-raw-duplicates.js --eval 'var LIMIT=200;'` reports no conflicts before restarting Concelier with the new migrations.
|
||||
2. **Post-migration check:** After the service restarts, validate that `db.advisory` is a view pointing to `advisory_backup_20251028`:
|
||||
```bash
|
||||
mongo concelier --quiet --eval 'db.getCollectionInfos({ name: "advisory" })[0]'
|
||||
```
|
||||
The `type` should be `"view"` and `options.viewOn` should equal `"advisory_backup_20251028"`.
|
||||
3. **Supersedes chain spot-check:** Inspect a sample set to ensure deterministic chaining:
|
||||
```bash
|
||||
mongo concelier --quiet --eval '
|
||||
db.advisory_raw.aggregate([
|
||||
{ $match: { "upstream.upstream_id": { $exists: true } } },
|
||||
{ $sort: { "tenant": 1, "source.vendor": 1, "upstream.upstream_id": 1, "upstream.retrieved_at": 1 } },
|
||||
{ $limit: 5 },
|
||||
{ $project: { _id: 1, supersedes: 1 } }
|
||||
]).forEach(printjson)'
|
||||
```
|
||||
Each revision should reference the previous `_id` (or `null` for the first revision). Record findings in the change ticket before proceeding to production.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Container environment flags
|
||||
|
||||
154
docs/examples/ui-tours.md
Normal file
154
docs/examples/ui-tours.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# StellaOps Console – Guided Tours (Sprint 23)
|
||||
|
||||
> **Audience:** Field enablement, Docs Guild writers, Console product leads, and onboarding facilitators.
|
||||
> **Scope:** Ready-to-run walkthrough scripts that showcase the Console’s critical workflows—triage, audit evidence, and policy rollout—while reinforcing CLI parity, tenancy, and offline expectations.
|
||||
|
||||
These tours stitch together the primary Console workspaces so trainers can deliver consistent demos or capture annotated media (screenshots/GIFs). Each tour lists prerequisites, live steps, CLI fallbacks, and assets to capture. Use them alongside the workspace dossiers in `/docs/ui/*.md` when preparing customer sessions or internal dry runs.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Prerequisites & Setup
|
||||
|
||||
- **Environment:** Console deployed per [deployment guide](../deploy/console.md) with Scheduler, Policy Engine, Concelier, Excititor, SBOM Service, and Downloads manifest available.
|
||||
- **Tenant & data:** Sample tenant populated with recent scans, findings, runs, and export bundles. Ensure Offline Kit snapshot exists for offline callouts.
|
||||
- **Scopes:** Presenter identity must hold `ui.read`, `findings.read`, `policy:*` (read/write/simulate/approve), `runs.read`, `downloads.read`, `aoc:verify`, and `ui.telemetry` to surface telemetry banners.
|
||||
- **Browser tooling:** Enable screen recording (1920×1080 @ 60 fps) and keyboard overlay if capturing walkthroughs.
|
||||
- **CLI parity:** Have `stella` CLI configured against the same tenant; keep terminal window ready for parity steps.
|
||||
- **Assets directory:** Store captures under `docs/assets/ui/tours/` (see [`README`](../assets/ui/tours/README.md)) with the naming convention `<tour>-step-<nn>.png` and `<tour>-flow.gif`.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Tour A — Critical Finding Triage
|
||||
|
||||
**Persona:** Security analyst responding to a fresh high-severity finding.
|
||||
**Goal:** Navigate from dashboard signal to remediation decision, highlighting explain trails and run evidence.
|
||||
|
||||
### 2.1 Key references
|
||||
- [Console overview](../ui/console-overview.md) – tenant switching, status ticker.
|
||||
- [Navigation](../ui/navigation.md) – command palette, shortcuts.
|
||||
- [Findings workspace](../ui/findings.md) – filters, explain drawer, exports.
|
||||
- [Runs workspace](../ui/runs.md) – live progress, evidence downloads.
|
||||
|
||||
### 2.2 Live walkthrough
|
||||
1. **Start on Dashboard:** Show status ticker surfacing new `Critical` badge. Call out tenant pill and offline banner behaviour (§3 of console overview).
|
||||
2. **Command palette jump:** Press `Ctrl/Cmd+K`, type `Findings`, hit `Enter`. Narrate keyboard accessibility from navigation guide.
|
||||
3. **Apply global filters:** Open filter tray (`Shift+F`), set `Severity = Critical`, `Status = affected`, time window `Last 24h`. Mention saved view presets triggered with `Ctrl/Cmd+1`.
|
||||
4. **Open explain drawer:** Select top finding, trigger `Explain` tab. Highlight rule chain, VEX impact, and evidence references (§5 of findings doc).
|
||||
5. **Dive into related run:** Click `Run ID` link inside explain drawer → opens Runs detail drawer filtered by run ID. Show segmented progress SSE updates.
|
||||
6. **Capture evidence:** In Runs drawer, download evidence bundle; note CLI parity `stella runs export --run <id>`. Mention offline fallback (download queue offline banner from runs doc §10).
|
||||
7. **Escalate / create ticket:** Use bulk action or comment (if configured) to demonstrate optional integration; mention Authority audit log tie-in.
|
||||
8. **Wrap with CLI:** Pop terminal and run `stella findings explain --policy <id> --finding <key> --format markdown` to show reproducibility.
|
||||
|
||||
### 2.3 Capture checklist
|
||||
- `docs/assets/ui/tours/triage-step-01.png` — dashboard ticker highlighting new criticals.
|
||||

|
||||
- `docs/assets/ui/tours/triage-step-03.png` — filter tray with severity/time window applied.
|
||||

|
||||
- `docs/assets/ui/tours/triage-step-04.png` — explain drawer evidence tab.
|
||||

|
||||
- `docs/assets/ui/tours/triage-flow.gif` — 20 s screen recording of steps 1–5 with annotations.
|
||||

|
||||
|
||||
### 2.4 Talking points & callouts
|
||||
- Call out Aggregation-Only boundaries: findings reference Concelier/Excititor provenance, UI stays read-only.
|
||||
- Mention `ui_route_render_seconds` telemetry for demos (see [observability guide](../observability/ui-telemetry.md)).
|
||||
- Offline note: highlight offline banner that appears if `/console/status` heartbeat fails (§6 of console overview).
|
||||
|
||||
---
|
||||
|
||||
## 3 · Tour B — Audit Evidence Export
|
||||
|
||||
**Persona:** Compliance lead compiling artefacts for an external audit.
|
||||
**Goal:** Retrieve signed manifests, export run/finding evidence, and verify parity with Offline Kit.
|
||||
|
||||
### 3.1 Key references
|
||||
- [Downloads workspace](../ui/downloads.md) – manifest, parity, export queue.
|
||||
- [Runs workspace](../ui/runs.md) – evidence panel.
|
||||
- [Console security posture](../security/console-security.md) – evidence handling.
|
||||
- [CLI vs UI parity matrix](../cli-vs-ui-parity.md).
|
||||
|
||||
### 3.2 Live walkthrough
|
||||
1. **Open Downloads:** Use left rail or command palette to reach `/console/downloads`. Point out snapshot banner, cosign verification status.
|
||||
2. **Verify manifest:** Click “Verify signature” quick action; narrate parity with `cosign verify --key <key> manifest.json` from downloads doc §3.
|
||||
3. **Compare Offline Kit:** Switch to “Offline Kits” tab, run parity check to ensure kit digest matches manifest. Demonstrate offline guidance (downloads doc §6).
|
||||
4. **Queue evidence bundle:** Navigate to Runs workspace, choose relevant run, trigger “Bundle for offline” (runs doc §8).
|
||||
5. **Return to Downloads → Exports tab:** Show newly generated evidence bundle with retention countdown.
|
||||
6. **Download & inspect:** Open detail drawer, copy CLI command `stella runs export --run <id> --bundle`. Mention location for storing evidence.
|
||||
7. **Log parity results:** Use notes or tags to flag audit package completion (if notifications configured).
|
||||
8. **CLI parity close-out:** Run `stella downloads manifest --channel stable` to mirror UI manifest retrieval. Confirm digests match.
|
||||
|
||||
### 3.3 Capture checklist
|
||||
- `docs/assets/ui/tours/audit-step-02.png` — manifest verification banner (green).
|
||||

|
||||
- `docs/assets/ui/tours/audit-step-05.png` — exports tab showing evidence bundle ready.
|
||||

|
||||
- `docs/assets/ui/tours/audit-flow.gif` — 25 s capture from manifest view through export download.
|
||||

|
||||
|
||||
### 3.4 Talking points & callouts
|
||||
- Stress deterministic manifests and Cosign signatures; reference deployment doc for TLS/CSP alignment.
|
||||
- Highlight audit trail: downloads actions recorded via `ui.download.commandCopied` logs and Authority audit entries.
|
||||
- Offline note: show guidance when parity check detects stale manifest; mention CLI fallback for sealed networks.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Tour C — Policy Rollout & Promotion
|
||||
|
||||
**Persona:** Policy owner preparing and promoting a new ruleset.
|
||||
**Goal:** Draft review, simulation, approval, and promotion within Console, with CLI parity.
|
||||
|
||||
### 4.1 Key references
|
||||
- [Policies workspace](../ui/policies.md) – simulations, approvals, promotion.
|
||||
- [Policy editor](../ui/policy-editor.md) – Monaco editor, linting.
|
||||
- [Runs workspace](../ui/runs.md) – policy run monitoring.
|
||||
- [Security posture](../security/console-security.md) – fresh-auth and scopes.
|
||||
|
||||
### 4.2 Live walkthrough
|
||||
1. **Policy overview:** Open `/console/policies`, filter by “Staged” state. Highlight list columns (owners, pending approvals).
|
||||
2. **Enter draft:** Select policy → open editor view. Show checklist sidebar (lint, simulation, determinism).
|
||||
3. **Run lint & simulation:** Hit `Run lint`, then `Run simulation`. Narrate asynchronous progress with SSE ticker; reference CLI `stella policy simulate`.
|
||||
4. **Review diff:** Open simulation diff view to compare Active vs Staged; highlight severity up/down badges (§6 of policies doc).
|
||||
5. **Approval workflow:** Assign reviewer, show comment thread. Trigger fresh-auth prompt when clicking “Submit for review” (security doc §1.2).
|
||||
6. **Promote policy:** After approvals, open promotion dialog, choose “Full run”. Emphasise policy run scheduling and RBAC.
|
||||
7. **Monitor run:** Jump to Runs workspace, filter by policy run; show progress segments and findings delta metrics.
|
||||
8. **Publish CLI parity:** Execute `stella policy promote --policy <id> --revision <rev> --run-mode full` to reinforce reproducibility.
|
||||
|
||||
### 4.3 Capture checklist
|
||||
- `docs/assets/ui/tours/policy-step-02.png` — editor checklist with lint/simulation statuses.
|
||||

|
||||
- `docs/assets/ui/tours/policy-step-04.png` — simulation diff comparing Active vs Staged.
|
||||

|
||||
- `docs/assets/ui/tours/policy-flow.gif` — 30 s clip from draft view through promotion confirmation.
|
||||

|
||||
|
||||
### 4.4 Talking points & callouts
|
||||
- Stress governance: approvals logged with correlation IDs, fresh-auth enforced.
|
||||
- Mention telemetry metrics (`ui_tenant_switch_total`, policy run charts) for monitoring adoption.
|
||||
- Offline note: show how promotion dialog surfaces CLI script when in sealed mode; reference offline guidance in policies doc §10.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Production Tips & Media Hygiene
|
||||
|
||||
- **Script timing:** Keep each tour ≤ 3 minutes live demo, ≤ 30 s GIF. Include captions for accessibility.
|
||||
- **Annotations:** Use consistent callouts (numbered badges, short labels) overlayed in post-processing; ensure final media compressed but legible (< 2 MB PNG, < 8 MB GIF). See `docs/assets/ui/tours/README.md` for shared template guidance.
|
||||
- **Versioning:** Annotated assets should include Console build hash in metadata or caption (align with `/console/downloads` manifest version).
|
||||
- **Storage:** Commit final media under `docs/assets/ui/tours/` and update `.gitattributes` if smudge filters required. Note large GIFs may need Git LFS depending on repository policy.
|
||||
- **Review cadence:** Re-run tours whenever workspaces change navigation or introduce new buttons; log updates in `docs/updates/<date>-console-tours.md` (create if absent).
|
||||
|
||||
---
|
||||
|
||||
## 6 · Compliance Checklist
|
||||
|
||||
- [x] Tour scripts cover triage, audit evidence, and policy rollout scenarios requested in DOCS-CONSOLE-23-017.
|
||||
- [x] Each tour references authoritative workspace docs and CLI parity commands.
|
||||
- [x] Capture checklist names align with `docs/assets/ui/tours/` convention.
|
||||
- [x] Offline and sealed-mode notes included for every flow.
|
||||
- [x] Security considerations (scopes, fresh-auth, evidence handling) highlighted.
|
||||
- [x] Observability/telemetry pointers surfaced to support Ops follow-up.
|
||||
- [x] Media hygiene guidance documented (assets, compression, versioning).
|
||||
- [x] Document timestamp reflects Sprint 23 delivery.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 23).*
|
||||
337
docs/export-center/api.md
Normal file
337
docs/export-center/api.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# Export Center REST API
|
||||
|
||||
> **Audience:** Platform integrators, Console/CLI developers, and automation engineers orchestrating export runs.
|
||||
> **Base route:** `/api/export/*` behind the StellaOps gateway; requires Authority-issued tokens with export scopes.
|
||||
|
||||
This reference describes the Export Center API introduced in Export Center Phase 1 (Epic 10) and extended in Phase 2. Use it alongside the [Export Center Architecture](architecture.md) and [Profiles](profiles.md) guides for service-level semantics.
|
||||
|
||||
> Status: Endpoint implementation lands with `EXPORT-SVC-35-006` (Sprint 35) and related follow-on tasks. As of the current build the WebService hosts only the template stub; use this contract for coordination and update once the API is wired.
|
||||
|
||||
## 1. Authentication and headers
|
||||
|
||||
- **Authorization:** Bearer tokens in `Authorization: Bearer <token>` paired with DPoP proof. Required scopes per endpoint:
|
||||
- `export:profile:manage` for profile CRUD.
|
||||
- `export:run` to submit and cancel runs.
|
||||
- `export:read` to list and inspect runs.
|
||||
- `export:download` for bundle downloads and manifests.
|
||||
- **Tenant context:** Provide `X-Stella-Tenant` when the token carries multiple tenants; defaults to token tenant otherwise.
|
||||
- **Idempotency:** Mutating endpoints accept `Idempotency-Key` (UUID). Retrying with the same key returns the original result.
|
||||
- **Rate limits and quotas:** Responses include `X-Stella-Quota-Limit`, `X-Stella-Quota-Remaining`, and `X-Stella-Quota-Reset`. Exceeding quotas returns `429 Too Many Requests` with `ERR_EXPORT_QUOTA`.
|
||||
- **Content negotiation:** Requests and responses use `application/json; charset=utf-8` unless otherwise stated. Downloads stream binary content with profile-specific media types.
|
||||
- **SSE:** Event streams set `Content-Type: text/event-stream` and keep connections alive with comment heartbeats every 15 seconds.
|
||||
|
||||
## 2. Error model
|
||||
|
||||
Errors follow standard HTTP codes with structured payloads:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "ERR_EXPORT_002",
|
||||
"message": "Profile not found for tenant acme",
|
||||
"details": [],
|
||||
"traceId": "01J9N4Y4K2XY8C5V7T2S",
|
||||
"timestamp": "2025-10-29T13:42:11Z"
|
||||
}
|
||||
```
|
||||
|
||||
| Code | Description | Typical HTTP status | Notes |
|
||||
|------|-------------|---------------------|-------|
|
||||
| `ERR_EXPORT_001` | Validation failure (selectors, configuration) | 400 | `details` enumerates offending fields. |
|
||||
| `ERR_EXPORT_002` | Profile missing or not accessible for tenant | 404 | Returned on run submission or profile fetch. |
|
||||
| `ERR_EXPORT_003` | Concurrency or quota exceeded | 429 | Includes `retryAfterSeconds` in `details`. |
|
||||
| `ERR_EXPORT_004` | Adapter failure (schema mismatch, upstream outage) | 502 | Worker logs contain adapter error reason. |
|
||||
| `ERR_EXPORT_005` | Signing or KMS error | 500 | Run marked failed with `errorCode=signing`. |
|
||||
| `ERR_EXPORT_006` | Distribution failure (HTTP, OCI, object storage) | 502 | `details` lists failing distribution driver. |
|
||||
| `ERR_EXPORT_007` | Run canceled or expired | 409 | Includes cancel author and timestamp. |
|
||||
| `ERR_EXPORT_BASE_MISSING` | Base manifest for delta exports not found | 400 | Specific to `mirror:delta`. |
|
||||
| `ERR_EXPORT_EMPTY` | No records matched selectors (when `allowEmpty=false`) | 422 | Useful for guard-railled automation. |
|
||||
| `ERR_EXPORT_QUOTA` | Daily quota exhausted | 429 | Always paired with quota headers. |
|
||||
|
||||
All responses include `traceId` for correlation with logs and metrics.
|
||||
|
||||
## 3. Profiles endpoints
|
||||
|
||||
### 3.1 List profiles
|
||||
|
||||
```
|
||||
GET /api/export/profiles?kind=json&variant=raw&page=1&pageSize=20
|
||||
Scopes: export:read
|
||||
```
|
||||
|
||||
Returns tenant-scoped profiles. Response headers: `X-Total-Count`, `Link` for pagination.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"profileId": "prof-json-raw",
|
||||
"name": "Daily JSON Raw",
|
||||
"kind": "json",
|
||||
"variant": "raw",
|
||||
"distribution": ["http", "object"],
|
||||
"retention": {"mode": "days", "value": 14},
|
||||
"createdAt": "2025-10-23T08:00:00Z",
|
||||
"createdBy": "user:ops"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Get a profile
|
||||
|
||||
```
|
||||
GET /api/export/profiles/{profileId}
|
||||
Scopes: export:read
|
||||
```
|
||||
|
||||
Returns full configuration, including `config` payload, distribution options, and metadata.
|
||||
|
||||
### 3.3 Create a profile
|
||||
|
||||
```
|
||||
POST /api/export/profiles
|
||||
Scopes: export:profile:manage
|
||||
```
|
||||
|
||||
**Request**
|
||||
|
||||
```json
|
||||
{
|
||||
"profileId": "prof-airgap-mirror",
|
||||
"name": "Airgap Mirror Weekly",
|
||||
"kind": "mirror",
|
||||
"variant": "full",
|
||||
"include": ["advisories", "vex", "sboms", "policy"],
|
||||
"distribution": ["http", "object"],
|
||||
"encryption": {
|
||||
"enabled": true,
|
||||
"recipientKeys": ["age1tenantkey..."],
|
||||
"strict": false
|
||||
},
|
||||
"retention": {"mode": "days", "value": 30}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 201**
|
||||
|
||||
```json
|
||||
{
|
||||
"profileId": "prof-airgap-mirror",
|
||||
"version": 1,
|
||||
"createdAt": "2025-10-29T12:05:22Z",
|
||||
"createdBy": "user:ops",
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Update profile metadata
|
||||
|
||||
```
|
||||
PATCH /api/export/profiles/{profileId}
|
||||
Scopes: export:profile:manage
|
||||
```
|
||||
|
||||
Allows renaming, toggling distribution switches, or updating retention. Structural configuration updates (kind/variant/include) create a new revision; the API returns `revisionCreated=true` and the new `profileId` (e.g., `prof-airgap-mirror@2`).
|
||||
|
||||
### 3.5 Archive profile
|
||||
|
||||
```
|
||||
POST /api/export/profiles/{profileId}:archive
|
||||
Scopes: export:profile:manage
|
||||
```
|
||||
|
||||
Marks profile as inactive; existing runs remain accessible. Use `:restore` to reactivate.
|
||||
|
||||
## 4. Run management
|
||||
|
||||
### 4.1 Submit an export run
|
||||
|
||||
```
|
||||
POST /api/export/runs
|
||||
Scopes: export:run
|
||||
```
|
||||
|
||||
**Request**
|
||||
|
||||
```json
|
||||
{
|
||||
"profileId": "prof-json-raw",
|
||||
"selectors": {
|
||||
"tenants": ["acme"],
|
||||
"timeWindow": {
|
||||
"from": "2025-10-01T00:00:00Z",
|
||||
"to": "2025-10-29T00:00:00Z"
|
||||
},
|
||||
"products": ["registry.example.com/app:*"],
|
||||
"sboms": ["sbom:S-1001", "sbom:S-2004"]
|
||||
},
|
||||
"policySnapshotId": "policy-snap-42",
|
||||
"options": {
|
||||
"allowEmpty": false,
|
||||
"priority": "standard"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response 202**
|
||||
|
||||
```json
|
||||
{
|
||||
"runId": "run-20251029-01",
|
||||
"status": "pending",
|
||||
"profileId": "prof-json-raw",
|
||||
"createdAt": "2025-10-29T12:12:11Z",
|
||||
"createdBy": "user:ops",
|
||||
"selectors": { "...": "..." },
|
||||
"links": {
|
||||
"self": "/api/export/runs/run-20251029-01",
|
||||
"events": "/api/export/runs/run-20251029-01/events"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 List runs
|
||||
|
||||
```
|
||||
GET /api/export/runs?status=active&profileId=prof-json-raw&page=1&pageSize=10
|
||||
Scopes: export:read
|
||||
```
|
||||
|
||||
Returns latest runs with pagination. Each item includes summary counts, duration, and last event.
|
||||
|
||||
### 4.3 Get run status
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}
|
||||
Scopes: export:read
|
||||
```
|
||||
|
||||
Response fields:
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `status` | `pending`, `running`, `success`, `failed`, `canceled`. |
|
||||
| `progress` | Object with `adapters`, `bytesWritten`, `recordsProcessed`. |
|
||||
| `errorCode` | Populated when `status=failed` (`signing`, `distribution`, etc). |
|
||||
| `policySnapshotId` | Returned for policy-aware profiles. |
|
||||
| `distributions` | List of available distribution descriptors (type, location, sha256, expiresAt). |
|
||||
|
||||
### 4.4 Cancel a run
|
||||
|
||||
```
|
||||
POST /api/export/runs/{runId}:cancel
|
||||
Scopes: export:run
|
||||
```
|
||||
|
||||
Body optional (`{"reason": "Aborted due to incident INC-123"}`). Returns 202 and pushes `run.canceled` event.
|
||||
|
||||
## 5. Events and telemetry
|
||||
|
||||
### 5.1 Server-sent events
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}/events
|
||||
Scopes: export:read
|
||||
Accept: text/event-stream
|
||||
```
|
||||
|
||||
Event payload example:
|
||||
|
||||
```
|
||||
event: run.progress
|
||||
data: {"runId":"run-20251029-01","phase":"adapter","adapter":"json","records":1024,"bytes":7340032,"timestamp":"2025-10-29T12:13:15Z"}
|
||||
```
|
||||
|
||||
Event types:
|
||||
|
||||
| Event | Meaning |
|
||||
|-------|---------|
|
||||
| `run.accepted` | Planner accepted job and queued with Orchestrator. |
|
||||
| `run.progress` | Periodic updates with phase, adapter, counts. |
|
||||
| `run.distribution` | Distribution driver finished (includes descriptor). |
|
||||
| `run.signed` | Signing completed successfully. |
|
||||
| `run.succeeded` | Run marked `success`. |
|
||||
| `run.failed` | Run failed; payload includes `errorCode`. |
|
||||
| `run.canceled` | Run canceled; includes `canceledBy`. |
|
||||
|
||||
SSE heartbeats (`: ping`) keep long-lived connections alive and should be ignored by clients.
|
||||
|
||||
### 5.2 Audit events
|
||||
|
||||
`GET /api/export/runs/{runId}/events?format=audit` returns the same event stream in newline-delimited JSON for offline ingestion.
|
||||
|
||||
## 6. Download endpoints
|
||||
|
||||
### 6.1 Bundle download
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}/download
|
||||
Scopes: export:download
|
||||
```
|
||||
|
||||
Streams the primary bundle (tarball, zip, or profile-specific layout). Headers:
|
||||
|
||||
- `Content-Disposition: attachment; filename="export-run-20251029-01.tar.zst"`
|
||||
- `X-Export-Digest: sha256:...`
|
||||
- `X-Export-Size: 73482019`
|
||||
- `X-Export-Encryption: age` (when mirror encryption enabled)
|
||||
|
||||
Supports HTTP range requests for resume functionality. If no bundle exists yet, responds `409` with `ERR_EXPORT_007`.
|
||||
|
||||
### 6.2 Manifest download
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}/manifest
|
||||
Scopes: export:download
|
||||
```
|
||||
|
||||
Returns signed `export.json`. To fetch the detached signature, append `?signature=true`.
|
||||
|
||||
### 6.3 Provenance download
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}/provenance
|
||||
Scopes: export:download
|
||||
```
|
||||
|
||||
Returns signed `provenance.json`. Supports `?signature=true`. Provenance includes attestation subject digests, policy snapshot ids, adapter versions, and KMS key identifiers.
|
||||
|
||||
### 6.4 Distribution descriptors
|
||||
|
||||
```
|
||||
GET /api/export/runs/{runId}/distributions
|
||||
Scopes: export:read
|
||||
```
|
||||
|
||||
Lists all registered distribution targets (HTTP, OCI, object storage). Each item includes `type`, `location`, `sha256`, `sizeBytes`, and `expiresAt`.
|
||||
|
||||
## 7. Webhook hand-off
|
||||
|
||||
Exports can notify external systems once a run succeeds by registering an HTTP webhook:
|
||||
|
||||
```
|
||||
POST /api/export/webhooks
|
||||
Scopes: export:profile:manage
|
||||
```
|
||||
|
||||
Payload includes `targetUrl`, `events` (e.g., `run.succeeded`), and optional secret for HMAC signatures. Webhook deliveries sign payloads with `X-Stella-Signature` header (`sha256=...`). Retries follow exponential backoff with dead-letter capture in `export_events`.
|
||||
|
||||
## 8. Observability
|
||||
|
||||
- **Metrics endpoint:** `/metrics` (service-local) exposes Prometheus metrics listed in [Architecture](architecture.md#observability).
|
||||
- **Tracing:** When `traceparent` header is provided, worker spans join the calling trace.
|
||||
- **Run lookup by trace:** Use `GET /api/export/runs?traceId={id}` when troubleshooting distributed traces.
|
||||
|
||||
## 9. Related documentation
|
||||
|
||||
- [Export Center Overview](overview.md)
|
||||
- [Export Center Architecture](architecture.md)
|
||||
- [Export Center Profiles](profiles.md)
|
||||
- [Export Center CLI Guide](cli.md) *(companion document)*
|
||||
- [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md)
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
125
docs/export-center/architecture.md
Normal file
125
docs/export-center/architecture.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Export Center Architecture
|
||||
|
||||
The Export Center is the dedicated service layer that packages StellaOps evidence and policy overlays into reproducible bundles. It runs as a multi-surface API backed by asynchronous workers and format adapters, enforcing Aggregation-Only Contract (AOC) guardrails while providing deterministic manifests, signing, and distribution paths.
|
||||
|
||||
## Runtime topology
|
||||
- **Export Center API (`StellaOps.ExportCenter.WebService`).** Receives profile CRUD, export run requests, status queries, and download streams through the unified Web API gateway. Enforces tenant scopes, RBAC, quotas, and concurrency guards.
|
||||
- **Export Center Worker (`StellaOps.ExportCenter.Worker`).** Dequeues export jobs from the Orchestrator, resolves selectors, invokes adapters, and writes manifests and bundle artefacts. Stateless; scales horizontally.
|
||||
- **Backing stores.**
|
||||
- MongoDB collections: `export_profiles`, `export_runs`, `export_inputs`, `export_distributions`, `export_events`.
|
||||
- Object storage bucket or filesystem for staging bundle payloads.
|
||||
- Optional registry/object storage credentials injected via Authority-scoped secrets.
|
||||
- **Integration peers.**
|
||||
- **Findings Ledger** for advisory, VEX, SBOM payload streaming.
|
||||
- **Policy Engine** for deterministic policy snapshots and evaluated findings.
|
||||
- **Orchestrator** for job scheduling, quotas, and telemetry fan-out.
|
||||
- **Authority** for tenant-aware access tokens and KMS key references.
|
||||
- **Console & CLI** as presentation surfaces consuming the API.
|
||||
|
||||
## Job lifecycle
|
||||
1. **Profile selection.** Operator or automation picks a profile (`json:raw`, `json:policy`, `trivy:db`, `trivy:java-db`, `mirror:full`, `mirror:delta`) and submits scope selectors (tenant, time window, products, SBOM subjects, ecosystems). See `docs/export-center/profiles.md` for profile definitions and configuration fields.
|
||||
2. **Planner resolution.** API validates selectors, expands include/exclude lists, and writes a pending `export_run` with immutable parameters and deterministic ordering hints.
|
||||
3. **Orchestrator dispatch.** `export_run` triggers a job lease via Orchestrator with quotas per tenant/profile and concurrency caps (default 4 active per tenant).
|
||||
4. **Worker execution.** Worker streams data from Findings Ledger and Policy Engine using pagination cursors. Adapters write canonical payloads to staging storage, compute checksums, and emit streaming progress events (SSE).
|
||||
5. **Manifest and provenance emission.** Worker writes `export.json` and `provenance.json`, signs them with configured KMS keys (cosign-compatible), and uploads signatures alongside content.
|
||||
6. **Distribution registration.** Worker records available distribution methods (download URL, OCI reference, object storage path), raises completion/failure events, and exposes metrics/logs.
|
||||
7. **Download & verification.** Clients download bundles or pull OCI artefacts, verify signatures, and consume provenance to trace source artefacts.
|
||||
|
||||
Cancellation requests mark runs as `aborted` and cause workers to stop iterating sources; partially written files are destroyed and the run is marked with an audit entry.
|
||||
|
||||
## Core components
|
||||
### API surface
|
||||
- Detailed request and response payloads are catalogued in `docs/export-center/api.md`.
|
||||
- **Profiles API.**
|
||||
- `GET /api/export/profiles`: list tenant-scoped profiles.
|
||||
- `POST /api/export/profiles`: create custom profiles (variants of JSON, Trivy, mirror) with validated configuration schema.
|
||||
- `PATCH /api/export/profiles/{id}`: update metadata; config changes clone new revision to preserve determinism.
|
||||
- **Runs API.**
|
||||
- `POST /api/export/runs`: submit export run for a profile with selectors and options (policy snapshot id, mirror base manifest).
|
||||
- `GET /api/export/runs/{id}`: status, progress counters, provenance summary.
|
||||
- `GET /api/export/runs/{id}/events`: server-sent events with state transitions, adapter milestones, signing status.
|
||||
- `POST /api/export/runs/{id}/cancel`: cooperative cancellation with audit logging.
|
||||
- **Downloads API.**
|
||||
- `GET /api/export/runs/{id}/download`: streaming download with range support and checksum trailers.
|
||||
- `GET /api/export/runs/{id}/manifest`: signed `export.json`.
|
||||
- `GET /api/export/runs/{id}/provenance`: signed `provenance.json`.
|
||||
|
||||
All endpoints require Authority-issued JWT + DPoP tokens with scopes `export:run`, `export:read`, and tenant claim alignment. Rate-limiting and quotas surface via `X-Stella-Quota-*` headers.
|
||||
|
||||
### Worker pipeline
|
||||
- **Input resolvers.** Query Findings Ledger and Policy Engine using stable pagination (Mongo `_id` ascending, or resume tokens for change streams). Selector expressions compile into Mongo filter fragments and/or API query parameters.
|
||||
- **Adapter host.** Adapter plugin loader (restart-time only) resolves profile variant to adapter implementation. Adapters present a deterministic `RunAsync(context)` contract with streaming writers and telemetry instrumentation.
|
||||
- **Content writers.**
|
||||
- JSON adapters emit `.jsonl.zst` files with canonical ordering (tenant, subject, document id).
|
||||
- Trivy adapters materialise SQLite databases or tar archives matching Trivy DB expectations; schema version gates prevent unsupported outputs.
|
||||
- Mirror adapters assemble deterministic filesystem trees (manifests, indexes, payload subtrees) and, when configured, OCI artefact layers.
|
||||
- **Manifest generator.** Aggregates counts, bytes, hash digests (SHA-256), profile metadata, and input references. Writes `export.json` and `provenance.json` using canonical JSON (sorted keys, RFC3339 UTC timestamps).
|
||||
- **Signing service.** Integrates with platform KMS via Authority (default cosign signer). Produces in-toto SLSA attestations when configured. Supports detached signatures and optional in-bundle signatures.
|
||||
- **Distribution drivers.** `dist-http` exposes staged files via download endpoint; `dist-oci` pushes artefacts to registries using ORAS with digest pinning; `dist-objstore` uploads to tenant-specific prefixes with immutability flags.
|
||||
|
||||
## Data model snapshots
|
||||
|
||||
| Collection | Purpose | Key fields | Notes |
|
||||
|------------|---------|------------|-------|
|
||||
| `export_profiles` | Profile definitions (kind, variant, config). | `_id`, `tenant`, `name`, `kind`, `variant`, `config_json`, `created_by`, `created_at`. | Config includes adapter parameters (included record types, compression, encryption). |
|
||||
| `export_runs` | Run state machine and audit info. | `_id`, `profile_id`, `tenant`, `status`, `requested_by`, `selectors`, `policy_snapshot_id`, `started_at`, `completed_at`, `duration_ms`, `error_code`. | Immutable selectors; status transitions recorded in `export_events`. |
|
||||
| `export_inputs` | Resolved input ranges. | `run_id`, `source`, `cursor`, `count`, `hash`. | Enables resumable retries and audit. |
|
||||
| `export_distributions` | Distribution artefacts. | `run_id`, `type` (`http`, `oci`, `object`), `location`, `sha256`, `size_bytes`, `expires_at`. | `expires_at` used for retention policies and automatic pruning. |
|
||||
| `export_events` | Timeline of state transitions and metrics. | `run_id`, `event_type`, `message`, `at`, `metrics`. | Feeds SSE stream and audit trails. |
|
||||
|
||||
## Adapter responsibilities
|
||||
- **JSON (`json:raw`, `json:policy`).**
|
||||
- Ensures canonical casing, timezone normalization, and linkset preservation.
|
||||
- Policy variant embeds policy snapshot metadata (`policy_version`, `inputs_hash`, `decision_trace` fingerprint) and emits evaluated findings as separate files.
|
||||
- Enforces AOC guardrails: no derived modifications to raw evidence fields.
|
||||
- **Trivy (`trivy:db`, `trivy:java-db`).**
|
||||
- Maps StellaOps advisory schema to Trivy DB format, handling namespace collisions and ecosystem-specific ranges.
|
||||
- Validates compatibility against supported Trivy schema versions; run fails fast if mismatch.
|
||||
- Emits optional manifest summarising package counts and severity distribution.
|
||||
- **Mirror (`mirror:full`, `mirror:delta`).**
|
||||
- Builds self-contained filesystem layout (`/manifests`, `/data/raw`, `/data/policy`, `/indexes`).
|
||||
- Delta variant compares against base manifest (`base_export_id`) to write only changed artefacts; records `removed` entries for cleanup.
|
||||
- Supports optional encryption of `/data` subtree (age/AES-GCM) with key wrapping stored in `provenance.json`.
|
||||
|
||||
Adapters expose structured telemetry events (`adapter.start`, `adapter.chunk`, `adapter.complete`) with record counts and byte totals per chunk. Failures emit `adapter.error` with reason codes.
|
||||
|
||||
## Signing and provenance
|
||||
- **Manifest schema.** `export.json` contains run metadata, profile descriptor, selector summary, counts, SHA-256 digests, compression hints, and distribution list. Deterministic field ordering and normalized timestamps.
|
||||
- **Provenance schema.** `provenance.json` captures in-toto subject listing (bundle digest, manifest digest), referenced inputs (findings ledger queries, policy snapshot ids, SBOM identifiers), tool version (`exporter_version`, adapter versions), and KMS key identifiers.
|
||||
- **Attestation.** Cosign SLSA Level 2 template by default; optional SLSA Level 3 when supply chain attestations are enabled. Detached signatures stored alongside manifests; CLI/Console encourage `cosign verify --key <tenant-key>` workflow.
|
||||
- **Audit trail.** Each run stores success/failure status, signature identifiers, and verification hints for downstream automation (CI pipelines, offline verification scripts).
|
||||
|
||||
## Distribution flows
|
||||
- **HTTP download.** Console and CLI stream bundles via chunked transfer; supports range requests and resumable downloads. Response includes `X-Export-Digest`, `X-Export-Length`, and optional encryption metadata.
|
||||
- **OCI push.** Worker uses ORAS to publish bundles as OCI artefacts with annotations describing profile, tenant, manifest digest, and provenance reference. Supports multi-tenant registries with `repository-per-tenant` naming.
|
||||
- **Object storage.** Writes to tenant-prefixed paths (`s3://stella-exports/{tenant}/{run-id}/...`) with immutable retention policies. Retention scheduler purges expired runs based on profile configuration.
|
||||
- **Offline Kit seeding.** Mirror bundles optionally staged into Offline Kit assembly pipelines, inheriting the same manifests and signatures.
|
||||
|
||||
## Observability
|
||||
- **Metrics.** Emits `exporter_run_duration_seconds`, `exporter_run_bytes_total{profile}`, `exporter_run_failures_total{error_code}`, `exporter_active_runs{tenant}`, `exporter_distribution_push_seconds{type}`.
|
||||
- **Logs.** Structured logs with fields `run_id`, `tenant`, `profile_kind`, `adapter`, `phase`, `correlation_id`, `error_code`. Phases include `plan`, `resolve`, `adapter`, `manifest`, `sign`, `distribute`.
|
||||
- **Traces.** Optional OpenTelemetry spans (`export.plan`, `export.fetch`, `export.write`, `export.sign`, `export.distribute`) for cross-service correlation.
|
||||
- **Dashboards & alerts.** DevOps pipeline seeds Grafana dashboards summarising throughput, size, failure ratios, and distribution latency. Alert thresholds: failure rate >5% per profile, median run duration >p95 baseline, signature verification failures >0.
|
||||
|
||||
## Security posture
|
||||
- Tenant claim enforced at every query and distribution path; cross-tenant selectors rejected unless explicit cross-tenant mirror feature toggled with signed approval.
|
||||
- RBAC scopes: `export:profile:manage`, `export:run`, `export:read`, `export:download`. Console hides actions without scope; CLI returns `401/403`.
|
||||
- Encryption options configurable per profile; keys derived from Authority-managed KMS. Mirror encryption uses tenant-specific recipients; JSON/Trivy rely on transport security plus optional encryption at rest.
|
||||
- Restart-only plugin loading ensures adapters and distribution drivers are vetted at deployment time, reducing runtime injection risks.
|
||||
- Deterministic output ensures tamper detection via content hashes; provenance links to source runs and policy snapshots to maintain auditability.
|
||||
|
||||
## Deployment considerations
|
||||
- Packaged as separate API and worker containers. Helm chart and compose overlays define horizontal scaling, worker concurrency, queue leases, and object storage credentials.
|
||||
- Requires Authority client credentials for KMS and optional registry credentials stored via sealed secrets.
|
||||
- Offline-first deployments disable OCI distribution by default and provide local object storage endpoints; HTTP downloads served via internal gateway.
|
||||
- Health endpoints: `/health/ready` validates Mongo connectivity, object storage access, adapter registry integrity, and KMS signer readiness.
|
||||
|
||||
## Compliance checklist
|
||||
- [ ] Profiles and runs enforce tenant scoping; cross-tenant exports disabled unless approved.
|
||||
- [ ] Manifests and provenance files are generated with deterministic hashes and signed via configured KMS.
|
||||
- [ ] Adapters run with restart-time registration only; no runtime plugin loading.
|
||||
- [ ] Distribution drivers respect allowlist; OCI push disabled when offline mode is active.
|
||||
- [ ] Metrics, logs, and traces follow observability guidelines; dashboards and alerts configured.
|
||||
- [ ] Retention policies and pruning jobs configured for staged bundles.
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
231
docs/export-center/cli.md
Normal file
231
docs/export-center/cli.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Stella CLI - Export Center Commands
|
||||
|
||||
> **Audience:** Operators, release engineers, and CI maintainers using the `stella` CLI to manage Export Center profiles and runs.
|
||||
> **Supported from:** `stella` CLI >= 0.22.0 (Export Center Phase 1).
|
||||
> **Prerequisites:** Authority token with the scopes noted per command (`export:profile:manage`, `export:run`, `export:read`, `export:download`).
|
||||
|
||||
Use this guide with the [Export Center API reference](api.md) and [Profiles](profiles.md) catalogue. The CLI wraps the same REST endpoints, preserving deterministic behaviour and guardrails.
|
||||
|
||||
> Status: CLI support is tracked under `CLI-EXPORT-35-001` and `CLI-EXPORT-36-001`. The current CLI build does not yet surface these commands; treat this guide as the target contract and adjust once implementations merge.
|
||||
|
||||
## 1. Global options and configuration
|
||||
|
||||
| Flag | Default | Description |
|
||||
|------|---------|-------------|
|
||||
| `--server <url>` | `https://stella.local` | Gateway root. Matches `STELLA_SERVER`. |
|
||||
| `--tenant <id>` | Token tenant | Override tenant for multi-tenant tokens. |
|
||||
| `--profile <name>` | none | Loads saved defaults from `~/.stella/profiles/<name>.toml`. |
|
||||
| `--output <file>` | stdout | Redirect full JSON response. |
|
||||
| `--format <table|json|yaml>` | `table` on TTY | Controls table formatting for list commands. |
|
||||
| `--trace` | false | Emit request timing and correlation ids. |
|
||||
|
||||
Environment variables: `STELLA_TOKEN`, `STELLA_SERVER`, `STELLA_TENANT`, `STELLA_PROFILE`.
|
||||
|
||||
Exit codes align with API error codes (see section 6).
|
||||
|
||||
## 2. Profile management commands
|
||||
|
||||
### 2.1 `stella export profile list`
|
||||
|
||||
List profiles for the current tenant.
|
||||
|
||||
```
|
||||
stella export profile list --kind json --variant raw --format table
|
||||
```
|
||||
|
||||
Outputs columns `PROFILE`, `KIND`, `VARIANT`, `DISTRIBUTION`, `RETENTION`. Use `--format json` for automation.
|
||||
|
||||
### 2.2 `stella export profile show`
|
||||
|
||||
```
|
||||
stella export profile show prof-json-raw --output profile.json
|
||||
```
|
||||
|
||||
Fetches full configuration and writes it to file.
|
||||
|
||||
### 2.3 `stella export profile create`
|
||||
|
||||
```
|
||||
stella export profile create --file profiles/prof-json-raw.json
|
||||
```
|
||||
|
||||
JSON schema matches `POST /api/export/profiles`. CLI validates against built-in schema before submission. Requires `export:profile:manage`.
|
||||
|
||||
### 2.4 `stella export profile update`
|
||||
|
||||
```
|
||||
stella export profile update prof-json-raw \
|
||||
--retention "days:21" \
|
||||
--distribution http,object
|
||||
```
|
||||
|
||||
Supports toggling retention, adding/removing distribution targets, and renaming. Structural changes (kind, variant, include set) require editing the JSON and using `--replace-file` to create a new revision.
|
||||
|
||||
### 2.5 `stella export profile archive`
|
||||
|
||||
```
|
||||
stella export profile archive prof-json-raw --reason "Superseded by Phase 2 profile"
|
||||
```
|
||||
|
||||
Marks the profile inactive. Use `stella export profile restore` to re-activate.
|
||||
|
||||
## 3. Run lifecycle commands
|
||||
|
||||
### 3.1 `stella export run submit`
|
||||
|
||||
```
|
||||
stella export run submit prof-json-raw \
|
||||
--selector tenant=acme \
|
||||
--selector product=registry.example.com/app:* \
|
||||
--selector time=2025-10-01T00:00:00Z,2025-10-29T00:00:00Z \
|
||||
--policy-snapshot policy-snap-42 \
|
||||
--allow-empty=false
|
||||
```
|
||||
|
||||
Selectors accept `key=value` pairs; use `time=<from>,<to>` for windows. The command prints the `runId` and initial status.
|
||||
|
||||
### 3.2 `stella export run ls`
|
||||
|
||||
```
|
||||
stella export run ls --profile prof-json-raw --status active --tail 5
|
||||
```
|
||||
|
||||
Shows recent runs with columns `RUN`, `PROFILE`, `STATUS`, `PROGRESS`, `UPDATED`.
|
||||
|
||||
### 3.3 `stella export run show`
|
||||
|
||||
```
|
||||
stella export run show run-20251029-01 --format json
|
||||
```
|
||||
|
||||
Outputs full metadata, progress counters, distribution descriptors, and links.
|
||||
|
||||
### 3.4 `stella export run watch`
|
||||
|
||||
```
|
||||
stella export run watch run-20251029-01 --follow
|
||||
```
|
||||
|
||||
Streams server-sent events and renders a live progress bar. `--json` prints raw events for scripting.
|
||||
|
||||
### 3.5 `stella export run cancel`
|
||||
|
||||
```
|
||||
stella export run cancel run-20251029-01 --reason "Replacing with refined selectors"
|
||||
```
|
||||
|
||||
Gracefully cancels the run; exit code `0` indicates cancellation request accepted.
|
||||
|
||||
## 4. Download and verification commands
|
||||
|
||||
### 4.1 `stella export download`
|
||||
|
||||
```
|
||||
stella export download run-20251029-01 \
|
||||
--output out/exports/run-20251029-01.tar.zst \
|
||||
--resume
|
||||
```
|
||||
|
||||
Downloads the primary bundle. `--resume` enables HTTP range requests; the CLI checkpoints progress to `.part` files.
|
||||
|
||||
### 4.2 `stella export manifest`
|
||||
|
||||
```
|
||||
stella export manifest run-20251029-01 --output manifests/export.json
|
||||
```
|
||||
|
||||
Fetches the signed manifest. Use `--signature manifests/export.json.sig` to save the detached signature.
|
||||
|
||||
### 4.3 `stella export provenance`
|
||||
|
||||
```
|
||||
stella export provenance run-20251029-01 --output manifests/provenance.json
|
||||
```
|
||||
|
||||
Retrieves the signed provenance file. `--signature` behaves like the manifest command.
|
||||
|
||||
### 4.4 `stella export verify`
|
||||
|
||||
```
|
||||
stella export verify run-20251029-01 \
|
||||
--manifest manifests/export.json \
|
||||
--provenance manifests/provenance.json \
|
||||
--key keys/acme-export.pub
|
||||
```
|
||||
|
||||
Wrapper around `cosign verify`. Returns exit `0` when signatures and digests validate. Exit `20` when verification fails.
|
||||
|
||||
## 5. CI recipe (GitHub Actions example)
|
||||
|
||||
```yaml
|
||||
name: Export Center Bundle
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
export:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Stella CLI
|
||||
run: curl -sSfL https://downloads.stellaops.org/cli/install.sh | sh
|
||||
- name: Submit export run
|
||||
env:
|
||||
STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }}
|
||||
run: |
|
||||
run_id=$(stella export run submit prof-json-raw \
|
||||
--selector tenant=acme \
|
||||
--selector product=registry.example.com/app:* \
|
||||
--allow-empty=false \
|
||||
--format json | jq -r '.runId')
|
||||
echo "RUN_ID=$run_id" >> $GITHUB_ENV
|
||||
- name: Wait for completion
|
||||
env:
|
||||
STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }}
|
||||
run: |
|
||||
stella export run watch "$RUN_ID" --json \
|
||||
| tee artifacts/run.log \
|
||||
| jq -e 'select(.event == "run.succeeded")' > /dev/null
|
||||
- name: Download bundle
|
||||
env:
|
||||
STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }}
|
||||
run: |
|
||||
stella export download "$RUN_ID" --output artifacts/export.tar.zst --resume
|
||||
stella export manifest "$RUN_ID" --output artifacts/export.json --signature artifacts/export.json.sig
|
||||
stella export provenance "$RUN_ID" --output artifacts/provenance.json --signature artifacts/provenance.json.sig
|
||||
- name: Verify signatures
|
||||
run: |
|
||||
stella export verify "$RUN_ID" \
|
||||
--manifest artifacts/export.json \
|
||||
--provenance artifacts/provenance.json \
|
||||
--key keys/acme-export.pub
|
||||
```
|
||||
|
||||
## 6. Exit codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `0` | Command succeeded. |
|
||||
| `10` | Validation error (`ERR_EXPORT_001`). |
|
||||
| `11` | Profile missing or inaccessible (`ERR_EXPORT_002`). |
|
||||
| `12` | Quota or concurrency exceeded (`ERR_EXPORT_003` or `ERR_EXPORT_QUOTA`). |
|
||||
| `13` | Run failed due to adapter/signing/distribution error. |
|
||||
| `20` | Verification failure (`stella export verify`). |
|
||||
| `21` | Download incomplete after retries (network errors). |
|
||||
| `30` | CLI configuration error (missing token, invalid profile file). |
|
||||
|
||||
Exit codes above 100 are reserved for future profile-specific tooling.
|
||||
|
||||
## 7. Offline usage notes
|
||||
|
||||
- Use profiles that enable `object` distribution with local object storage endpoints. CLI reads `STELLA_EXPORT_OBJECT_ENDPOINT` when provided (falls back to gateway).
|
||||
- Mirror bundles work offline by skipping OCI distribution. CLI adds `--offline` to bypass OCI checks.
|
||||
- `stella export verify` works fully offline when provided with tenant public keys (packaged in Offline Kit).
|
||||
|
||||
## 8. Related documentation
|
||||
|
||||
- [Export Center Profiles](profiles.md)
|
||||
- [Export Center API reference](api.md)
|
||||
- [Export Center Architecture](architecture.md)
|
||||
- [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md)
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
202
docs/export-center/mirror-bundles.md
Normal file
202
docs/export-center/mirror-bundles.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# Export Center Mirror Bundles
|
||||
|
||||
Mirror bundles package StellaOps evidence, policy overlays, and indexes for air-gapped or bandwidth-constrained environments. They are produced by the `mirror:full` and `mirror:delta` profiles described in Epic 10 (Export Center) and implemented across Sprints 35-37 (`EXPORT-SVC-35-004`, `EXPORT-SVC-37-001`, `EXPORT-SVC-37-002`). This guide details bundle layouts, delta mechanics, encryption workflow, import procedures, and operational best practices.
|
||||
|
||||
> Export Center workers are being wired while this document is written. Treat the content as the target contract for adapter development and update specifics as the implementation lands.
|
||||
|
||||
## 1. Bundle overview
|
||||
|
||||
| Profile | Contents | Typical use cases | Distribution |
|
||||
|---------|----------|-------------------|--------------|
|
||||
| `mirror:full` | Complete snapshot of raw evidence, normalized records, indexes, policy snapshots, provenance, signatures. | Initial seeding of an air-gapped mirror, disaster recovery drills. | Download bundle, optional OCI artifact. |
|
||||
| `mirror:delta` | Changes since a specified base export: added/updated/removed advisories, VEX statements, SBOMs, indexes, manifests. | Incremental updates, bandwidth reduction, nightly refreshes. | Download bundle, optional OCI artifact. |
|
||||
|
||||
Both profiles respect AOC boundaries: raw ingestion data remains untouched, and policy outputs live under their own directory with explicit provenance.
|
||||
|
||||
## 2. Filesystem layout
|
||||
|
||||
Directory structure inside the extracted bundle:
|
||||
|
||||
```
|
||||
mirror/
|
||||
manifest.yaml
|
||||
export.json
|
||||
provenance.json
|
||||
README.md
|
||||
indexes/
|
||||
advisories.index.json
|
||||
vex.index.json
|
||||
sbom.index.json
|
||||
findings.index.json
|
||||
data/
|
||||
raw/
|
||||
advisories/*.jsonl.zst
|
||||
vex/*.jsonl.zst
|
||||
sboms/<subject>/sbom.json
|
||||
normalized/
|
||||
advisories/*.jsonl.zst
|
||||
vex/*.jsonl.zst
|
||||
policy/
|
||||
snapshot.json
|
||||
evaluations.jsonl.zst
|
||||
consensus/
|
||||
vex_consensus.jsonl.zst
|
||||
signatures/
|
||||
export.sig
|
||||
manifest.sig
|
||||
```
|
||||
|
||||
`manifest.yaml` summarises profile metadata, selectors, counts, sizes, and SHA-256 digests. `export.json` and `provenance.json` mirror the JSON profile manifests and are signed using the configured KMS key.
|
||||
|
||||
Example `manifest.yaml`:
|
||||
|
||||
```yaml
|
||||
profile: mirror:full
|
||||
runId: run-20251029-01
|
||||
tenant: acme
|
||||
selectors:
|
||||
products:
|
||||
- registry.example.com/app:*
|
||||
timeWindow:
|
||||
from: 2025-10-01T00:00:00Z
|
||||
to: 2025-10-29T00:00:00Z
|
||||
counts:
|
||||
advisories: 15234
|
||||
vex: 3045
|
||||
sboms: 872
|
||||
policyEvaluations: 19876
|
||||
artifacts:
|
||||
- path: data/raw/advisories/a0.jsonl.zst
|
||||
sha256: 9f4b...
|
||||
bytes: 7340021
|
||||
encryption:
|
||||
mode: age
|
||||
strict: false
|
||||
recipients:
|
||||
- age1tenantkey...
|
||||
```
|
||||
|
||||
## 3. Delta mechanics
|
||||
|
||||
Delta bundles reference a previous full or delta run via `baseExportId` and `baseManifestDigest`. They contain:
|
||||
|
||||
```
|
||||
delta/
|
||||
changed/
|
||||
data/raw/advisories/*.jsonl.zst
|
||||
...
|
||||
removed/
|
||||
advisories.jsonl # list of advisory IDs removed
|
||||
vex.jsonl
|
||||
sboms.jsonl
|
||||
manifest.diff.json # summary of counts, hashes, base export metadata
|
||||
```
|
||||
|
||||
- **Base lookup:** The worker verifies that the base export is reachable (download path or OCI reference). If missing, the run fails with `ERR_EXPORT_BASE_MISSING`.
|
||||
- **Change detection:** Uses deterministic hashing of normalized records to compute additions/updates. Indexes are regenerated only for affected subjects.
|
||||
- **Application order:** Consumers apply deltas sequentially. A `resetBaseline=true` flag instructs them to drop cached state and apply the bundle as a full refresh.
|
||||
|
||||
Example `manifest.diff.json` (delta):
|
||||
|
||||
```json
|
||||
{
|
||||
"baseExportId": "run-20251025-01",
|
||||
"baseManifestDigest": "sha256:aa11...",
|
||||
"resetBaseline": false,
|
||||
"added": {
|
||||
"advisories": 43,
|
||||
"vex": 12,
|
||||
"sboms": 5
|
||||
},
|
||||
"changed": {
|
||||
"advisories": 18,
|
||||
"vex": 7
|
||||
},
|
||||
"removed": {
|
||||
"advisories": 2,
|
||||
"vex": 0,
|
||||
"sboms": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Encryption workflow
|
||||
|
||||
Mirror bundles support optional encryption of the `data/` subtree:
|
||||
|
||||
- **Algorithm:** Age (X25519) or AES-GCM (256-bit) based on profile configuration.
|
||||
- **Key wrapping:** Keys fetched from Authority-managed KMS through Export Center. Wrapped data keys stored in `provenance.json` under `encryption.recipients[]`.
|
||||
- **Metadata:** `manifest.yaml` records `encryption.mode`, `recipients`, and `encryptedPaths`.
|
||||
- **Strict mode:** `strict=true` encrypts everything except `manifest.yaml` and `export.json`. Default (`false`) leaves manifests unencrypted to simplify discovery.
|
||||
- **Verification:** CLI (`stella export verify`) and Offline Kit scripts perform signature checks prior to decryption.
|
||||
|
||||
Operators must distribute recipient keys out of band. Export Center does not transmit private keys.
|
||||
|
||||
## 5. Import workflow
|
||||
|
||||
### 5.1 Offline Kit
|
||||
|
||||
Offline Kit bundles reference the latest full mirror export plus the last `N` deltas. Administrators run:
|
||||
|
||||
```
|
||||
./offline-kit/bin/mirror import /path/to/mirror-20251029-full.tar.zst
|
||||
./offline-kit/bin/mirror import /path/to/mirror-20251030-delta.tar.zst
|
||||
```
|
||||
|
||||
The tool verifies signatures, applies deltas, and updates the mirror index served by the local gateway.
|
||||
|
||||
### 5.2 Custom automation
|
||||
|
||||
1. Download bundle (`stella export download`) and verify signatures (`stella export verify`).
|
||||
2. Extract archive into a staging directory.
|
||||
3. For encrypted bundles, decrypt using the provided age/AES key.
|
||||
4. Sync `mirror/data` onto the target mirror store (object storage, NFS, etc.).
|
||||
5. Republish indexes or reload services that depend on the mirror.
|
||||
|
||||
Delta consumers must track `appliedExportIds` to ensure ordering.
|
||||
|
||||
Sequence diagram of download/import:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CLI as stella CLI
|
||||
participant Mirror as Mirror Store
|
||||
participant Verify as Verification Tool
|
||||
CLI->>CLI: stella export download run-20251029-01
|
||||
CLI->>Verify: stella export verify run-20251029-01
|
||||
CLI->>Mirror: mirror import mirror-20251029-full.tar.zst
|
||||
CLI->>Mirror: mirror import mirror-20251030-delta.tar.zst
|
||||
Mirror-->>CLI: import complete (run-20251030-02)
|
||||
```
|
||||
|
||||
## 6. Operational guidance
|
||||
|
||||
- **Retention:** Keep at least one full bundle plus the deltas required for disaster recovery. Configure `ExportCenter:Retention:Mirror` to prune older bundles automatically.
|
||||
- **Storage footprint:** Full bundles can exceed tens of gigabytes. Plan object storage or NAS capacity accordingly and enable compression (`compression.codec=zstd`).
|
||||
- **Scheduling:** For high-churn environments, run daily full exports and hourly deltas. Record cadence in `manifest.yaml` (`schedule.cron`).
|
||||
- **Incident recovery:** To rebuild a mirror:
|
||||
1. Apply the most recent full bundle.
|
||||
2. Apply deltas in order of `createdAt`.
|
||||
3. Re-run integrity checks (`mirror verify <path>`).
|
||||
- **Audit logging:** Export Center logs `mirror.bundle.created`, `mirror.delta.applied`, and `mirror.encryption.enabled` events. Consume them in the central observability pipeline.
|
||||
|
||||
## 7. Troubleshooting
|
||||
|
||||
| Symptom | Meaning | Action |
|
||||
|---------|---------|--------|
|
||||
| `ERR_EXPORT_BASE_MISSING` | Base export not available | Republish base bundle or rebuild as full export. |
|
||||
| Delta applies but mirror misses entries | Deltas applied out of order | Rebuild from last full bundle and reapply in sequence. |
|
||||
| Decryption fails | Recipient key mismatch or corrupted bundle | Confirm key distribution and re-download bundle. |
|
||||
| Verification errors | Signature mismatch | Do not import; regenerate bundle and investigate signing pipeline. |
|
||||
| Manifest hash mismatch | Files changed after extraction | Re-extract bundle and re-run verification; check storage tampering. |
|
||||
|
||||
## 8. References
|
||||
|
||||
- [Export Center Overview](overview.md)
|
||||
- [Export Center Architecture](architecture.md)
|
||||
- [Export Center API reference](api.md)
|
||||
- [Export Center CLI Guide](cli.md)
|
||||
- [Concelier mirror runbook](../ops/concelier-mirror-operations.md)
|
||||
- [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md)
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
63
docs/export-center/overview.md
Normal file
63
docs/export-center/overview.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Export Center Overview
|
||||
|
||||
The Export Center packages StellaOps evidence and policy outputs into portable, verifiable bundles. It provides one workflow for operators to deliver advisories, SBOMs, VEX statements, and policy decisions into downstream systems or air-gapped environments without rewriting data or violating the Aggregation-Only Contract (AOC).
|
||||
|
||||
## What the Export Center delivers
|
||||
- **Unified export service.** A dedicated `exporter` service coordinates profiles, runs, signing, and distribution targets with deterministic manifests.
|
||||
- **Profile catalogue.** Out of the box variants include `json:raw`, `json:policy`, `trivy:db`, `trivy:java-db`, `mirror:full`, and `mirror:delta`, each aligned with AOC rules and downstream compatibility requirements.
|
||||
- **Surface parity.** Operators can create, monitor, and download exports through the Web API gateway, Console workflows, and the CLI (`stella export ...`). All surfaces enforce tenant scope and RBAC consistently.
|
||||
- **Automation hooks.** One-off, cron, and event triggers are orchestrated via the Scheduler/Orchestrator integration. Export telemetry (durations, bundle size, verification outcomes) feeds structured logs, metrics, and optional OpenTelemetry traces.
|
||||
|
||||
### Profile variants at a glance
|
||||
|
||||
| Profile | Contents | Primary scenarios | Distribution defaults |
|
||||
|---------|----------|-------------------|-----------------------|
|
||||
| `json:raw` | Canonical advisories, VEX, SBOM JSONL with hashes | Downstream analytics, evidence escrow | HTTP download, object storage |
|
||||
| `json:policy` | `json:raw` plus policy snapshot, evaluated findings | Policy attestation, audit packages | HTTP download, object storage |
|
||||
| `trivy:db` / `trivy:java-db` | Trivy-compatible vulnerability databases | Feed external scanners and CI | OCI artifact push, download |
|
||||
| `mirror:full` | Complete evidence, indexes, policy, provenance | Air-gap mirror, disaster recovery | Filesystem bundle, OCI artifact |
|
||||
| `mirror:delta` | Changes relative to prior manifest | Incremental updates to mirrors | Filesystem bundle, OCI artifact |
|
||||
|
||||
## How it works end-to-end
|
||||
1. **Profile & scope resolution.** A profile defines export type, content filters, and bundle settings. Scope selectors target tenants, artifacts, time windows, ecosystems, or SBOM subjects.
|
||||
2. **Ledger collection.** Workers stream canonical data from Findings Ledger, VEX Lens, Conseiller feeds, and SBOM service. Policy exports pin a deterministic policy snapshot from Policy Engine.
|
||||
3. **Adapter execution.** JSON adapters produce normalized `.jsonl.zst` outputs, Trivy adapters translate to the Trivy DB schema, and mirror adapters build filesystem or OCI bundle layouts.
|
||||
4. **Manifesting & provenance.** Every run emits `export.json` (profile, filters, counts, checksums) and `provenance.json` (source artifacts, policy snapshot ids, signature references).
|
||||
5. **Signing & distribution.** Bundles are signed via configured KMS (cosign-compatible) and distributed through HTTP streaming, OCI registry pushes, or object storage staging.
|
||||
|
||||
Refer to `docs/export-center/architecture.md` (Sprint 35 task) for component diagrams and adapter internals once published.
|
||||
|
||||
## Security and compliance guardrails
|
||||
- **AOC alignment.** Exports bundle raw evidence and optional policy evaluations without mutating source content. Policy overlays remain attributed to Policy Engine and are clearly partitioned.
|
||||
- **Tenant isolation.** All queries, manifests, and bundle paths carry tenant identifiers. Cross-tenant exports require explicit signed approval and ship with provenance trails.
|
||||
- **Signing and encryption.** Manifests and payloads are signed using the platform KMS. Mirror profiles support optional in-bundle encryption (age/AES-GCM) with key wrapping.
|
||||
- **Determinism.** Identical inputs yield identical bundles. Timestamps serialize in UTC ISO-8601; manifests include content hashes for audit replay.
|
||||
|
||||
See `docs/security/policy-governance.md` and `docs/ingestion/aggregation-only-contract.md` for broader guardrail context.
|
||||
|
||||
## Operating it offline
|
||||
- **Offline Kit integration.** Air-gapped deployments receive pre-built export profiles and object storage layout templates through the Offline Kit bundles.
|
||||
- **Mirror bundles.** `mirror:full` packages raw evidence, normalized indexes, policy snapshots, and provenance in a portable filesystem layout suitable for disconnected environments. `mirror:delta` tracks changes relative to a prior export manifest.
|
||||
- **No unsanctioned egress.** The exporter respects the platform allowlist. External calls (e.g., OCI pushes) require explicit configuration and are disabled by default for offline installs.
|
||||
|
||||
Consult `docs/24_OFFLINE_KIT.md` for Offline Kit delivery and `docs/ops/concelier-mirror-operations.md` for mirror ingestion procedures.
|
||||
|
||||
## Getting started
|
||||
1. **Choose a profile.** Map requirements to the profile table above. Policy-aware exports need a published policy snapshot.
|
||||
2. **Define selectors.** Decide on tenants, products, SBOM subjects, or time windows to include. Default selectors export the entire tenant scope.
|
||||
3. **Run via preferred surface.**
|
||||
- **Console:** Navigate to the Export Center view, create a run, monitor progress, and download artifacts.
|
||||
- **CLI:** Use `stella export run --profile <name> --selector <filters>` to submit a job, then `stella export download`.
|
||||
- **API:** POST to `/api/export/runs` with profile id and scope payload; stream results from `/api/export/runs/{id}/download`.
|
||||
4. **Verify bundles.** Use the attached provenance manifest and cosign signature to validate contents before distributing downstream.
|
||||
|
||||
Refer to `docs/export-center/cli.md` for detailed command syntax and automation examples.
|
||||
|
||||
## Observability & troubleshooting
|
||||
- Structured logs emit lifecycle events (`fetch`, `adapter`, `sign`, `publish`) with correlation IDs for parallel job tracing.
|
||||
- Metrics `exporter_run_duration_seconds`, `exporter_bundle_bytes_total`, and `exporter_run_failures_total` feed Grafana dashboards defined in the deployment runbooks.
|
||||
- Verification failures or schema mismatches bubble up through failure events and appear in Console/CLI with actionable error messages. Inspect the run's audit log and `provenance.json` for root cause.
|
||||
|
||||
See `docs/observability/policy.md` and `docs/ops/deployment-upgrade-runbook.md` for telemetry and operations guidance.
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
139
docs/export-center/profiles.md
Normal file
139
docs/export-center/profiles.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Export Center Profiles
|
||||
|
||||
Export Center profiles define what data is collected, how it is encoded, and which distribution paths are enabled for a run. Profiles are tenant-scoped and deterministic: identical selectors and source data produce identical bundles. This guide summarises built-in profiles, configuration fields, schema conventions, and compatibility notes.
|
||||
|
||||
## Profile catalogue
|
||||
|
||||
| Profile | Kind / Variant | Output artefacts | Primary use cases |
|
||||
|---------|----------------|------------------|-------------------|
|
||||
| `json:raw` | `json` / `raw` | Canonical JSONL archives of advisories, VEX, SBOMs | Evidence escrow, analytics pipelines |
|
||||
| `json:policy` | `json` / `policy` | `json:raw` artefacts plus policy snapshot + evaluated findings | Audit, compliance attestations |
|
||||
| `trivy:db` | `trivy` / `db` | Trivy-compatible vulnerability database | Feeding external scanners / CI |
|
||||
| `trivy:java-db` | `trivy` / `java-db` | Java ecosystem supplement for Trivy | Supply Java CVE data to Trivy |
|
||||
| `mirror:full` | `mirror` / `full` | Complete mirror bundle (raw, policy, indexes, provenance) | Air-gap deployments, disaster recovery |
|
||||
| `mirror:delta` | `mirror` / `delta` | Incremental changes relative to a prior manifest | Efficient mirror updates |
|
||||
|
||||
Profiles can be cloned and customised; configuration is immutable per revision to keep runs reproducible.
|
||||
|
||||
## Common configuration fields
|
||||
|
||||
| Field | Description | Applies to | Notes |
|
||||
|-------|-------------|------------|-------|
|
||||
| `name` | Human-readable identifier displayed in Console/CLI | All | Unique per tenant. |
|
||||
| `kind` | Logical family (`json`, `trivy`, `mirror`) | All | Determines eligible adapters. |
|
||||
| `variant` | Specific export flavour (see table above) | All | Controls adapter behaviour. |
|
||||
| `include` | Record types to include (`advisories`, `vex`, `sboms`, `findings`) | JSON, mirror | Defaults depend on variant. |
|
||||
| `policySnapshotMode` | `required`, `optional`, or `none` | JSON policy, mirror | `required` forces a policy snapshot id when creating runs. |
|
||||
| `distribution` | Enabled distribution drivers (`http`, `oci`, `object`) | All | Offline installs typically disable `oci`. |
|
||||
| `compression` | Compression settings (`zstd`, level) | JSON, mirror | Trivy adapters manage compression internally. |
|
||||
| `encryption` | Mirror encryption options (`enabled`, `recipientKeys`, `strict`) | Mirror | When enabled, only `/data` subtree is encrypted; manifests remain plaintext. |
|
||||
| `retention` | Retention policy (days or `never`) | All | Drives pruning jobs for staged bundles. |
|
||||
|
||||
Selectors (time windows, tenants, products, SBOM subjects, ecosystems) are supplied per run, not stored in the profile.
|
||||
|
||||
## JSON profiles
|
||||
|
||||
### `json:raw`
|
||||
- **Content:** Exports raw advisories, VEX statements, and SBOMs as newline-delimited JSON (`.jsonl.zst`).
|
||||
- **Schema:** Follows canonical StellaOps schema with casing and timestamps normalised. Each record includes `tenant`, `source`, `linkset`, and `content` fields.
|
||||
- **Options:**
|
||||
- `include` defaults to `["advisories", "vex", "sboms"]`.
|
||||
- `compression` defaults to `zstd` level 9.
|
||||
- `maxRecordsPerFile` (optional) splits outputs for large datasets.
|
||||
- **Compatibility:** Intended for analytics platforms, data escrow, or feeding downstream normalisation pipelines.
|
||||
- **Sample manifest excerpt:**
|
||||
|
||||
```json
|
||||
{
|
||||
"profile": { "kind": "json", "variant": "raw" },
|
||||
"outputs": [
|
||||
{ "type": "advisories.jsonl.zst", "sha256": "...", "count": 15234 },
|
||||
{ "type": "vex.jsonl.zst", "sha256": "...", "count": 3045 },
|
||||
{ "type": "sboms.jsonl.zst", "sha256": "...", "count": 872 }
|
||||
],
|
||||
"selectors": { "tenant": "acme", "products": ["registry.example/app"] }
|
||||
}
|
||||
```
|
||||
|
||||
### `json:policy`
|
||||
- **Content:** Everything from `json:raw` plus:
|
||||
- `policy_snapshot.json` (policy metadata, version, hash).
|
||||
- `findings.policy.jsonl.zst` (evaluated findings with decision, rationale, rule id, inputs hash).
|
||||
- **Determinism:** Requires a policy snapshot id; runs fail if snapshot is missing or non-deterministic mode is active.
|
||||
- **Use cases:** Compliance exports, auditor packages, policy attestation archives.
|
||||
- **Guardrails:** AOC boundaries preserved: policy outputs are clearly partitioned from raw evidence.
|
||||
|
||||
## Trivy profiles
|
||||
|
||||
### `trivy:db`
|
||||
- Detailed adapter behaviour is documented in `docs/export-center/trivy-adapter.md`.
|
||||
- **Content:** Produces a Trivy DB-compatible bundle (SQLite database or tarball as required by Trivy version).
|
||||
- **Mapping rules:**
|
||||
- Advisory namespaces mapped to Trivy vendor IDs (e.g., `ubuntu`, `debian`, `npm`).
|
||||
- Version ranges translated into Trivy's semantic version syntax.
|
||||
- Severity mapped to Trivy standard (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`).
|
||||
- **Validation:** Adapter enforces supported Trivy schema versions; configuration includes `targetSchemaVersion`.
|
||||
- **Distribution:** Typically pushed to OCI or object storage for downstream scanners; Console download remains available.
|
||||
|
||||
### `trivy:java-db`
|
||||
- Refer to `docs/export-center/trivy-adapter.md` for ecosystem-specific notes.
|
||||
- **Content:** Optional Java ecosystem supplement for Trivy (matching Trivy's separate Java DB).
|
||||
- **Dependencies:** Requires Java advisories in Findings Ledger; run fails with `ERR_EXPORT_EMPTY` if no Java data present and `allowEmpty=false`.
|
||||
- **Compatibility:** Intended for organisations using Trivy's Java plugin or hardened pipelines that split general and Java feeds.
|
||||
|
||||
## Mirror profiles
|
||||
|
||||
### `mirror:full`
|
||||
- Bundle structure and delta strategy are covered in `docs/export-center/mirror-bundles.md`.
|
||||
- **Content:** Complete export with:
|
||||
- Raw advisories, VEX, SBOMs (`/data/raw`).
|
||||
- Policy overlays (`/data/policy`), including evaluated findings and policy snapshots.
|
||||
- Indexes for fast lookup (`/indexes/advisories.pb`, `/indexes/sboms.pb`).
|
||||
- Manifests and provenance (`/manifests/export.json`, `/manifests/provenance.json`).
|
||||
- **Layout:** Deterministic directory structure with hashed filenames to reduce duplication.
|
||||
- **Encryption:** Optional `encryption` block enables age/AES-GCM encryption of `/data`. `strict=true` encrypts everything except `export.json`.
|
||||
- **Use cases:** Air-gap replication, disaster recovery drills, Offline Kit seeding.
|
||||
|
||||
### `mirror:delta`
|
||||
- See `docs/export-center/mirror-bundles.md` for delta mechanics and application order.
|
||||
- **Content:** Includes only changes relative to a base manifest (specified by `baseExportId` when running).
|
||||
- `changed`, `added`, `removed` lists in `manifests/delta.json`.
|
||||
- Incremental indexes capturing only updated subjects.
|
||||
- **Constraints:** Requires the base manifest to exist in object storage or artifact registry accessible to the worker. Fails with `ERR_EXPORT_BASE_MISSING` otherwise.
|
||||
- **Workflow:** Ideal for frequent updates to mirrored environments with limited bandwidth.
|
||||
|
||||
## Compatibility and guardrails
|
||||
- **Aggregation-Only Contract:** All profiles respect AOC boundaries: raw evidence is never mutated. Policy outputs are appended separately with clear provenance.
|
||||
- **Tenant scoping:** Profiles are tenant-specific. Cross-tenant exports require explicit administrative approval and signed justification.
|
||||
- **Retriable runs:** Re-running a profile with identical selectors yields matching manifests and hashes, facilitating verify-on-download workflows.
|
||||
- **Offline operation:** JSON and mirror profiles function in offline mode without additional configuration. Trivy profiles require pre-seeded schema metadata shipped via Offline Kit.
|
||||
- **Quota integration:** Profiles can define run quotas (per tenant per day). Quota exhaustion surfaces as `429 Too Many Requests` with `X-Stella-Quota-*` hints.
|
||||
|
||||
## Example profile definition (CLI)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"name": "daily-json-raw",
|
||||
"kind": "json",
|
||||
"variant": "raw",
|
||||
"include": ["advisories", "vex", "sboms"],
|
||||
"distribution": ["http", "object"],
|
||||
"compression": { "codec": "zstd", "level": 9 },
|
||||
"retention": { "mode": "days", "value": 14 }
|
||||
}
|
||||
```
|
||||
|
||||
Create via `stella export profile create --file profile.json` (CLI command documented separately).
|
||||
|
||||
## Verification workflow
|
||||
- Download bundle via Console/CLI and extract `export.json` and `provenance.json`.
|
||||
- Run `cosign verify --key <tenant-key> export.json` (for detatched signatures use `--signature export.json.sig`).
|
||||
- Validate Trivy bundles with `trivy --cache-dir <temp> --debug --db-repository <oci-ref>` or local `trivy module db import`.
|
||||
- For mirror bundles, run internal `mirror verify` script (bundled in Offline Kit) to ensure directory layout and digests match manifest.
|
||||
|
||||
## Extending profiles
|
||||
- Use API/CLI to clone an existing profile and adjust `include`, `distribution`, or retention policies.
|
||||
- Adapter plug-ins can introduce new variants (e.g., `json:raw-lite`, `mirror:policy-only`). Plug-ins must be registered at service restart and documented under `/docs/export-center/profiles.md`.
|
||||
- Any new profile must append the imposed rule line and follow determinism and guardrail requirements.
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
150
docs/export-center/provenance-and-signing.md
Normal file
150
docs/export-center/provenance-and-signing.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Export Center Provenance & Signing
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
Export Center runs emit deterministic manifests, provenance records, and signatures so operators can prove bundle integrity end-to-end—whether the artefact is downloaded over HTTPS, pulled as an OCI object, or staged through the Offline Kit. This guide captures the canonical artefacts, signing pipeline, verification workflows, and failure handling expectations that backlogs `EXPORT-SVC-35-005` and `EXPORT-SVC-37-002` implement.
|
||||
|
||||
---
|
||||
|
||||
## 1. Goals & scope
|
||||
|
||||
- **Authenticity.** Every export manifest and provenance document is signed using Authority-managed KMS keys (cosign-compatible) with optional SLSA Level 3 attestation.
|
||||
- **Traceability.** Provenance links each bundle to the inputs that produced it: tenant, findings ledger queries, policy snapshots, SBOM identifiers, adapter versions, and encryption recipients.
|
||||
- **Determinism.** Canonical JSON (sorted keys, RFC 3339 UTC timestamps, normalized numbers) guarantees byte-for-byte stability across reruns with identical input.
|
||||
- **Portability.** Signatures and attestations travel with filesystem bundles, OCI artefacts, and Offline Kit staging trees. Verification does not require online Authority access when the bundle includes the cosign public key.
|
||||
|
||||
---
|
||||
|
||||
## 2. Artefact inventory
|
||||
|
||||
| File | Location | Description | Notes |
|
||||
|------|----------|-------------|-------|
|
||||
| `export.json` | `manifests/export.json` or HTTP `GET /api/export/runs/{id}/manifest` | Canonical manifest describing profile, selectors, counts, SHA-256 digests, compression hints, distribution targets. | Hash of this file is included in provenance `subjects[]`. |
|
||||
| `provenance.json` | `manifests/provenance.json` or `GET /api/export/runs/{id}/provenance` | In-toto provenance record listing subjects, materials, toolchain metadata, encryption recipients, and KMS key identifiers. | Mirrors SLSA Level 2 schema; optionally upgraded to Level 3 with builder attestations. |
|
||||
| `export.json.sig` / `export.json.dsse` | `signatures/export.json.sig` | Cosign signature (and optional DSSE envelope) for manifest. | File naming matches cosign defaults; offline verification scripts expect `.sig`. |
|
||||
| `provenance.json.sig` / `provenance.json.dsse` | `signatures/provenance.json.sig` | Cosign signature (and optional DSSE envelope) for provenance document. | `dsse` present when SLSA Level 3 is enabled. |
|
||||
| `bundle.attestation` | `signatures/bundle.attestation` (optional) | SLSA Level 2/3 attestation binding bundle tarball/OCI digest to the run. | Only produced when `export.attestation.enabled=true`. |
|
||||
| `manifest.yaml` | bundle root | Human-readable summary including digests, sizes, encryption metadata, and verification hints. | Unsigned but redundant; signatures cover the JSON manifests. |
|
||||
|
||||
All digests use lowercase hex SHA-256 (`sha256:<digest>`). When bundle encryption is enabled, `provenance.json` records wrapped data keys and recipient fingerprints under `encryption.recipients[]`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Signing pipeline
|
||||
|
||||
1. **Canonicalisation.** Export worker serialises `export.json` and `provenance.json` using `NotifyCanonicalJsonSerializer` (identical canonical JSON helpers shared across services). Keys are sorted lexicographically, arrays ordered deterministically, timestamps normalised to UTC.
|
||||
2. **Digest creation.** SHA-256 digests are computed and recorded:
|
||||
- `manifest_hash` and `provenance_hash` stored in the run metadata (Mongo) and exported via `/api/export/runs/{id}`.
|
||||
- Provenance `subjects[]` contains both manifest hash and bundle/archive hash.
|
||||
3. **Key retrieval.** Worker obtains a short-lived signing token from Authority’s KMS client using tenant-scoped credentials (`export.sign` scope). Keys live in Authority or tenant-specific HSMs depending on deployment.
|
||||
4. **Signature emission.** Cosign generates detached signatures (`*.sig`). If DSSE is enabled, cosign wraps payload bytes in a DSSE envelope (`*.dsse`). Attestations follow the SLSA Level 2 provenance template; Level 3 requires builder metadata (`EXPORT-SVC-37-002` optional feature flag).
|
||||
5. **Storage & distribution.** Signatures and attestations are written alongside manifests in object storage, included in filesystem bundles, and attached as OCI artefact layers/annotations.
|
||||
6. **Audit trail.** Run metadata captures signer identity (`signing_key_id`), cosign certificate serial, signature timestamps, and verification hints. Console/CLI surface these details for downstream automation.
|
||||
|
||||
> **Key management.** Secrets and key references are configured per tenant via `export.signing`, pointing to Authority clients or external HSM aliases. Offline deployments pre-load cosign public keys into the bundle (`signatures/pubkeys/{tenant}.pem`).
|
||||
|
||||
---
|
||||
|
||||
## 4. Provenance schema highlights
|
||||
|
||||
`provenance.json` follows the SLSA provenance (`https://slsa.dev/provenance/v1`) structure with StellaOps-specific extensions. Key fields:
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `subject[]` | Array of `{name,digest}` pairs. Includes bundle tarball/OCI digest and `export.json` digest. |
|
||||
| `predicateType` | SLSA v1 (default). |
|
||||
| `predicate.builder` | `{id:"stellaops/export-center@<region>"}` identifies the worker instance/cluster. |
|
||||
| `predicate.buildType` | Profile identifier (`mirror:full`, `mirror:delta`, etc.). |
|
||||
| `predicate.invocation.parameters` | Profile selectors, retention flags, encryption mode, base export references. |
|
||||
| `predicate.materials[]` | Source artefacts with digests: findings ledger query snapshots, policy snapshot IDs + hashes, SBOM identifiers, adapter release digests. |
|
||||
| `predicate.metadata.buildFinishedOn` | RFC 3339 timestamp when signing completed. |
|
||||
| `predicate.metadata.reproducible` | Always `true`—workers guarantee determinism. |
|
||||
| `predicate.environment.encryption` | Records encryption recipients, wrapped keys, algorithm (`age` or `aes-gcm`). |
|
||||
| `predicate.environment.kms` | Signing key identifier (`authority://tenant/export-signing-key`) and certificate chain fingerprints. |
|
||||
|
||||
Sample (abridged):
|
||||
|
||||
```json
|
||||
{
|
||||
"subject": [
|
||||
{ "name": "bundle.tar.zst", "digest": { "sha256": "c1fe..." } },
|
||||
{ "name": "manifests/export.json", "digest": { "sha256": "ad42..." } }
|
||||
],
|
||||
"predicate": {
|
||||
"buildType": "mirror:delta",
|
||||
"invocation": {
|
||||
"parameters": {
|
||||
"tenant": "tenant-01",
|
||||
"baseExportId": "run-20251020-01",
|
||||
"selectors": { "sources": ["concelier","vexer"], "profiles": ["mirror"] }
|
||||
}
|
||||
},
|
||||
"materials": [
|
||||
{ "uri": "ledger://tenant-01/findings?cursor=rev-42", "digest": { "sha256": "0f9a..." } },
|
||||
{ "uri": "policy://tenant-01/snapshots/rev-17", "digest": { "sha256": "8c3d..." } }
|
||||
],
|
||||
"environment": {
|
||||
"encryption": {
|
||||
"mode": "age",
|
||||
"recipients": [
|
||||
{ "recipient": "age1qxyz...", "wrappedKey": "BASE64...", "keyId": "tenant-01/notify-age" }
|
||||
]
|
||||
},
|
||||
"kms": {
|
||||
"signingKeyId": "authority://tenant-01/export-signing",
|
||||
"certificateChainSha256": "1f5e..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification workflows
|
||||
|
||||
| Scenario | Steps |
|
||||
|----------|-------|
|
||||
| **CLI verification** | 1. `stella export manifest <runId> --output manifests/export.json --signature manifests/export.json.sig`<br>2. `stella export provenance <runId> --output manifests/provenance.json --signature manifests/provenance.json.sig`<br>3. `cosign verify-blob --key pubkeys/tenant.pem --signature manifests/export.json.sig manifests/export.json`<br>4. `cosign verify-blob --key pubkeys/tenant.pem --signature manifests/provenance.json.sig manifests/provenance.json` |
|
||||
| **Bundle verification (offline)** | 1. Extract bundle (or mount OCI artefact).<br>2. Validate manifest/provenance signatures using bundled public key.<br>3. Recompute SHA-256 for `data/` files and compare with entries in `export.json`.<br>4. If encrypted, decrypt with Age/AES-GCM recipient key, then re-run digest comparisons on decrypted content. |
|
||||
| **CI pipeline** | Use `stella export verify --manifest manifests/export.json --provenance manifests/provenance.json --signature manifests/export.json.sig --signature manifests/provenance.json.sig` (task `CLI-EXPORT-37-001`). Failure exits non-zero with reason codes (`ERR_EXPORT_SIG_INVALID`, `ERR_EXPORT_DIGEST_MISMATCH`). |
|
||||
| **Console download** | Console automatically verifies signatures before exposing the bundle; failure surfaces an actionable error referencing the export run ID and required remediation. |
|
||||
|
||||
Verification guidance (docs/cli/cli-reference.md §export) cross-links here; keep both docs in sync when CLI behaviour changes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Distribution considerations
|
||||
|
||||
- **HTTP headers.** `X-Export-Digest` includes bundle digest; `X-Export-Provenance` references `provenance.json` URL; `X-Export-Signature` references `.sig`. Clients use these hints to short-circuit re-downloads.
|
||||
- **OCI annotations.** `org.opencontainers.image.ref.name`, `io.stellaops.export.manifest-digest`, and `io.stellaops.export.provenance-ref` allow registry tooling to locate manifests/signatures quickly.
|
||||
- **Offline Kit staging.** Offline kit assembler copies `manifests/`, `signatures/`, and `pubkeys/` verbatim. Verification scripts (`offline-kits/bin/verify-export.sh`) wrap the cosign commands described above.
|
||||
|
||||
---
|
||||
|
||||
## 7. Failure handling & observability
|
||||
|
||||
- Runs surface signature status via `/api/export/runs/{id}` (`signing.status`, `signing.lastError`). Common errors include `ERR_EXPORT_KMS_UNAVAILABLE`, `ERR_EXPORT_ATTESTATION_FAILED`, `ERR_EXPORT_CANONICALIZE`.
|
||||
- Metrics: `exporter_sign_duration_seconds`, `exporter_sign_failures_total{error_code}`, `exporter_provenance_verify_failures_total`.
|
||||
- Logs: `phase=sign`, `error_code`, `signing_key_id`, `cosign_certificate_sn`.
|
||||
- Alerts: DevOps dashboards (task `DEVOPS-EXPORT-37-001`) trigger on consecutive signing failures or verification failures >0.
|
||||
|
||||
When verification fails downstream, operators should:
|
||||
1. Confirm signatures using the known-good key.
|
||||
2. Inspect `provenance.json` materials; rerun the source queries to ensure matching digests.
|
||||
3. Review run audit logs and retry export with `--resume` to regenerate manifests.
|
||||
|
||||
---
|
||||
|
||||
## 8. Compliance checklist
|
||||
|
||||
- [ ] Manifests and provenance documents generated with canonical JSON, deterministic digests, and signatures.
|
||||
- [ ] Cosign public keys published per tenant, rotated through Authority, and distributed to Offline Kit consumers.
|
||||
- [ ] SLSA attestations enabled where supply-chain requirements demand Level 3 evidence.
|
||||
- [ ] CLI/Console verification paths documented and tested (CI pipelines exercise `stella export verify`).
|
||||
- [ ] Encryption metadata (recipients, wrapped keys) recorded in provenance and validated during verification.
|
||||
- [ ] Run audit logs capture signature timestamps, signer identity, and failure reasons.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
246
docs/export-center/trivy-adapter.md
Normal file
246
docs/export-center/trivy-adapter.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Export Center Trivy Adapters
|
||||
|
||||
The Trivy adapters translate StellaOps normalized advisories into the format consumed by Aqua Security's Trivy scanner. They enable downstream tooling to reuse StellaOps' curated data without bespoke converters, while preserving Aggregation-Only Contract (AOC) boundaries. This guide documents bundle layouts, field mappings, compatibility guarantees, validation workflows, and configuration toggles introduced in Sprint 36 (`EXPORT-SVC-36-001`, `EXPORT-SVC-36-002`).
|
||||
|
||||
> The current Export Center build is wiring the API and workers. Treat this document as the canonical interface for adapter implementation and update any behavioural changes during task sign-off.
|
||||
|
||||
## 1. Adapter overview
|
||||
|
||||
| Variant | Bundle | Default profile | Notes |
|
||||
|---------|--------|-----------------|-------|
|
||||
| `trivy:db` | `db.bundle` | `trivy:db` | Core vulnerability database compatible with Trivy CLI >= 0.50.0 (schema v2). |
|
||||
| `trivy:java-db` | `java-db.bundle` | Optional extension | Java ecosystem supplement (Maven, Gradle). Enabled when `ExportCenter:Profiles:Trivy:EnableJavaDb=true`. |
|
||||
|
||||
Both variants ship inside the export run under `/export/trivy/`. Each bundle is a gzip-compressed tarball containing:
|
||||
|
||||
```
|
||||
metadata.json
|
||||
trivy.db # BoltDB file with vulnerability/provider tables
|
||||
packages/*.json # Only when schema requires JSON overlays (language ecosystems)
|
||||
```
|
||||
|
||||
The adapters never mutate input evidence. They only reshape normalized advisories and copy the exact upstream references so consumers can trace provenance.
|
||||
|
||||
## 2. Bundle layout
|
||||
|
||||
```
|
||||
trivy/
|
||||
db.bundle
|
||||
+-- metadata.json
|
||||
+-- trivy.db
|
||||
java-db.bundle # present when Java DB enabled
|
||||
+-- metadata.json
|
||||
+-- trivy-java.db
|
||||
+-- ecosystem/...
|
||||
signatures/
|
||||
trivy-db.sig
|
||||
trivy-java-db.sig
|
||||
```
|
||||
|
||||
`metadata.json` aligns with Trivy's expectations (`schemaVersion`, `buildInfo`, `updatedAt`, etc.). Export Center adds an `stella` block to capture profile id, run id, and policy snapshot hints.
|
||||
|
||||
Example `metadata.json` (trimmed):
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"buildInfo": {
|
||||
"trivyVersion": "0.50.1",
|
||||
"vulnerabilityDBVersion": "2025-10-28T00:00:00Z"
|
||||
},
|
||||
"updatedAt": "2025-10-29T11:42:03Z",
|
||||
"stella": {
|
||||
"runId": "run-20251029-01",
|
||||
"profileId": "prof-trivy-db",
|
||||
"tenant": "acme",
|
||||
"policySnapshotId": "policy-snap-42",
|
||||
"schemaVersion": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Field mappings
|
||||
|
||||
### 3.1 Namespace resolution
|
||||
|
||||
| Stella field | Trivy field | Notes |
|
||||
|--------------|-------------|-------|
|
||||
| `advisory.source.vendor` | `namespace` | Canonicalized to lowercase; e.g. `Ubuntu` -> `ubuntu`. |
|
||||
| `advisory.source.product` | `distribution` / `ecosystem` | Mapped via allowlist (`Ubuntu 22.04` -> `ubuntu:22.04`). |
|
||||
| `package.ecosystem` | `package.ecosystem` | OSS ecosystems (`npm`, `pip`, `nuget`, etc.). |
|
||||
| `package.nevra` / `package.evr` | `package.version` (OS) | RPM/DEB version semantics preserved. |
|
||||
|
||||
If a record lacks a supported namespace, the adapter drops it and logs `adapter.trivy.unsupported_namespace`.
|
||||
|
||||
### 3.2 Vulnerability metadata
|
||||
|
||||
| Stella field | Trivy field | Transformation |
|
||||
|--------------|-------------|----------------|
|
||||
| `advisory.identifiers.cve[]` | `vulnerability.CVEIDs` | Array of strings. |
|
||||
| `advisory.identifiers.aliases[]` | `vulnerability.CWEIDs` / `References` | CVE -> `CVEIDs`, others appended to `References`. |
|
||||
| `advisory.summary` | `vulnerability.Title` | Stripped to 256 chars; rest moved to `Description`. |
|
||||
| `advisory.description` | `vulnerability.Description` | Markdown allowed, normalized to LF line endings. |
|
||||
| `advisory.severity.normalized` | `vulnerability.Severity` | Uses table below. |
|
||||
| `advisory.cvss[]` | `vulnerability.CVSS` | Stored as `{"vector": "...", "score": 7.8, "source": "NVD"}`. |
|
||||
| `advisory.published` | `vulnerability.PublishedDate` | ISO 8601 UTC. |
|
||||
| `advisory.modified` | `vulnerability.LastModifiedDate` | ISO 8601 UTC. |
|
||||
| `advisory.vendorStatement` | `vulnerability.VendorSeverity` / `VendorVectors` | Preserved in vendor block. |
|
||||
|
||||
Severity mapping:
|
||||
|
||||
| Stella severity | Trivy severity |
|
||||
|-----------------|----------------|
|
||||
| `critical` | `CRITICAL` |
|
||||
| `high` | `HIGH` |
|
||||
| `medium` | `MEDIUM` |
|
||||
| `low` | `LOW` |
|
||||
| `none` / `info` | `UNKNOWN` |
|
||||
|
||||
### 3.3 Affected packages
|
||||
|
||||
| Stella field | Trivy field | Notes |
|
||||
|--------------|-------------|-------|
|
||||
| `package.name` | `package.name` | For OS distros uses source package when available. |
|
||||
| `package.purl` | `package.PURL` | Copied verbatim. |
|
||||
| `affects.vulnerableRange` | `package.vulnerableVersionRange` | SemVer or distro version range. |
|
||||
| `remediations.fixedVersion` | `package.fixedVersion` | Latest known fix. |
|
||||
| `remediations.urls[]` | `package.links` | Array; duplicates removed. |
|
||||
| `states.cpes[]` | `package.cpes` | For CPE-backed advisories. |
|
||||
|
||||
The adapter deduplicates entries by `(namespace, package.name, vulnerableRange)` to avoid duplicate records when multiple upstream segments agree.
|
||||
|
||||
Example mapping (Ubuntu advisory):
|
||||
|
||||
```jsonc
|
||||
// Stella normalized input
|
||||
{
|
||||
"source": {"vendor": "Ubuntu", "product": "22.04"},
|
||||
"identifiers": {"cve": ["CVE-2024-12345"]},
|
||||
"severity": {"normalized": "high"},
|
||||
"affects": [{
|
||||
"package": {"name": "openssl", "ecosystem": "ubuntu", "nevra": "1.1.1f-1ubuntu2.12"},
|
||||
"vulnerableRange": "< 1.1.1f-1ubuntu2.13",
|
||||
"remediations": [{"fixedVersion": "1.1.1f-1ubuntu2.13"}]
|
||||
}]
|
||||
}
|
||||
|
||||
// Trivy vulnerability entry
|
||||
{
|
||||
"namespace": "ubuntu",
|
||||
"package": {
|
||||
"name": "openssl",
|
||||
"version": "< 1.1.1f-1ubuntu2.13",
|
||||
"fixedVersion": "1.1.1f-1ubuntu2.13"
|
||||
},
|
||||
"vulnerability": {
|
||||
"ID": "CVE-2024-12345",
|
||||
"Severity": "HIGH"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 Java DB specifics
|
||||
|
||||
The Java supplement only includes ecosystems `maven`, `gradle`, `sbt`. Additional fields:
|
||||
|
||||
| Stella field | Trivy Java field | Notes |
|
||||
|--------------|------------------|-------|
|
||||
| `package.group` | `GroupID` | Derived from Maven coordinates. |
|
||||
| `package.artifact` | `ArtifactID` | Derived from Maven coordinates. |
|
||||
| `package.version` | `Version` | Compared with semver-lite rules. |
|
||||
| `affects.symbolicRanges[]` | `VulnerableVersions` | Strings like `[1.0.0,1.2.3)`. |
|
||||
|
||||
## 4. Compatibility matrix
|
||||
|
||||
| Trivy version | Schema version | Supported by adapter | Notes |
|
||||
|---------------|----------------|----------------------|-------|
|
||||
| 0.46.x | 2 | Yes | Baseline compatibility target. |
|
||||
| 0.50.x | 2 | Yes | Default validation target in CI. |
|
||||
| 0.51.x+ | 3 | Pending | Adapter throws `ERR_EXPORT_UNSUPPORTED_SCHEMA` until implemented. |
|
||||
|
||||
Schema mismatches emit `adapter.trivy.unsupported_schema_version` and abort the run. Operators can pin the schema via `ExportCenter:Adapters:Trivy:SchemaVersion`.
|
||||
|
||||
## 5. Validation workflow
|
||||
|
||||
1. **Unit tests** (`StellaOps.ExportCenter.Tests`):
|
||||
- Mapping tests for OS and ecosystem packages.
|
||||
- Severity conversion and range handling property tests.
|
||||
2. **Integration tests** (`EXPORT-SVC-36-001`):
|
||||
- Generate bundle from fixture dataset.
|
||||
- Run `trivy module db import <bundle>` (Trivy CLI) to ensure the bundle is accepted.
|
||||
- For Java DB, run `trivy java-repo --db <bundle>` against sample repository.
|
||||
3. **CI smoke (`DEVOPS-EXPORT-36-001`)**:
|
||||
- Validate metadata fields using `jq`.
|
||||
- Ensure signatures verify with `cosign`.
|
||||
- Check runtime by invoking `trivy fs --cache-dir <temp> --skip-update --custom-db <bundle> fixtures/image`.
|
||||
|
||||
Failures set the run status to `failed` with `errorCode="adapter-trivy"` so Console/CLI expose the reason.
|
||||
|
||||
## 6. Configuration knobs
|
||||
|
||||
```yaml
|
||||
ExportCenter:
|
||||
Adapters:
|
||||
Trivy:
|
||||
SchemaVersion: 2 # enforce schema version
|
||||
IncludeJavaDb: true # enable java-db.bundle
|
||||
AllowEmpty: false # fail when no records match
|
||||
MaxCvssVectorsPerEntry: 5 # truncate to avoid oversized payloads
|
||||
Distribution:
|
||||
Oras:
|
||||
TrivyRepository: "registry.example.com/stella/trivy-db"
|
||||
PublishDelta: false
|
||||
Download:
|
||||
FilenameFormat: "trivy-db-{runId}.tar.gz"
|
||||
IncludeMetadata: true
|
||||
```
|
||||
|
||||
- `AllowEmpty=false` converts empty datasets into `ERR_EXPORT_EMPTY`.
|
||||
- `MaxCvssVectorsPerEntry` prevents extremely large multi-vector advisories from bloating the DB.
|
||||
- `PublishDelta` works in tandem with the planner's delta logic; when true, only changed blobs are pushed.
|
||||
- `FilenameFormat` lets operators align downloads with existing mirror tooling.
|
||||
- `IncludeMetadata` toggles whether `metadata.json` is stored alongside the bundle in the staging directory for quick inspection.
|
||||
|
||||
## 7. Distribution guidelines
|
||||
|
||||
- **Download profile**: `db.bundle` placed under `/export/trivy/` and signed. Recommended filename `trivy-db-<runId>.tar.gz`.
|
||||
- **OCI push**: ORAS artifact with annotations:
|
||||
- `org.opencontainers.artifact.description=StellaOps Trivy DB`
|
||||
- `io.stella.export.profile=trivy:db`
|
||||
- `io.stella.export.run=<runId>`
|
||||
- `io.stella.export.schemaVersion=2`
|
||||
- **Offline Kit**: When `offlineBundle.includeTrivyDb=true`, the exporter copies the latest full bundle plus the last `N` deltas (configurable) with manifests for quick import.
|
||||
|
||||
Consumers should always verify signatures using `trivy-db.sig` / `trivy-java-db.sig` before trusting the bundle.
|
||||
|
||||
Example verification flow:
|
||||
|
||||
```bash
|
||||
cosign verify-blob \
|
||||
--key tenants/acme/export-center.pub \
|
||||
--signature signatures/trivy-db.sig \
|
||||
trivy/db.bundle
|
||||
|
||||
trivy module db import trivy/db.bundle --cache-dir /tmp/trivy-cache
|
||||
```
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Remedy |
|
||||
|---------|--------------|--------|
|
||||
| `ERR_EXPORT_UNSUPPORTED_SCHEMA` | Trivy CLI updated schema version. | Bump `SchemaVersion`, extend mapping tables, regenerate fixtures. |
|
||||
| `adapter.trivy.unsupported_namespace` | Advisory namespace not in allowlist. | Extend namespace mapping or exclude in selector. |
|
||||
| `trivy import` fails with "invalid bolt page" | Corrupted bundle or truncated upload. | Re-run export; verify storage backend and signatures. |
|
||||
| Missing Java advisories | `IncludeJavaDb=false` or no Java data in Findings Ledger. | Enable flag and confirm upstream connectors populate Java ecosystems. |
|
||||
| Severity downgraded to UNKNOWN | Source severity missing or unrecognized. | Ensure upstream connectors populate severity or supply CVSS scores. |
|
||||
| `ERR_EXPORT_EMPTY` returned unexpectedly | Selectors yielded zero records while `AllowEmpty=false`. | Review selectors; set `AllowEmpty=true` if empty exports are acceptable. |
|
||||
|
||||
## 9. References
|
||||
|
||||
- [Export Center API reference](api.md)
|
||||
- [Export Center CLI Guide](cli.md)
|
||||
- [Export Center Architecture](architecture.md)
|
||||
- [Export Center Overview](overview.md)
|
||||
- [Aqua Security Trivy documentation](https://aquasecurity.github.io/trivy/dev/database/structure/) *(external reference for schema expectations)*
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
@@ -76,6 +76,10 @@
|
||||
- `references`: Array of `{ type, url }` pairs pointing back to vendor advisories, patches, or exploits.
|
||||
- `reconciled_from`: Provenance of linkset entries (JSON Pointer or field origin) to make automated checks auditable.
|
||||
|
||||
Canonicalisation rules:
|
||||
- Package URLs are rendered in canonical form without qualifiers/subpaths (`pkg:type/namespace/name@version`).
|
||||
- CPE values are normalised to the 2.3 binding (`cpe:2.3:part:vendor:product:version:*:*:*:*:*:*:*`).
|
||||
|
||||
### 4.4 `advisory_observations`
|
||||
|
||||
`advisory_observations` is an immutable projection of the validated raw document used by Link‑Not‑Merge overlays. Fields mirror the JSON contract surfaced by `StellaOps.Concelier.Models.Observations.AdvisoryObservation`.
|
||||
|
||||
118
docs/notifications/architecture.md
Normal file
118
docs/notifications/architecture.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Notifications Architecture
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
This dossier distils the Notify architecture into implementation-ready guidance for service owners, SREs, and integrators. It complements the high-level overview by detailing process boundaries, persistence models, and extensibility points.
|
||||
|
||||
---
|
||||
|
||||
## 1. Runtime shape
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ Authority (OpTok)│
|
||||
└───────┬──────────┘
|
||||
│
|
||||
┌───────▼──────────┐ ┌───────────────┐
|
||||
│ Notify.WebService│◀──────▶│ MongoDB │
|
||||
Tenant API│ REST + gRPC WIP │ │ rules/channels│
|
||||
└───────▲──────────┘ │ deliveries │
|
||||
│ │ digests │
|
||||
Internal bus │ └───────────────┘
|
||||
(NATS/Redis/etc) │
|
||||
│
|
||||
┌─────────▼─────────┐ ┌───────────────┐
|
||||
│ Notify.Worker │◀────▶│ Redis / Cache │
|
||||
│ rule eval + render│ │ throttles/locks│
|
||||
└─────────▲─────────┘ └───────▲───────┘
|
||||
│ │
|
||||
│ │
|
||||
┌──────┴──────┐ ┌─────────┴────────┐
|
||||
│ Connectors │──────▶│ Slack/Teams/... │
|
||||
│ (plug-ins) │ │ External targets │
|
||||
└─────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
- **WebService** hosts REST endpoints (`/channels`, `/rules`, `/templates`, `/deliveries`, `/digests`, `/stats`) and handles schema normalisation, validation, and Authority enforcement.
|
||||
- **Worker** subscribes to the platform event bus, evaluates rules per tenant, applies throttles/digests, renders payloads, writes ledger entries, and invokes connectors.
|
||||
- **Plug-ins** live under `plugins/notify/` and are loaded deterministically at service start (`orderedPlugins` list). Each implements connector contracts and optional health/test-preview providers.
|
||||
|
||||
Both services share options via `notify.yaml` (see `etc/notify.yaml.sample`). For dev/test scenarios, an in-memory repository exists but production requires Mongo + Redis/NATS for durability and coordination.
|
||||
|
||||
---
|
||||
|
||||
## 2. Event ingestion and rule evaluation
|
||||
|
||||
1. **Subscription.** Workers attach to the internal bus (Redis Streams or NATS JetStream). Each partition key is `tenantId|scope.digest|event.kind` to preserve order for a given artefact.
|
||||
2. **Normalisation.** Incoming events are hydrated into `NotifyEvent` envelopes. Payload JSON is normalised (sorted object keys) to preserve determinism and enable hashing.
|
||||
3. **Rule snapshot.** Per-tenant rule sets are cached in memory. Change streams from Mongo trigger snapshot refreshes without restart.
|
||||
4. **Match pipeline.**
|
||||
- Tenant check (`rule.tenantId` vs. event tenant).
|
||||
- Kind/namespace/repository/digest filters.
|
||||
- Severity and KEV gating based on event deltas.
|
||||
- VEX gating using `NotifyRuleMatchVex`.
|
||||
- Action iteration with throttle/digest decisions.
|
||||
5. **Idempotency.** Each action computes `hash(ruleId|actionId|event.kind|scope.digest|delta.hash|dayBucket)`; matches within throttle TTL record `status=Throttled` and stop.
|
||||
6. **Dispatch.** If digest is `instant`, the renderer immediately processes the action. Otherwise the event is appended to the digest window for later flush.
|
||||
|
||||
Failures during evaluation are logged with correlation IDs and surfaced through `/stats` and worker metrics (`notify_rule_eval_failures_total`, `notify_digest_flush_errors_total`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Rendering & connectors
|
||||
|
||||
- **Template resolution.** The renderer picks the template in this order: action template → channel default template → locale fallback → built-in minimal template. Locale negotiation reduces `en-US` to `en-us`.
|
||||
- **Helpers & partials.** Exposed helpers mirror the list in [`notifications/templates.md`](templates.md#3-variables-helpers-and-context). Plug-ins may register additional helpers but must remain deterministic and side-effect free.
|
||||
- **Rendering output.** `NotifyDeliveryRendered` captures:
|
||||
- `channelType`, `format`, `locale`
|
||||
- `title`, `body`, optional `summary`, `textBody`
|
||||
- `target` (redacted where necessary)
|
||||
- `attachments[]` (safe URLs or references)
|
||||
- `bodyHash` (lowercase SHA-256) for audit parity
|
||||
- **Connector contract.** Connectors implement `INotifyConnector` (send + health) and can implement `INotifyChannelTestProvider` for `/channels/{id}/test`. All plugs are single-tenant aware; secrets are pulled via references at send time and never persisted in Mongo.
|
||||
- **Retries.** Workers track attempts with exponential jitter. On permanent failure, deliveries are marked `Failed` with `statusReason`, and optional DLQ fan-out is slated for Sprint 40.
|
||||
|
||||
---
|
||||
|
||||
## 4. Persistence model
|
||||
|
||||
| Collection | Purpose | Key fields & indexes |
|
||||
|------------|---------|----------------------|
|
||||
| `rules` | Tenant rule definitions. | `_id`, `tenantId`, `enabled`; index on `{tenantId, enabled}`. |
|
||||
| `channels` | Channel metadata + config references. | `_id`, `tenantId`, `type`; index on `{tenantId, type}`. |
|
||||
| `templates` | Locale-specific render bodies. | `_id`, `tenantId`, `channelType`, `key`; index on `{tenantId, channelType, key}`. |
|
||||
| `deliveries` | Ledger of rendered notifications. | `_id`, `tenantId`, `sentAt`; compound index on `{tenantId, sentAt:-1}` for history queries. |
|
||||
| `digests` | Open digest windows per action. | `_id` (`tenantId:actionKey:window`), `status`; index on `{tenantId, actionKey}`. |
|
||||
| `throttles` | Short-lived throttle tokens (Mongo or Redis). | Key format `idem:<hash>` with TTL aligned to throttle duration. |
|
||||
|
||||
Documents are stored using the canonical JSON serializer (`NotifyCanonicalJsonSerializer`) to preserve property ordering and casing. Schema migration helpers upgrade stored documents when new versions ship.
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployment & configuration
|
||||
|
||||
- **Configuration sources.** YAML files feed typed options (`NotifyMongoOptions`, `NotifyWorkerOptions`, etc.). Environment variables can override connection strings and rate limits for production.
|
||||
- **Authority integration.** Two OAuth clients (`notify-web`, `notify-web-dev`) with scopes `notify.read` and `notify.admin` are required. Authority enforcement can be disabled for air-gapped dev use by providing `developmentSigningKey`.
|
||||
- **Plug-in management.** `plugins.baseDirectory` and `orderedPlugins` guarantee deterministic loading. Offline Kits copy the plug-in tree verbatim; operations must keep the order aligned across environments.
|
||||
- **Observability.** Workers expose structured logs (`ruleId`, `actionId`, `eventId`, `throttleKey`). Metrics include:
|
||||
- `notify_rule_matches_total{tenant,eventKind}`
|
||||
- `notify_delivery_attempts_total{channelType,status}`
|
||||
- `notify_digest_open_windows{window}`
|
||||
- Optional OpenTelemetry traces for rule evaluation and connector round-trips.
|
||||
- **Scaling levers.** Increase worker replicas to cope with bus throughput; adjust `worker.prefetchCount` for Redis Streams or `ackWait` for NATS JetStream. WebService remains stateless and scales horizontally behind the gateway.
|
||||
|
||||
---
|
||||
|
||||
## 6. Roadmap alignment
|
||||
|
||||
| Backlog | Architectural note |
|
||||
|---------|--------------------|
|
||||
| `NOTIFY-SVC-38-001` | Standardise event envelope publication (idempotency keys) – ensure bus bindings use the documented key format. |
|
||||
| `NOTIFY-SVC-38-002..004` | Introduce simulation endpoints and throttle dashboards – expect additional `/internal/notify/simulate` routes and metrics; update once merged. |
|
||||
| `NOTIFY-SVC-39-001..004` | Correlation engine, digests generator, simulation API, quiet hours – anticipate new Mongo documents (`quietHours`, correlation caches) and connector metadata (quiet mode hints). Review this guide when implementations land. |
|
||||
|
||||
Action: schedule a documentation sync with the Notifications Service Guild immediately after `NOTIFY-SVC-39-001..004` merge to confirm schema adjustments (e.g., correlation edge storage, quiet hour calendars) and add any new persistence or API details here.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
92
docs/notifications/digests.md
Normal file
92
docs/notifications/digests.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Notifications Digests
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
Digests coalesce multiple matching events into a single notification when rules request batched delivery. They protect responders from alert storms while preserving a deterministic record of every input.
|
||||
|
||||
---
|
||||
|
||||
## 1. Digest lifecycle
|
||||
|
||||
1. **Window selection.** Rule actions opt into a digest cadence by setting `actions[].digest` (`instant`, `5m`, `15m`, `1h`, `1d`). `instant` skips digest logic entirely.
|
||||
2. **Aggregation.** When an event matches, the worker appends it to the open digest window (`tenantId + actionId + window`). Events include the canonical scope, delta counts, and references.
|
||||
3. **Flush.** When the window expires or hits the worker’s safety cap (configurable), the worker renders a digest template and emits a single delivery with status `Digested`.
|
||||
4. **Audit.** The delivery ledger links back to the digest document so operators can inspect individual items and the aggregated summary.
|
||||
|
||||
---
|
||||
|
||||
## 2. Storage model
|
||||
|
||||
Digest state lives in Mongo (`digests` collection) and mirrors the schema described in [ARCHITECTURE_NOTIFY.md](../ARCHITECTURE_NOTIFY.md#7-data-model-mongo):
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": "tenant-dev:act-email-compliance:1h",
|
||||
"tenantId": "tenant-dev",
|
||||
"actionKey": "act-email-compliance",
|
||||
"window": "1h",
|
||||
"openedAt": "2025-10-24T08:00:00Z",
|
||||
"status": "open",
|
||||
"items": [
|
||||
{
|
||||
"eventId": "00000000-0000-0000-0000-000000000001",
|
||||
"scope": {
|
||||
"namespace": "prod-payments",
|
||||
"repo": "ghcr.io/acme/api",
|
||||
"digest": "sha256:…"
|
||||
},
|
||||
"delta": {
|
||||
"newCritical": 1,
|
||||
"kev": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- `status` reflects whether the window is currently collecting (`open`) or has been completed (`closed`). Future revisions may introduce `flushing` for in-progress operations.
|
||||
- `items[].delta` captures aggregated counts for reporting (e.g., new critical findings, KEV, quieted).
|
||||
- Workers use optimistic concurrency on the document ID to avoid duplicate flushes across replicas.
|
||||
|
||||
---
|
||||
|
||||
## 3. Rendering and templates
|
||||
|
||||
- Digest deliveries use the same template engine as instant notifications. Templates receive an additional `digest` object with `window`, `openedAt`, `itemCount`, and `items` (findings grouped by namespace/repository when available).
|
||||
- Provide digest-specific templates (e.g., `tmpl-digest-hourly`) so the body can enumerate top offenders, summarise totals, and link to detailed dashboards.
|
||||
- When no template is specified, Notify falls back to channel defaults that emphasise summary counts and redirect to Console for detail.
|
||||
|
||||
---
|
||||
|
||||
## 4. API surface
|
||||
|
||||
| Endpoint | Description | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `POST /digests` | Issues administrative commands (e.g., force flush, reopen) for a specific action/window. | Request body specifies the command target; requires `notify.admin`. |
|
||||
| `GET /digests/{actionKey}` | Returns the currently open window (if any) for the referenced action. | Supports operators/CLI inspecting pending digests; requires `notify.read`. |
|
||||
| `DELETE /digests/{actionKey}` | Drops the open window without notifying (emergency stop). | Emits an audit record; use sparingly. |
|
||||
|
||||
All routes honour the tenant header and reuse the standard Notify rate limits.
|
||||
|
||||
---
|
||||
|
||||
## 5. Worker behaviour and safety nets
|
||||
|
||||
- **Idempotency.** Flush operations generate a deterministic digest delivery ID (`digest:<tenant>:<actionId>:<window>:<openedAt>`). Retries reuse the same ID.
|
||||
- **Throttles.** Digest generation respects action throttles; setting an aggressive throttle together with a digest window may result in deliberate skips (logged as `Throttled` in the delivery ledger).
|
||||
- **Quiet hours.** Future sprint work (`NOTIFY-SVC-39-004`) integrates quiet-hour calendars. When enabled, flush timers pause during quiet windows and resume afterwards.
|
||||
- **Back-pressure.** When the window reaches the configured item cap before the timer, the worker flushes early and starts a new window immediately.
|
||||
- **Crash resilience.** Workers rebuild in-flight windows from Mongo on startup; partially flushed windows remain closed after success or reopened if the flush fails.
|
||||
|
||||
---
|
||||
|
||||
## 6. Operator guidance
|
||||
|
||||
- Choose hourly digests for high-volume compliance events; daily digests suit executive reporting.
|
||||
- Pair digests with incident-focused instant rules so critical items surface immediately while less urgent noise is summarised.
|
||||
- Monitor `/stats` output for `openDigestCount` to ensure windows are flushing; spikes may indicate downstream connector failures.
|
||||
- When testing new digest templates, open a small (`5m`) window, trigger sample events, then call `POST /digests/{actionId}/flush` to validate rendering before moving to longer cadences.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
76
docs/notifications/overview.md
Normal file
76
docs/notifications/overview.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Notifications Overview
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
Notifications Studio turns raw platform events into concise, tenant-scoped alerts that reach the right responders without overwhelming them. The service is sovereign/offline-first, follows the Aggregation-Only Contract (AOC), and produces deterministic outputs so the same configuration yields identical deliveries across environments.
|
||||
|
||||
---
|
||||
|
||||
## 1. Mission & value
|
||||
|
||||
- **Reduce noise.** Only materially new or high-impact changes reach chat, email, or webhooks thanks to rule filters, throttles, and digest windows.
|
||||
- **Explainable results.** Every delivery is traceable back to a rule, action, and event payload stored in the delivery ledger; operators can audit what fired and why.
|
||||
- **Safe by default.** Secrets remain in external stores, templates are sandboxed, quiet hours and throttles prevent storms, and idempotency guarantees protect downstream systems.
|
||||
- **Offline-aligned.** All configuration, templates, and plug-ins ship with Offline Kits; no external SaaS is required to send notifications.
|
||||
|
||||
---
|
||||
|
||||
## 2. Core capabilities
|
||||
|
||||
| Capability | What it does | Key docs |
|
||||
|------------|--------------|----------|
|
||||
| Rules engine | Declarative matchers for event kinds, severities, namespaces, VEX context, KEV flags, and more. | [`notifications/rules.md`](rules.md) |
|
||||
| Channel catalog | Slack, Teams, Email, Webhook connectors loaded via restart-time plug-ins; metadata stored without secrets. | [`notifications/architecture.md`](architecture.md) |
|
||||
| Templates | Locale-aware, deterministic rendering via safe helpers; channel defaults plus tenant-specific overrides. | [`notifications/templates.md`](templates.md) |
|
||||
| Digests | Coalesce bursts into periodic summaries with deterministic IDs and audit trails. | [`notifications/digests.md`](digests.md) |
|
||||
| Delivery ledger | Tracks rendered payload hashes, attempts, throttles, and outcomes for every action. | [`ARCHITECTURE_NOTIFY.md`](../ARCHITECTURE_NOTIFY.md#7-data-model-mongo) |
|
||||
|
||||
---
|
||||
|
||||
## 3. How it fits into Stella Ops
|
||||
|
||||
1. **Producers emit events.** Scanner, Scheduler, VEX Lens, Attestor, and Zastava publish canonical envelopes (`NotifyEvent`) onto the internal bus.
|
||||
2. **Notify.Worker evaluates rules.** For each tenant, the worker applies match filters, VEX gates, throttles, and digest policies before rendering the action.
|
||||
3. **Connectors deliver.** Channel plug-ins send the rendered payload to Slack/Teams/Email/Webhook targets and report back attempts and outcomes.
|
||||
4. **Consumers investigate.** Operators pivot from message links into Console dashboards, SBOM views, or policy overlays with correlation IDs preserved.
|
||||
|
||||
The Notify WebService fronts worker state with REST APIs used by the UI and CLI. Tenants authenticate via StellaOps Authority scopes `notify.read` and `notify.admin`. All operations require the tenant header (`X-StellaOps-Tenant`) to preserve sovereignty boundaries.
|
||||
|
||||
---
|
||||
|
||||
## 4. Operating model
|
||||
|
||||
| Area | Guidance |
|
||||
|------|----------|
|
||||
| **Tenancy** | Each rule, channel, template, and delivery belongs to exactly one tenant. Cross-tenant sharing is intentionally unsupported. |
|
||||
| **Determinism** | Configuration persistence normalises strings and sorts collections. Template rendering produces identical `bodyHash` values when inputs match. |
|
||||
| **Scaling** | Workers scale horizontally; per-tenant rule snapshots are cached and refreshed from Mongo change streams. Redis (or equivalent) guards throttles and locks. |
|
||||
| **Offline** | Offline Kits include plug-ins, default templates, and seed rules. Operators can edit YAML/JSON manifests before air-gapped deployment. |
|
||||
| **Security** | Channel secrets use indirection (`secretRef`), Authority-protected OAuth clients secure API access, and delivery payloads are redacted before storage where required. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Getting started (first 30 minutes)
|
||||
|
||||
| Step | Goal | Reference |
|
||||
|------|------|-----------|
|
||||
| 1 | Deploy Notify WebService + Worker with Mongo and Redis | [`ARCHITECTURE_NOTIFY.md`](../ARCHITECTURE_NOTIFY.md#1-runtime-shape--projects) |
|
||||
| 2 | Register OAuth clients/scopes in Authority | [`etc/authority.yaml.sample`](../etc/authority.yaml.sample) |
|
||||
| 3 | Install channel plug-ins and capture secret references | [`plugins/notify`](../../plugins) |
|
||||
| 4 | Create a tenant rule and test preview | [`POST /channels/{id}/test`](../ARCHITECTURE_NOTIFY.md#8-external-apis-webservice) |
|
||||
| 5 | Inspect deliveries and digests | `/api/v1/notify/deliveries`, `/api/v1/notify/digests` |
|
||||
|
||||
---
|
||||
|
||||
## 6. Alignment with implementation work
|
||||
|
||||
| Backlog item | Impact on docs | Status |
|
||||
|--------------|----------------|--------|
|
||||
| `NOTIFY-SVC-38-001..004` | Foundational correlation, throttling, simulation hooks. | **In progress** – align behaviour once services publish beta APIs. |
|
||||
| `NOTIFY-SVC-39-001..004` | Adds correlation engine, digest generator, simulation API, quiet hours. | **Pending** – revisit rule/digest sections when these tasks merge. |
|
||||
|
||||
Action: coordinate with the Notifications Service Guild when `NOTIFY-SVC-39-001..004` land to validate payload fields, quiet-hours semantics, and any new connector metadata that should be documented here and in the channel-specific guides.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
62
docs/notifications/pack-approvals-integration.md
Normal file
62
docs/notifications/pack-approvals-integration.md
Normal file
@@ -0,0 +1,62 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Pack Approval Notification Integration — Requirements
|
||||
|
||||
## Overview
|
||||
|
||||
Task Runner now produces pack plans with explicit approval and policy-gate metadata. The Notifications service must ingest those events, persist their state, and fan out actionable alerts (approvals requested, policy holds, resumptions). This document captures the requirements for the first Notifications sprint dedicated to the Task Runner bridge.
|
||||
|
||||
Deliverables feed Sprint 37 tasks (`NOTIFY-SVC-37-00x`) and unblock Task Runner sprint 43 (`TASKRUN-43-001`).
|
||||
|
||||
## Functional Requirements
|
||||
|
||||
### 1. Approval Event Contract
|
||||
- Define a canonical schema for **PackApprovalRequested** and **PackApprovalUpdated** events.
|
||||
- Fields must include `runId`, `approvalId`, tenant context, plan hash, required grants, step identifiers, message template, and resume callback metadata.
|
||||
- Provide an OpenAPI fragment and x-go/x-cs models for Task Runner and CLI compatibility.
|
||||
- Document error/acknowledgement semantics (success, retryable failure, validation failure).
|
||||
|
||||
### 2. Ingestion & Persistence
|
||||
- Expose a secure Notifications API endpoint (`POST /notifications/pack-approvals`) receiving Task Runner events.
|
||||
- Validate scope (`Packs.Approve`, `Notifier.Events:Write`) and tenant match.
|
||||
- Persist approval state transitions in Mongo (`notifications.pack_approvals`) with indexes on run/approval/tenant.
|
||||
- Store outbound notification audit records with correlation IDs to support Task Runner resume flow.
|
||||
|
||||
### 3. Notification Routing
|
||||
- Derive recipients from new rule predicates (`event.kind == "pack.approval"`).
|
||||
- Render approval templates (email + webhook JSON) including plan metadata and approval links (resume token).
|
||||
- Emit policy gate notifications as “hold” incidents with context (parameters, messages).
|
||||
- Support localization fallback and redaction of secrets (never ship approval tokens unencrypted).
|
||||
|
||||
### 4. Resume & Ack Handshake
|
||||
- Provide an approval ack endpoint (`POST /notifications/pack-approvals/{runId}/{approvalId}/ack`) that records decision metadata and forwards to Task Runner resume hook (HTTP callback + message bus placeholder).
|
||||
- Return structured responses with resume token / status for CLI integration.
|
||||
- Ensure idempotent updates (dedupe by runId + approvalId + decisionHash).
|
||||
|
||||
### 5. Observability & Security
|
||||
- Emit metrics for approval notifications queued/sent, outstanding approvals, and acknowledgement latency.
|
||||
- Log audit trail events (`pack.approval.requested`, `pack.approval.acknowledged`, `pack.policy.hold`).
|
||||
- Enforce HMAC or mTLS for Task Runner -> Notifier ingestion; support configurable IP allowlist.
|
||||
- Provide chaos-test plan for notification failure modes (channel outage, storage failure).
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
- Deterministic processing: identical approval events lead to identical outbound notifications (idempotent).
|
||||
- Timeouts: ingestion endpoint must respond < 500 ms under nominal load.
|
||||
- Retry strategy: Task Runner expects 5xx/429 for transient errors; document backoff guidance.
|
||||
- Data retention: approval records retained 90 days, purge job tracked under ops runbook.
|
||||
|
||||
## Sprint 37 Task Mapping
|
||||
|
||||
| Task ID | Scope |
|
||||
| --- | --- |
|
||||
| **NOTIFY-SVC-37-001** | Author this contract doc, OpenAPI fragment, and schema references. Coordinate with Task Runner/Authority guilds. |
|
||||
| **NOTIFY-SVC-37-002** | Implement secure ingestion endpoint, Mongo persistence, and audit hooks. Provide integration tests with sample events. |
|
||||
| **NOTIFY-SVC-37-003** | Build approval/policy notification templates, routing rules, and channel dispatch (email + webhook). |
|
||||
| **NOTIFY-SVC-37-004** | Ship acknowledgement endpoint + Task Runner callback client, resume token handling, and metrics/dashboards. |
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. Who owns approval resume callback (Task Runner Worker vs Orchestrator)? Resolve before NOTIFY-SVC-37-004.
|
||||
2. Should approvals generate incidents in existing incident schema or dedicated collection? Decision impacts Mongo design.
|
||||
3. Authority scopes for approval ingestion/ack — reuse `Packs.Approve` or introduce `Packs.Approve:notify`? Coordinate with Authority team.
|
||||
147
docs/notifications/rules.md
Normal file
147
docs/notifications/rules.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Notifications Rules
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
Rules decide which platform events deserve a notification, how aggressively they should be throttled, and which channels/actions should run. They are tenant-scoped contracts that guarantee deterministic routing across Notify.Worker replicas.
|
||||
|
||||
---
|
||||
|
||||
## 1. Rule lifecycle
|
||||
|
||||
1. **Authoring.** Operators create or update rules through the Notify WebService (`POST /rules`, `PATCH /rules/{id}`) or UI. Payloads are normalised to the current `NotifyRule` schema version.
|
||||
2. **Evaluation.** Notify.Worker evaluates enabled rules per incoming event. Tenancy is enforced first, followed by match filters, VEX gates, throttles, and digest handling.
|
||||
3. **Delivery.** Matching actions are enqueued with an idempotency key to prevent storm loops. Throttle rejections and digest coalescing are recorded in the delivery ledger.
|
||||
4. **Audit.** Every change carries `createdBy`/`updatedBy` plus timestamps; the delivery ledger references `ruleId`/`actionId` for traceability.
|
||||
|
||||
---
|
||||
|
||||
## 2. Rule schema reference
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `ruleId` | string | Stable identifier; clients may provide UUID/slug. |
|
||||
| `tenantId` | string | Must match the tenant header supplied when the rule is created. |
|
||||
| `name` | string | Display label shown in UI and audits. |
|
||||
| `description` | string? | Optional operator-facing note. |
|
||||
| `enabled` | bool | Disabled rules remain stored but skipped during evaluation. |
|
||||
| `labels` | map<string,string> | Sorted, trimmed key/value tags supporting filtering. |
|
||||
| `metadata` | map<string,string> | Reserved for automation; stored verbatim (sorted). |
|
||||
| `match` | [`NotifyRuleMatch`](#3-match-filters) | Declarative filters applied before actions execute. |
|
||||
| `actions[]` | [`NotifyRuleAction`](#4-actions-throttles-and-digests) | Ordered set of channel dispatchers; minimum one. |
|
||||
| `createdBy`/`createdAt` | string?, instant | Populated automatically when omitted. |
|
||||
| `updatedBy`/`updatedAt` | string?, instant | Defaults to creation values when unspecified. |
|
||||
| `schemaVersion` | string | Auto-upgraded during persistence; use for migrations. |
|
||||
|
||||
Rules are immutable snapshots; updates produce a full document write so workers observing change streams can refresh caches deterministically.
|
||||
|
||||
---
|
||||
|
||||
## 3. Match filters
|
||||
|
||||
`NotifyRuleMatch` narrows which events trigger the rule. All string collections are trimmed, deduplicated, and sorted to guarantee deterministic evaluation.
|
||||
|
||||
| Field | Type | Behaviour |
|
||||
|-------|------|-----------|
|
||||
| `eventKinds[]` | string | Lower-cased; supports any canonical Notify event (`scanner.report.ready`, `scheduler.rescan.delta`, `zastava.admission`, etc.). Empty list matches all kinds. |
|
||||
| `namespaces[]` | string | Exact match against `event.scope.namespace`. Supports glob-style filters via upstream enrichment (planned). |
|
||||
| `repositories[]` | string | Matches `event.scope.repo`. |
|
||||
| `digests[]` | string | Lower-cased; matches `event.scope.digest`. |
|
||||
| `labels[]` | string | Matches event attributes or delta labels (`kev`, `critical`, `license`, …). |
|
||||
| `componentPurls[]` | string | Matches component identifiers inside the event payload when provided. |
|
||||
| `minSeverity` | string? | Lower-cased severity gate (e.g., `medium`, `high`, `critical`). Evaluated on new findings inside event deltas; events lacking severity bypass this gate unless set. |
|
||||
| `verdicts[]` | string | Accepts scan/report verdicts (`fail`, `warn`, `block`, `escalate`, `deny`). |
|
||||
| `kevOnly` | bool? | When `true`, only KEV-tagged findings fire. |
|
||||
| `vex` | object | Additional gating aligned with VEX consensus; see below. |
|
||||
|
||||
### 3.1 VEX gates
|
||||
|
||||
`NotifyRuleMatchVex` offers fine-grained control when VEX findings accompany events:
|
||||
|
||||
| Field | Default | Effect |
|
||||
|-------|---------|--------|
|
||||
| `includeAcceptedJustifications` | `true` | Include findings marked `not_affected`/`acceptable` in consensus. |
|
||||
| `includeRejectedJustifications` | `false` | Surface findings the consensus rejected. |
|
||||
| `includeUnknownJustifications` | `false` | Allow findings without explicit justification. |
|
||||
| `justificationKinds[]` | `[]` | Optional allow-list of justification codes (e.g., `exploit_observed`, `component_not_present`). |
|
||||
|
||||
If the VEX block filters out every applicable finding, the rule is treated as a non-match and no actions run.
|
||||
|
||||
---
|
||||
|
||||
## 4. Actions, throttles, and digests
|
||||
|
||||
Each rule requires at least one action. Actions are deduplicated and sorted by `actionId`, so prefer deterministic identifiers.
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `actionId` | string | Stable identifier unique within the rule. |
|
||||
| `channel` | string | Reference to a channel (`channelId`) configured in `/channels`. |
|
||||
| `template` | string? | Template key to use for rendering; falls back to channel default when omitted. |
|
||||
| `digest` | string? | Digest window key (`instant`, `5m`, `15m`, `1h`, `1d`). `instant` bypasses coalescing. |
|
||||
| `throttle` | ISO8601 duration? | Optional throttle TTL (`PT300S`, `PT1H`). Prevents duplicate deliveries when the same idempotency hash appears before expiry. |
|
||||
| `locale` | string? | BCP-47 tag (stored lower-case). Template lookup falls back to channel locale then `en-us`. |
|
||||
| `enabled` | bool | Disabled actions skip rendering but remain stored. |
|
||||
| `metadata` | map<string,string> | Connector-specific hints (priority, layout, etc.). |
|
||||
|
||||
### 4.1 Evaluation order
|
||||
|
||||
1. Verify channel exists and is enabled; disabled channels mark the delivery as `Dropped`.
|
||||
2. Apply throttle idempotency key: `hash(ruleId|actionId|event.kind|scope.digest|delta.hash|dayBucket)`. Hits are logged as `Throttled`.
|
||||
3. If the action defines a digest window other than `instant`, append the event to the open window and defer delivery until flush.
|
||||
4. When delivery proceeds, the renderer resolves the template, locale, and metadata before invoking the connector.
|
||||
|
||||
---
|
||||
|
||||
## 5. Example rule payload
|
||||
|
||||
```json
|
||||
{
|
||||
"ruleId": "rule-critical-soc",
|
||||
"tenantId": "tenant-dev",
|
||||
"name": "Critical scanner verdicts",
|
||||
"description": "Route KEV-tagged critical findings to SOC Slack with zero delay.",
|
||||
"enabled": true,
|
||||
"match": {
|
||||
"eventKinds": ["scanner.report.ready"],
|
||||
"labels": ["kev", "critical"],
|
||||
"minSeverity": "critical",
|
||||
"verdicts": ["fail", "block"],
|
||||
"kevOnly": true
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"actionId": "act-slack-critical",
|
||||
"channel": "chn-slack-soc",
|
||||
"template": "tmpl-critical",
|
||||
"digest": "instant",
|
||||
"throttle": "PT300S",
|
||||
"locale": "en-us",
|
||||
"metadata": {
|
||||
"priority": "p1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"labels": {
|
||||
"owner": "soc"
|
||||
},
|
||||
"metadata": {
|
||||
"revision": "12"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Dry-run calls (`POST /rules/{id}/test`) accept the same structure along with a sample Notify event payload to exercise match logic without invoking connectors.
|
||||
|
||||
---
|
||||
|
||||
## 6. Operational guidance
|
||||
|
||||
- Keep rule scopes narrow (namespace/repository) before relying on severity gates; this minimises noise and improves digest summarisation.
|
||||
- Always configure a throttle window for instant actions to protect against repeated upstream retries.
|
||||
- Use rule labels to organise dashboards and access control (e.g., `owner:soc`, `env:prod`).
|
||||
- Prefer tenant-specific rule IDs so Offline Kit exports remain deterministic across environments.
|
||||
- If a rule depends on derived metadata (e.g., policy verdict tags), list those dependencies in the rule description for audit readiness.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
130
docs/notifications/templates.md
Normal file
130
docs/notifications/templates.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Notifications Templates
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
Templates shape the payload rendered for each channel when a rule action fires. They are deterministic, locale-aware artefacts stored alongside rules so Notify.Worker replicas can render identical messages regardless of environment.
|
||||
|
||||
---
|
||||
|
||||
## 1. Template lifecycle
|
||||
|
||||
1. **Authoring.** Operators create templates via the API (`POST /templates`) or UI. Each template binds to a channel type (`Slack`, `Teams`, `Email`, `Webhook`, `Custom`) and a locale.
|
||||
2. **Reference.** Rule actions opt in by referencing the template key (`actions[].template`). Channel defaults apply when no template is specified.
|
||||
3. **Rendering.** During delivery, the worker resolves the template (locale fallbacks included), executes it using the safe Handlebars-style engine, and passes the rendered payload plus metadata to the connector.
|
||||
4. **Audit.** Rendered payloads stored in the delivery ledger include the `templateId` so operators can trace which text was used.
|
||||
|
||||
---
|
||||
|
||||
## 2. Template schema reference
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `templateId` | string | Stable identifier (UUID/slug). |
|
||||
| `tenantId` | string | Must match the tenant header in API calls. |
|
||||
| `channelType` | [`NotifyChannelType`](../ARCHITECTURE_NOTIFY.md#5-channels--connectors-plug-ins) | Determines connector payload envelope. |
|
||||
| `key` | string | Human-readable key referenced by rules (`tmpl-critical`). |
|
||||
| `locale` | string | BCP-47 tag, stored lower-case (`en-us`, `bg-bg`). |
|
||||
| `body` | string | Template body; rendered strictly without executing arbitrary code. |
|
||||
| `renderMode` | enum | `Markdown`, `Html`, `AdaptiveCard`, `PlainText`, or `Json`. Guides connector sanitisation. |
|
||||
| `format` | enum | `Slack`, `Teams`, `Email`, `Webhook`, or `Json`. Signals delivery payload structure. |
|
||||
| `description` | string? | Optional operator note. |
|
||||
| `metadata` | map<string,string> | Sorted map for automation (layout hints, fallback text). |
|
||||
| `createdBy`/`createdAt` | string?, instant | Auto-populated. |
|
||||
| `updatedBy`/`updatedAt` | string?, instant | Auto-populated. |
|
||||
| `schemaVersion` | string | Auto-upgraded on persistence. |
|
||||
|
||||
Templates are normalised: string fields trimmed, locale lower-cased, metadata sorted to preserve determinism.
|
||||
|
||||
---
|
||||
|
||||
## 3. Variables, helpers, and context
|
||||
|
||||
Templates receive a structured context derived from the Notify event, rule match, and rendering metadata.
|
||||
|
||||
| Path | Description |
|
||||
|------|-------------|
|
||||
| `event.*` | Canonical event envelope (`kind`, `tenant`, `ts`, `actor`). |
|
||||
| `event.scope.*` | Namespace, repository, digest, image, component identifiers, labels, attributes. |
|
||||
| `payload.*` | Raw event payload (e.g., `payload.verdict`, `payload.delta.*`, `payload.links.*`). |
|
||||
| `rule.*` | Rule descriptor (`ruleId`, `name`, `labels`, `metadata`). |
|
||||
| `action.*` | Action descriptor (`actionId`, `channel`, `digest`, `throttle`, `metadata`). |
|
||||
| `policy.*` | Policy metadata when supplied (`revisionId`, `name`). |
|
||||
| `topFindings[]` | Top-N findings summarised for convenience (vulnerability ID, severity, reachability). |
|
||||
| `digest.*` | When rendering digest flushes: `window`, `openedAt`, `itemCount`. |
|
||||
|
||||
Built-in helpers mirror the architecture dossier:
|
||||
|
||||
| Helper | Usage |
|
||||
|--------|-------|
|
||||
| `severity_icon severity` | Returns emoji/text badge representing severity. |
|
||||
| `link text url` | Produces channel-safe hyperlink. |
|
||||
| `pluralize count "finding"` | Adds plural suffix when `count != 1`. |
|
||||
| `truncate text maxLength` | Cuts strings while preserving determinism. |
|
||||
| `code text` | Formats inline code (Markdown/HTML aware). |
|
||||
|
||||
Connectors may expose additional helpers via partials, but must remain deterministic and side-effect free.
|
||||
|
||||
---
|
||||
|
||||
## 4. Sample templates
|
||||
|
||||
### 4.1 Slack (Markdown + block kit)
|
||||
|
||||
```hbs
|
||||
{{#*inline "findingLine"}}
|
||||
- {{severity_icon severity}} {{vulnId}} ({{severity}}) in `{{component}}`
|
||||
{{/inline}}
|
||||
|
||||
*:rotating_light: {{payload.summary.total}} findings {{#if payload.delta.newCritical}}(new critical: {{payload.delta.newCritical}}){{/if}}*
|
||||
|
||||
{{#if topFindings.length}}
|
||||
Top findings:
|
||||
{{#each topFindings}}{{> findingLine}}{{/each}}
|
||||
{{/if}}
|
||||
|
||||
{{link "Open report in Console" payload.links.ui}}
|
||||
```
|
||||
|
||||
### 4.2 Email (HTML + text alternative)
|
||||
|
||||
```hbs
|
||||
<h2>{{payload.verdict}} for {{event.scope.repo}}</h2>
|
||||
<p>{{payload.summary.total}} findings ({{payload.summary.blocked}} blocked, {{payload.summary.warned}} warned)</p>
|
||||
<table>
|
||||
<thead><tr><th>Finding</th><th>Severity</th><th>Package</th></tr></thead>
|
||||
<tbody>
|
||||
{{#each topFindings}}
|
||||
<tr>
|
||||
<td>{{this.vulnId}}</td>
|
||||
<td>{{this.severity}}</td>
|
||||
<td>{{this.component}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>{{link "View full analysis" payload.links.ui}}</p>
|
||||
```
|
||||
|
||||
When delivering via email, connectors automatically attach a plain-text alternative derived from the rendered content to preserve accessibility.
|
||||
|
||||
---
|
||||
|
||||
## 5. Preview and validation
|
||||
|
||||
- `POST /channels/{id}/test` accepts an optional `templateId` and sample payload to produce a rendered preview without dispatching the event. Results include channel type, target, title/summary, locale, body hash, and connector metadata.
|
||||
- UI previews rely on the same API and highlight connector fallbacks (e.g., Teams adaptive card vs. text fallback).
|
||||
- Offline Kit scenarios can call `/internal/notify/templates/normalize` to ensure bundled templates match the canonical schema before packaging.
|
||||
|
||||
---
|
||||
|
||||
## 6. Best practices
|
||||
|
||||
- Keep channel-specific limits in mind (Slack block/character quotas, Teams adaptive card size, email line length). Lean on digests to summarise long lists.
|
||||
- Provide locale-specific versions for high-volume tenants; Notify selects the closest locale, falling back to `en-us`.
|
||||
- Store connector-specific hints (`metadata.layout`, `metadata.emoji`) in template metadata rather than rules when they affect rendering.
|
||||
- Version template bodies through metadata (e.g., `metadata.revision: "2025-10-28"`) so tenants can track changes over time.
|
||||
- Run test previews whenever introducing new helpers to confirm body hashes remain stable across environments.
|
||||
|
||||
---
|
||||
|
||||
> **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
134
docs/operations/cli-release-and-packaging.md
Normal file
134
docs/operations/cli-release-and-packaging.md
Normal file
@@ -0,0 +1,134 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# CLI Release & Packaging Runbook
|
||||
|
||||
This runbook describes how to build, sign, package, and distribute the StellaOps CLI with Task Pack support. It covers connected and air-gapped workflows, SBOM generation, parity gating, and distribution artifacts required by Sprint 43 (`DEVOPS-CLI-43-001`, `DEPLOY-PACKS-43-001`).
|
||||
|
||||
---
|
||||
|
||||
## 1 · Release Artifacts
|
||||
|
||||
| Artifact | Description | Notes |
|
||||
|----------|-------------|-------|
|
||||
| `stella-<version>-linux-x64.tar.gz` | Linux binary + completions | Includes man pages, localization files. |
|
||||
| `stella-<version>-macos-universal.tar.gz` | macOS universal binary | Signed/notarized where applicable. |
|
||||
| `stella-<version>-windows-x64.zip` | Windows binary + PowerShell modules | Code-signed. |
|
||||
| `stella-cli-container:<version>` | OCI image with CLI + pack runtime | Deterministic rootfs (scratch/distroless). |
|
||||
| SBOM (`.cdx.json`) | CycloneDX SBOM per artifact | Generated via `stella sbom generate` or `syft`. |
|
||||
| Checksums (`SHA256SUMS`) | Aggregated digest list | Signed with cosign. |
|
||||
| Provenance (`.intoto.jsonl`) | DSSE attestation (SLSA L2) | Contains build metadata. |
|
||||
| Release notes | Markdown summary | Links to task packs docs, parity matrix. |
|
||||
|
||||
---
|
||||
|
||||
## 2 · Build Pipeline
|
||||
|
||||
1. **Source checkout** – pinned commit, reproducible environment (Docker).
|
||||
2. **Dependency lock** – `dotnet restore`, `npm ci` (for CLI frontends), ensure deterministic build flags.
|
||||
3. **Build binaries** – cross-platform targets with reproducible timestamps.
|
||||
4. **Run tests** – unit + integration; include `stella pack` commands (plan/run/verify) in CI.
|
||||
5. **Generate SBOM** – `syft packages dist/stella-linux-x64 --output cyclonedx-json`.
|
||||
6. **Bundle** – compress artifacts, include completions (`bash`, `zsh`, `fish`, PowerShell).
|
||||
7. **Sign** – cosign signatures for binaries, checksums, container image.
|
||||
8. **Publish** – upload to `downloads.stella-ops.org`, container registry, Packs Registry (for CLI container).
|
||||
9. **Parity gating** – run CLI parity matrix tests vs Console features (automation in `DEVOPS-CLI-43-001`).
|
||||
|
||||
CI must run in isolated environment (no network beyond allowlist). Cache dependencies for offline bundling.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Versioning & Channels
|
||||
|
||||
- Semantic versioning (`YYYY.MM.patch`), e.g., `2025.10.0`.
|
||||
- Channels:
|
||||
- `edge` – nightly builds, limited support.
|
||||
- `beta` – pre-release candidates.
|
||||
- `stable` – production-ready, after parity gating.
|
||||
- Release promotions mirror Task Pack channels; update downloads manifest (`deploy/downloads/manifest.json`).
|
||||
|
||||
---
|
||||
|
||||
## 4 · Signing & Verification
|
||||
|
||||
- Binaries signed with cosign (`cosign sign-blob`).
|
||||
- Container image signed (`cosign sign stella-cli-container:<version>`).
|
||||
- DSSE provenance includes:
|
||||
- Build pipeline ID.
|
||||
- Source commit and repo.
|
||||
- Dependencies SBOM digest.
|
||||
- Test results summary.
|
||||
- Verification command for operators:
|
||||
|
||||
```bash
|
||||
cosign verify-blob \
|
||||
--certificate-identity https://ci.stella-ops.org \
|
||||
--certificate-oidc-issuer https://fulcio.sigstore.dev \
|
||||
--signature stella-2025.10.0-linux-x64.sig \
|
||||
stella-2025.10.0-linux-x64.tar.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5 · Distribution
|
||||
|
||||
### 5.1 Online
|
||||
|
||||
- Publish artifacts to Downloads service; update manifest with digests, SBOM URLs, attestations.
|
||||
- Update CLI parity docs (`docs/cli-vs-ui-parity.md`) and release notes.
|
||||
- Push container image to registry with SBOM + attestations referenced as OCI referrers.
|
||||
- Notify stakeholders via `#release-cli` channel and release mailing list.
|
||||
|
||||
### 5.2 Offline / Air-Gap
|
||||
|
||||
- Bundle CLI artifacts, Task Pack samples, and registry mirror:
|
||||
|
||||
```bash
|
||||
stella pack bundle export \
|
||||
--packs "sbom-remediation:1.3.0" \
|
||||
--output offline/packs-bundle-2025.10.0.tgz
|
||||
|
||||
stella cli bundle export \
|
||||
--output offline/cli-2025.10.0.tgz \
|
||||
--include-container \
|
||||
--include-sbom
|
||||
```
|
||||
|
||||
- Update Offline Kit manifest with new CLI version and pack bundle entries.
|
||||
- Provide import scripts (`ouk import`) for sealed sites.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Parity Gating
|
||||
|
||||
- `stella cli parity check` compares CLI commands vs parity matrix.
|
||||
- CI fails release if any required command flagged `🟥` or `🟡` with severity > threshold.
|
||||
- Parity report uploaded to Downloads workspace and linked in docs.
|
||||
- Manual review required for new commands (ensure `man` pages and help text localized).
|
||||
|
||||
---
|
||||
|
||||
## 7 · Localization & Documentation
|
||||
|
||||
- CLI includes localization bundles; ensure `i18n.txz` packaged.
|
||||
- Update man pages (`man/stella-pack.1`) and HTML docs.
|
||||
- Sync docs: `docs/cli/overview.md`, pack authoring guide, release notes.
|
||||
- Document new flags/commands in `docs/cli/commands/pack.md` (tracked in Sprint 42 tasks).
|
||||
|
||||
---
|
||||
|
||||
## 8 · Release Checklist
|
||||
|
||||
- [ ] All binaries built reproducibly (CI logs archived).
|
||||
- [ ] Tests + parity matrix passing.
|
||||
- [ ] SBOM + provenance generated and published.
|
||||
- [ ] Cosign signatures created and verified.
|
||||
- [ ] Downloads manifest updated (edge/beta/stable).
|
||||
- [ ] Offline bundle exported and validated.
|
||||
- [ ] Release notes + documentation updates merged.
|
||||
- [ ] Notifications sent (chat/email).
|
||||
- [ ] Imposed rule reminder present at top of document.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
203
docs/operations/export-runbook.md
Normal file
203
docs/operations/export-runbook.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Export Center Operations Runbook
|
||||
|
||||
> Export Center workers and API are landing across Sprints 35-37. This runbook captures the target operational procedures so DevOps can validate them as each milestone goes live. Update specific commands once `EXPORT-SVC-35-006`, `EXPORT-SVC-36-001..004`, and related CLI tasks ship.
|
||||
|
||||
## 1. Service scope
|
||||
|
||||
The Export Center packages StellaOps evidence and policy overlays into reproducible bundles (JSON, Trivy DB, mirror). Operations owns:
|
||||
|
||||
- Worker scaling, queue management, and distribution storage.
|
||||
- Monitoring and alerts for run throughput, failures, and verification issues.
|
||||
- Runbook execution for recovery, retention, and compliance.
|
||||
- Coordination with DevOps validation (cosign + `trivy module db import` smoke tests).
|
||||
|
||||
Related documentation:
|
||||
|
||||
- `docs/export-center/overview.md`
|
||||
- `docs/export-center/architecture.md`
|
||||
- `docs/export-center/profiles.md`
|
||||
- `docs/export-center/trivy-adapter.md`
|
||||
- `docs/export-center/mirror-bundles.md`
|
||||
- `docs/export-center/api.md`
|
||||
- `docs/export-center/cli.md`
|
||||
|
||||
## 2. Contacts & tooling
|
||||
|
||||
| Area | Owner(s) | Escalation |
|
||||
|------|----------|------------|
|
||||
| Export Center service | Exporter Service Guild | `#export-center-ops`, on-call rotation |
|
||||
| Distribution & CI smoke | DevOps Guild | CI channel, PagerDuty `devops-export` |
|
||||
| KMS / encryption | Authority Core | `#authority-core` |
|
||||
| Offline Kit dissemination | Offline Kit Guild | `#offline-kit` |
|
||||
|
||||
Primary tooling:
|
||||
|
||||
- `stella export` CLI (submit, watch, download, verify).
|
||||
- Export Center API (`/api/export/*`) for automation.
|
||||
- Grafana dashboards (`Export Center / Run Health`, `Export Center / Distribution`).
|
||||
- Alertmanager routes (`Export.Center.Failures`, `Export.Center.Verify`).
|
||||
|
||||
## 3. Monitoring & SLOs
|
||||
|
||||
Key metrics (exposed by workers and API):
|
||||
|
||||
| Metric | SLO / Alert | Notes |
|
||||
|--------|-------------|-------|
|
||||
| `exporter_run_duration_seconds` | p95 < 300 s (full), < 120 s (delta) | Break down by profile (`profile_kind`). |
|
||||
| `exporter_run_failures_total` | Alert when > 3 failures/15 min per profile | Include `error_code` label. |
|
||||
| `exporter_run_bytes_total` | Track growth trends | Helps with storage planning. |
|
||||
| `exporter_distribution_push_seconds` | p95 < 60 s | Covers OCI/object storage. |
|
||||
| `exporter_verify_failures_total` | Alert on any non-zero | Raised when cosign/Trivy smoke tests fail. |
|
||||
| `exporter_retention_pruned_total` | Should increase nightly | Confirms retention job success. |
|
||||
|
||||
Dashboards must include:
|
||||
|
||||
- Run throughput by profile.
|
||||
- Failure breakdown (adapter, signing, distribution).
|
||||
- Queue depth and worker concurrency (via Orchestrator metrics).
|
||||
- Storage consumption (object storage buckets, local staging).
|
||||
|
||||
Alerts (Alertmanager):
|
||||
|
||||
- `ExportCenterRunFailureSpike` - `exporter_run_failures_total` increase rate > 3/15 min.
|
||||
- `ExportCenterVerifyFailure` - any entry in `exporter_verify_failures_total` > 0.
|
||||
- `ExportCenterWorkerLag` - queue backlog > threshold for 10 minutes.
|
||||
- `ExportCenterRetentionStale` - no pruning events in 24 hours.
|
||||
|
||||
## 4. Routine operations
|
||||
|
||||
### 4.1 Daily checklist
|
||||
|
||||
- Review dashboard for run throughput and error classes.
|
||||
- Confirm CI smoke job (cosign + `trivy module db import`) passed.
|
||||
- Check storage usage against capacity thresholds.
|
||||
- Verify retention job executed (look for `exporter_retention_pruned_total` increment).
|
||||
- Scan logs for `adapter.trivy.unsupported_schema_version` or `mirror.delta.apply_failed`.
|
||||
|
||||
### 4.2 Weekly tasks
|
||||
|
||||
- Rotate Download/OCI API tokens if configured with short-lived credentials.
|
||||
- Review upcoming profile changes (new tenants, profile updates).
|
||||
- Test `stella export verify` against a recent run for each profile.
|
||||
- Exercise failover of workers (scale to zero one replica, ensure others pick up).
|
||||
|
||||
### 4.3 Pre-release
|
||||
|
||||
- Ensure bundles generated for release candidates pass cosign verification.
|
||||
- Capture sample manifests (`export.json`, `manifest.yaml`) for documentation archives.
|
||||
- Validate Offline Kit packaging includes latest full + delta mirror bundles.
|
||||
|
||||
## 5. Capacity & scaling
|
||||
|
||||
### 5.1 Worker sizing
|
||||
|
||||
- Default workers handle ~2 full runs or 6 delta runs concurrently per 4 vCPU.
|
||||
- Scale out when:
|
||||
- Queue depth (`exporter_jobs_ready`) > 10 for 10 minutes.
|
||||
- p95 durations exceed SLO for multiple runs without failures.
|
||||
- Use Orchestrator quotas: ensure per-tenant concurrency (`max_active_runs`) is tuned.
|
||||
|
||||
### 5.2 Storage planning
|
||||
|
||||
- Staging storage (object store or filesystem) must hold at least:
|
||||
- Latest full bundle per tenant per profile.
|
||||
- Last `N` deltas (default N=5).
|
||||
- Set retention policy via configuration:
|
||||
|
||||
```yaml
|
||||
ExportCenter:
|
||||
Retention:
|
||||
Mirror:
|
||||
Mode: days
|
||||
Value: 30
|
||||
Trivy:
|
||||
Mode: count
|
||||
Value: 10
|
||||
```
|
||||
|
||||
- Monitor `exporter_storage_bytes_total` (if available) or use bucket metrics from storage provider.
|
||||
|
||||
## 6. Failure response
|
||||
|
||||
| Symptom | Likely cause | Immediate action | Follow-up |
|
||||
|---------|--------------|------------------|-----------|
|
||||
| `ERR_EXPORT_UNSUPPORTED_SCHEMA` | Trivy schema mismatch | Pin `SchemaVersion` to previous value; rerun export | Coordinate with Exporter Guild to add new mapping |
|
||||
| `ERR_EXPORT_BASE_MISSING` | Base manifest unavailable | Trigger full export (`mirror:full`), notify tenant | Investigate storage retention settings |
|
||||
| Run stuck in `pending` | Worker unavailable / queue paused | Check worker pods / Orchestrator status | Scale workers or fix queue |
|
||||
| Signing failure (`errorCode=signing`) | KMS outage or permission change | Verify KMS health; retry run; escalate to Authority | Document incident, review key rotation schedule |
|
||||
| Distribution failure (`errorCode=distribution`) | OCI/object store outage | Switch profile distribution to download-only (`distribution: ["http"]`) | Restore distribution backend, resume normal config |
|
||||
| CLI verification failure in CI | New bundle did not pass cosign or Trivy import | Inspect pipeline logs; download bundle; rerun verification manually | Engage Exporter Guild if data quality issue |
|
||||
| Retention job skipped | Scheduler failure or misconfiguration | Run retention job manually (`stella export retention run`) | Audit scheduler configuration |
|
||||
|
||||
Log locations: `exporter` service emits structured logs with `runId`, `profile`, `errorCode`. For Kubernetes deployments, check `kubectl logs deployment/export-center-worker`.
|
||||
|
||||
## 7. Recovery playbooks
|
||||
|
||||
### 7.1 Replaying a failed run
|
||||
|
||||
1. Identify run (`runId`) and root cause via `GET /api/export/runs/{id}`.
|
||||
2. If configuration changed, clone profile and adjust settings.
|
||||
3. Resubmit run (`stella export run submit` or API) with `--allow-empty` if intentionally empty.
|
||||
4. Monitor SSE stream or `stella export run watch`.
|
||||
5. After success, prune failed run data if necessary.
|
||||
|
||||
### 7.2 Restoring from previous full bundle
|
||||
|
||||
1. Locate last successful full bundle (`mirror:full`) and associated manifest.
|
||||
2. Download and verify signatures.
|
||||
3. Extract into mirror staging area.
|
||||
4. Apply subsequent delta bundles in order.
|
||||
5. Trigger mirror verification script (`mirror verify <path>`).
|
||||
|
||||
### 7.3 KMS outage response
|
||||
|
||||
1. Disable new export submissions temporarily (set per-tenant quota to 0).
|
||||
2. Coordinate with Authority Core to restore KMS.
|
||||
3. Once KMS back, run `stella export run submit --profile <id> --selectors ... --priority catch-up` for affected tenants.
|
||||
|
||||
## 8. Verification workflow
|
||||
|
||||
All bundles must pass both signature and content verification.
|
||||
|
||||
### 8.1 Trivy bundle validation (CI job)
|
||||
|
||||
```bash
|
||||
cosign verify-blob \
|
||||
--key tenants/acme/export-center.pub \
|
||||
--signature signatures/trivy-db.sig \
|
||||
trivy/db.bundle
|
||||
|
||||
trivy module db import trivy/db.bundle --cache-dir /tmp/trivy-cache
|
||||
```
|
||||
|
||||
Automation: `DEVOPS-EXPORT-36-001` ensures this runs on every pipeline.
|
||||
|
||||
### 8.2 Mirror bundle validation
|
||||
|
||||
```bash
|
||||
cosign verify-blob \
|
||||
--key tenants/acme/export-center.pub \
|
||||
--signature signatures/export.sig \
|
||||
mirror/export.json
|
||||
|
||||
./offline-kit/bin/mirror verify mirror-20251029-full.tar.zst
|
||||
```
|
||||
|
||||
If encryption enabled, decrypt using age or AES key before verification.
|
||||
|
||||
## 9. Change management
|
||||
|
||||
- Profile changes require change record referencing tenant impact and expected bundle size.
|
||||
- Distribution configuration updates (`OCI` vs `HTTP`) must be tested in staging.
|
||||
- Schema upgrades (e.g., Trivy schema v3) need coordination with DevOps, Exporter, and Docs.
|
||||
- Update runbook and related docs when processes change (tie updates to `DOCS-EXPORT-37-005`).
|
||||
|
||||
## 10. References
|
||||
|
||||
- `docs/export-center/trivy-adapter.md`
|
||||
- `docs/export-center/mirror-bundles.md`
|
||||
- `ops/devops/TASKS.md` (`DEVOPS-EXPORT-36-001`, `DEVOPS-EXPORT-37-001`)
|
||||
- `docs/ingestion/aggregation-only-contract.md`
|
||||
- `docs/24_OFFLINE_KIT.md`
|
||||
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
@@ -81,3 +81,14 @@ Treat these as examples; real environments must maintain their own PEM material.
|
||||
- `docs/11_AUTHORITY.md` – Architecture and rotation SOP (Section 5).
|
||||
- `docs/ops/authority-backup-restore.md` – Recovery flow referencing this playbook.
|
||||
- `ops/authority/README.md` – CLI usage and examples.
|
||||
- `scripts/rotate-policy-cli-secret.sh` – Helper to mint new `policy-cli` shared secrets when policy scope bundles change.
|
||||
|
||||
## 7. Appendix — Policy CLI secret rotation
|
||||
|
||||
Scope migrations such as AUTH-POLICY-23-004 require issuing fresh credentials for the `policy-cli` client. Use the helper script committed with the repo to keep secrets deterministic across environments.
|
||||
|
||||
```bash
|
||||
./scripts/rotate-policy-cli-secret.sh --output etc/secrets/policy-cli.secret
|
||||
```
|
||||
|
||||
The script writes a timestamped header and a random secret into the target file. Use `--dry-run` when generating material for external secret stores. After updating secrets in staging/production, recycle the Authority pods and confirm the new client credentials work before the next release freeze.
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
- `process.runtime.gc.*`, `process.runtime.dotnet.*` (from `AddRuntimeInstrumentation`).
|
||||
- **Logs:** Serilog writes structured events to stdout. Notable templates:
|
||||
- `"Password grant verification failed ..."` and `"Plugin {PluginName} denied access ... due to lockout"` (lockout spike detector).
|
||||
- `"Password grant validation failed for {Username}: provider '{Provider}' does not support MFA required for exception approvals."` (identifies users attempting `exceptions:approve` without MFA support; tie to fresh-auth errors).
|
||||
- `"Client credentials validation failed for {ClientId}: exception scopes require tenant assignment."` (signals misconfigured exception service identities).
|
||||
- `"Granting StellaOps bypass for remote {RemoteIp}"` (bypass usage).
|
||||
- `"Rate limit exceeded for path {Path} from {RemoteIp}"` (limiter alerts).
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ Example verdict excerpt (JSON):
|
||||
## 7 · Operational Notes
|
||||
|
||||
- **Authoring** – Policy packs must ship effect definitions before Authority can issue instances. CLI validation (`stella policy lint`) fails if required fields are missing.
|
||||
- **Approvals & MFA** – Effects referencing routing templates inherit `requireMfa` rules from `exceptions.routingTemplates`. Governance guidance in `/docs/11_GOVERNANCE.md` captures Authority approval flows and audit expectations.
|
||||
- **Approvals & MFA** – Effects referencing routing templates inherit `requireMfa` rules from `exceptions.routingTemplates`. When a template requires MFA, Authority will refuse to mint tokens containing `exceptions:approve` unless the authenticating identity provider exposes MFA capability; the failure is logged as `authority.password.grant` with `reason="Exception approval scope requires an MFA-capable identity provider."` Review `/docs/security/authority-scopes.md` for scope/role assignments and `/docs/11_AUTHORITY.md` for configuration samples.
|
||||
- **Presence in exports** – Even when an exception suppresses a finding, explain traces and effective findings retain the applied exception metadata for audit parity.
|
||||
- **Determinism** – Specificity scoring plus tie-breakers ensure repeatable outcomes across runs, supporting sealed/offline replay.
|
||||
|
||||
|
||||
124
docs/policy/gateway.md
Normal file
124
docs/policy/gateway.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Policy Gateway
|
||||
|
||||
> **Delivery scope:** `StellaOps.Policy.Gateway` minimal API service fronting Policy Engine pack CRUD + activation endpoints for UI/CLI clients. Sender-constrained with DPoP and tenant headers, suitable for online and Offline Kit deployments.
|
||||
|
||||
## 1 · Responsibilities
|
||||
|
||||
- Proxy policy pack CRUD and activation requests to Policy Engine while enforcing scope policies (`policy:read`, `policy:author`, `policy:review`, `policy:operate`, `policy:activate`).
|
||||
- Normalise responses (DTO + `ProblemDetails`) so Console, CLI, and automation receive consistent payloads.
|
||||
- Guard activation actions with structured logging and metrics so approvals are auditable.
|
||||
- Support dual auth modes:
|
||||
- Forwarded caller tokens (Console/CLI) with DPoP proofs + `X-Stella-Tenant` header.
|
||||
- Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present.
|
||||
|
||||
## 2 · Endpoints
|
||||
|
||||
| Route | Method | Description | Required scope(s) |
|
||||
|-------|--------|-------------|-------------------|
|
||||
| `/api/policy/packs` | `GET` | List policy packs and revisions for the active tenant. | `policy:read` |
|
||||
| `/api/policy/packs` | `POST` | Create a policy pack shell or upsert display metadata. | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions` | `POST` | Create or update a policy revision (draft/approved). | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions/{version}:activate` | `POST` | Activate a revision, enforcing single/two-person approvals. | `policy:operate`, `policy:activate` |
|
||||
|
||||
### Response shapes
|
||||
|
||||
- Successful responses return camel-case DTOs matching `PolicyPackDto`, `PolicyRevisionDto`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`).
|
||||
- Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"Upstream authorization missing"` detail.
|
||||
|
||||
## 3 · Authentication & headers
|
||||
|
||||
| Header | Source | Notes |
|
||||
|--------|--------|-------|
|
||||
| `Authorization` | Forwarded caller token *or* gateway client credentials. | Caller tokens must include tenant scope; gateway tokens default to `DPoP` scheme. |
|
||||
| `DPoP` | Caller or gateway. | Required when Authority mandates proof-of-possession (default). Generated per request; gateway keeps ES256/ES384 key material under `etc/policy-gateway-dpop.pem`. |
|
||||
| `X-Stella-Tenant` | Caller | Tenant isolation header. Forwarded unchanged; gateway automation omits it. |
|
||||
|
||||
Gateway client credentials are configured in `policy-gateway.yaml`:
|
||||
|
||||
```yaml
|
||||
policyEngine:
|
||||
baseAddress: "https://policy-engine.internal"
|
||||
audience: "api://policy-engine"
|
||||
clientCredentials:
|
||||
enabled: true
|
||||
clientId: "policy-gateway"
|
||||
clientSecret: "<secret>"
|
||||
scopes:
|
||||
- policy:read
|
||||
- policy:author
|
||||
- policy:review
|
||||
- policy:operate
|
||||
- policy:activate
|
||||
dpop:
|
||||
enabled: true
|
||||
keyPath: "../etc/policy-gateway-dpop.pem"
|
||||
algorithm: "ES256"
|
||||
```
|
||||
|
||||
> 🔐 **DPoP key** – store the private key alongside Offline Kit secrets; rotate it whenever the gateway identity or Authority configuration changes.
|
||||
|
||||
## 4 · Metrics & logging
|
||||
|
||||
All activation calls emit:
|
||||
|
||||
- `policy_gateway_activation_requests_total{outcome,source}` – counter labelled with `outcome` (`activated`, `pending_second_approval`, `already_active`, `bad_request`, `not_found`, `unauthorized`, `forbidden`, `error`) and `source` (`caller`, `service`).
|
||||
- `policy_gateway_activation_latency_ms{outcome,source}` – histogram measuring proxy latency.
|
||||
|
||||
Structured logs (category `StellaOps.Policy.Gateway.Activation`) include `PackId`, `Version`, `Outcome`, `Source`, and upstream status code for audit trails.
|
||||
|
||||
## 5 · Sample `curl` workflows
|
||||
|
||||
Assuming you already obtained a DPoP-bound access token (`$TOKEN`) for tenant `acme`:
|
||||
|
||||
```bash
|
||||
# Generate a DPoP proof for GET via the CLI helper
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs \
|
||||
--htm GET \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme"
|
||||
|
||||
# Draft a new revision
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}'
|
||||
|
||||
# Activate revision 5 (returns 202 when awaiting the second approver)
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"comment":"Rollout baseline"}'
|
||||
```
|
||||
|
||||
For air-gapped environments, bundle `policy-gateway.yaml` and the DPoP key in the Offline Kit (see `/docs/24_OFFLINE_KIT.md` §5.7).
|
||||
|
||||
> **DPoP proof helper:** Use `stella auth dpop proof` to mint sender-constrained proofs locally. The command accepts `--htu`, `--htm`, and `--token` arguments and emits a ready-to-use header value. Teams maintaining alternate tooling (for example, `scripts/make-dpop.sh`) can substitute it as long as the inputs and output match the CLI behaviour.
|
||||
|
||||
## 6 · Offline Kit guidance
|
||||
|
||||
- Include `policy-gateway.yaml.sample` and the resolved runtime config in the Offline Kit’s `config/` tree.
|
||||
- Place the DPoP private key under `secrets/policy-gateway-dpop.pem` with restricted permissions; document rotation steps in the manifest.
|
||||
- When building verification scripts, use the gateway endpoints above instead of hitting Policy Engine directly. The Offline Kit validator now expects `policy_gateway_activation_requests_total` metrics in the Prometheus snapshot.
|
||||
|
||||
## 7 · Change log
|
||||
|
||||
- **2025-10-27 – Sprint 18.5**: Initial gateway bootstrap + activation metrics + DPoP client credentials.
|
||||
@@ -18,12 +18,12 @@ This guide explains how a policy progresses through Stella Ops, which roles ar
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Draft
|
||||
Draft --> Draft: edit/save (policy:write)
|
||||
Draft --> Submitted: submit(reviewers) (policy:submit)
|
||||
Submitted --> Draft: requestChanges (policy:write)
|
||||
Draft --> Draft: edit/save (policy:author)
|
||||
Draft --> Submitted: submit(reviewers) (policy:author)
|
||||
Submitted --> Draft: requestChanges (policy:review)
|
||||
Submitted --> Approved: approve (policy:approve)
|
||||
Approved --> Active: activate/run (policy:run)
|
||||
Active --> Archived: archive (policy:archive)
|
||||
Approved --> Active: activate/run (policy:operate)
|
||||
Active --> Archived: archive (policy:operate)
|
||||
Approved --> Archived: superseded/explicit archive
|
||||
Archived --> [*]
|
||||
```
|
||||
@@ -34,11 +34,11 @@ stateDiagram-v2
|
||||
|
||||
| Role (suggested) | Required scopes | Responsibilities |
|
||||
|------------------|-----------------|------------------|
|
||||
| **Policy Author** | `policy:write`, `policy:submit`, `policy:simulate` | Draft DSL, run local/CI simulations, submit for review. |
|
||||
| **Policy Reviewer** | `policy:review`, `policy:simulate`, `policy:runs` | Comment on submissions, demand additional simulations, request changes. |
|
||||
| **Policy Approver** | `policy:approve`, `policy:runs`, `policy:audit` | Grant final approval, ensure sign-off evidence captured. |
|
||||
| **Policy Operator** | `policy:run`, `policy:activate`, `findings:read` | Trigger full/incremental runs, monitor results, roll back to previous version. |
|
||||
| **Policy Auditor** | `policy:audit`, `findings:read`, `policy:history` | Review past versions, verify attestations, respond to compliance requests. |
|
||||
| **Policy Author** | `policy:author`, `policy:simulate`, `findings:read` | Draft DSL, run local/CI simulations, submit for review. |
|
||||
| **Policy Reviewer** | `policy:review`, `policy:simulate`, `findings:read` | Comment on submissions, demand additional simulations, request changes. |
|
||||
| **Policy Approver** | `policy:approve`, `policy:audit`, `findings:read` | Grant final approval, ensure sign-off evidence captured. |
|
||||
| **Policy Operator** | `policy:operate`, `policy:run`, `policy:activate`, `findings:read` | Trigger full/incremental runs, monitor results, roll back to previous version. |
|
||||
| **Policy Auditor** | `policy:audit`, `findings:read` | Review past versions, verify attestations, respond to compliance requests. |
|
||||
| **Policy Engine Service** | `effective:write`, `findings:read` | Materialise effective findings during runs; no approval capabilities. |
|
||||
|
||||
> Scopes are issued by Authority (`AUTH-POLICY-20-001`). Tenants may map organisational roles (e.g., `secops.approver`) to these scopes via issuer policy.
|
||||
@@ -49,7 +49,7 @@ stateDiagram-v2
|
||||
|
||||
### 3.1 Draft
|
||||
|
||||
- **Who:** Authors (policy:write).
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console editor, `stella policy edit`, policy DSL files.
|
||||
- **Actions:**
|
||||
- Author DSL leveraging [stella-dsl@1](dsl.md).
|
||||
@@ -64,7 +64,7 @@ stateDiagram-v2
|
||||
|
||||
### 3.2 Submission
|
||||
|
||||
- **Who:** Authors with `policy:submit`.
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console “Submit for review” button, `stella policy submit <policyId> --reviewers ...`.
|
||||
- **Actions:**
|
||||
- Provide review notes and required simulations (CLI uploads attachments).
|
||||
@@ -108,7 +108,7 @@ stateDiagram-v2
|
||||
|
||||
### 3.5 Activation & Runs
|
||||
|
||||
- **Who:** Operators (`policy:run`, `policy:activate`).
|
||||
- **Who:** Operators (`policy:operate`, `policy:run`, `policy:activate`).
|
||||
- **Tools:** Console “Promote to active”, CLI `stella policy activate <id> --version n`, `stella policy run`.
|
||||
- **Actions:**
|
||||
- Mark approved version as tenant’s active policy.
|
||||
@@ -236,4 +236,3 @@ Failure of any gate emits a `policy.lifecycle.violation` event and blocks transi
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
|
||||
@@ -12,28 +12,61 @@ Authority issues short-lived tokens bound to tenants and scopes. Sprint 19 int
|
||||
| Scope | Surface | Purpose | Notes |
|
||||
|-------|---------|---------|-------|
|
||||
| `advisory:ingest` | Concelier ingestion APIs | Append-only writes to `advisory_raw` collections. | Requires tenant claim; blocked for global clients. |
|
||||
| `advisory:read` | `/aoc/verify`, Concelier dashboards, CLI | Read-only access to stored advisories and guard results. | Needed alongside `aoc:verify` for CLI/console verification. |
|
||||
| `advisory:read` | `/aoc/verify`, Concelier dashboards, CLI | Read-only access to stored advisories and guard results. | Must be requested with `aoc:verify`; Authority rejects tokens missing the pairing. |
|
||||
| `vex:ingest` | Excititor ingestion APIs | Append-only writes to `vex_raw`. | Mirrors `advisory:ingest`; tenant required. |
|
||||
| `vex:read` | `/aoc/verify`, Excititor dashboards, CLI | Read-only access to stored VEX material. | Pair with `aoc:verify` for guard checks. |
|
||||
| `aoc:verify` | CLI/CI pipelines, Console verification jobs | Execute Aggregation-Only Contract guard runs. | Always issued with tenant; read-only combined with `advisory:read`/`vex:read`. |
|
||||
| `vex:read` | `/aoc/verify`, Excititor dashboards, CLI | Read-only access to stored VEX material. | Must be requested with `aoc:verify`; Authority rejects tokens missing the pairing. |
|
||||
| `aoc:verify` | CLI/CI pipelines, Console verification jobs | Execute Aggregation-Only Contract guard runs. | Always issued with tenant; required whenever requesting `advisory:read`, `vex:read`, or any `signals:*` scope. |
|
||||
| `signals:read` | Signals API, reachability dashboards | Read-only access to stored reachability signals. | Tenant and `aoc:verify` required; missing pairing returns `invalid_scope`. |
|
||||
| `signals:write` | Signals ingestion APIs | Append-only writes for reachability signals. | Requires tenant and `aoc:verify`; Authority logs `authority.aoc_scope_violation` on mismatch. |
|
||||
| `signals:admin` | Signals administration tooling | Rotate credentials, manage reachability sensors, purge stale data. | Reserved for automation; `aoc:verify` + tenant mandatory; violations are audited. |
|
||||
| `graph:write` | Cartographer pipeline | Enqueue graph build/overlay jobs. | Reserved for Cartographer service identity; tenant required. |
|
||||
| `graph:read` | Graph API, Scheduler overlays, UI | Read graph projections/overlays. | Tenant required; granted to Cartographer, Graph API, Scheduler. |
|
||||
| `graph:export` | Graph export endpoints | Stream GraphML/JSONL artefacts. | UI/gateway automation only; tenant required. |
|
||||
| `graph:simulate` | Policy simulation overlays | Trigger what-if overlays on graphs. | Restricted to automation; tenant required. |
|
||||
| `effective:write` | Policy Engine | Create/update `effective_finding_*` collections. | **Only** the Policy Engine service client may hold this scope; tenant required. |
|
||||
| `findings:read` | Console, CLI, exports | Read derived findings materialised by Policy Engine. | Shared across tenants with RBAC; tenant claim still enforced. |
|
||||
| `policy:author` | Policy Studio (Console, CLI) | Author drafts, run lint, execute quick simulations. | Tenant required; typically granted via `role/policy-author`. |
|
||||
| `policy:review` | Policy Studio review panes | Review drafts, leave comments, request changes. | Tenant required; pair with `policy:simulate` for diff previews. |
|
||||
| `policy:approve` | Policy Studio approvals | Approve or reject policy drafts. | Tenant required; fresh-auth enforced by Console UI. |
|
||||
| `policy:operate` | Policy Studio promotion controls | Trigger batch simulations, promotions, and canary runs. | Tenant required; combine with `policy:run`/`policy:activate`. |
|
||||
| `policy:audit` | Policy audit exports | Access immutable policy history, comments, and signatures. | Tenant required; read-only access. |
|
||||
| `policy:simulate` | Policy Studio / CLI simulations | Run simulations against tenant inventories. | Tenant required; available to authors, reviewers, operators. |
|
||||
| `vuln:read` | Vuln Explorer API/UI | Read normalized vulnerability data. | Tenant required. |
|
||||
| Existing scopes | (e.g., `policy:*`, `concelier.jobs.trigger`) | Unchanged. | Review `/docs/security/policy-governance.md` for policy-specific scopes. |
|
||||
| `export.viewer` | Export Center APIs | List export profiles/runs, fetch manifests and bundles. | Tenant required; read-only access. |
|
||||
| `export.operator` | Export Center APIs | Trigger export runs, manage schedules, request verifications. | Tenant required; pair with `export.admin` for retention/encryption changes. |
|
||||
| `export.admin` | Export Center administrative APIs | Configure retention policies, encryption keys, and scheduling defaults. | Tenant required; token requests must include `export_reason` + `export_ticket`; Authority audits denials. |
|
||||
| `orch:read` | Orchestrator dashboards/API | Read queued jobs, worker state, and rate-limit telemetry. | Tenant required; never grants mutation rights. |
|
||||
| `orch:operate` | Orchestrator control actions | Execute pause/resume, retry, sync-now, and backfill operations. Requires tenant assignment **and** `operator_reason`/`operator_ticket` parameters when requesting tokens. |
|
||||
| `exceptions:read` | Exception service APIs, Console | Enumerate exception definitions, routing templates, and approval state. | Tenant and approval routing metadata required for audit replay. |
|
||||
| `exceptions:write` | Policy Engine → Authority bridge | Persist exception evaluations, lifecycle events, and status changes. | Tenant required; only service principals should hold this scope. |
|
||||
| `exceptions:approve` | Console fresh-auth flows, delegated admins | Approve or reject exception requests routed through Authority. | Tenant required; Authority enforces MFA when any bound routing template has `requireMfa=true`. |
|
||||
| `ui.read` | Console base APIs | Retrieve tenant catalog, profile metadata, and token introspection results. | Tenant header required; responses are DPoP-bound and audit logged. |
|
||||
| `authority:tenants.read` | Console admin workspace | Enumerate configured tenants, default roles, and isolation metadata. | Tenant claim must match header; access audited via `authority.console.tenants.read`. |
|
||||
| Existing scopes | (e.g., `policy:*`, `concelier.jobs.trigger`) | Unchanged. | `concelier.merge` is retired — clients must request `advisory:ingest`/`advisory:read`; requests continue to fail with `invalid_client`. Review `/docs/security/policy-governance.md` for policy-specific scopes. |
|
||||
|
||||
### 1.1 Scope bundles (roles)
|
||||
|
||||
- **`role/concelier-ingest`** → `advisory:ingest`, `advisory:read`.
|
||||
- **`role/excititor-ingest`** → `vex:ingest`, `vex:read`.
|
||||
- **`role/signals-uploader`** → `signals:write`, `signals:read`, `aoc:verify`.
|
||||
- **`role/aoc-operator`** → `aoc:verify`, `advisory:read`, `vex:read`.
|
||||
- **`role/policy-engine`** → `effective:write`, `findings:read`.
|
||||
- **`role/cartographer-service`** → `graph:write`, `graph:read`.
|
||||
- **`role/graph-gateway`** → `graph:read`, `graph:export`, `graph:simulate`.
|
||||
- **`role/console`** → `advisory:read`, `vex:read`, `aoc:verify`, `findings:read`, `vuln:read`.
|
||||
- **`role/console`** → `ui.read`, `advisory:read`, `vex:read`, `exceptions:read`, `aoc:verify`, `findings:read`, `orch:read`, `vuln:read`.
|
||||
- **`role/ui-console-admin`** → `ui.read`, `authority:tenants.read`, `authority:roles.read`, `authority:tokens.read`, `authority:clients.read` (paired with write scopes where required).
|
||||
- **`role/orch-viewer`** *(Authority role: `Orch.Viewer`)* → `orch:read`.
|
||||
- **`role/orch-operator`** *(Authority role: `Orch.Operator`)* → `orch:read`, `orch:operate`.
|
||||
- **`role/policy-author`** → `policy:author`, `policy:read`, `policy:simulate`, `findings:read`.
|
||||
- **`role/policy-reviewer`** → `policy:review`, `policy:read`, `policy:simulate`, `findings:read`.
|
||||
- **`role/policy-approver`** → `policy:approve`, `policy:review`, `policy:read`, `policy:simulate`, `findings:read`.
|
||||
- **`role/policy-operator`** → `policy:operate`, `policy:run`, `policy:activate`, `policy:read`, `policy:simulate`, `findings:read`.
|
||||
- **`role/policy-auditor`** → `policy:audit`, `policy:read`, `policy:simulate`, `findings:read`.
|
||||
- **`role/export-viewer`** *(Authority role: `Export.Viewer`)* → `export.viewer`.
|
||||
- **`role/export-operator`** *(Authority role: `Export.Operator`)* → `export.viewer`, `export.operator`.
|
||||
- **`role/export-admin`** *(Authority role: `Export.Admin`)* → `export.viewer`, `export.operator`, `export.admin`.
|
||||
- **`role/exceptions-service`** → `exceptions:read`, `exceptions:write`.
|
||||
- **`role/exceptions-approver`** → `exceptions:read`, `exceptions:approve`.
|
||||
|
||||
Roles are declared per tenant in `authority.yaml`:
|
||||
|
||||
@@ -43,12 +76,34 @@ tenants:
|
||||
roles:
|
||||
concelier-ingest:
|
||||
scopes: [advisory:ingest, advisory:read]
|
||||
signals-uploader:
|
||||
scopes: [signals:write, signals:read, aoc:verify]
|
||||
aoc-operator:
|
||||
scopes: [aoc:verify, advisory:read, vex:read]
|
||||
orch-viewer:
|
||||
scopes: [orch:read]
|
||||
orch-operator:
|
||||
scopes: [orch:read, orch:operate]
|
||||
policy-author:
|
||||
scopes: [policy:author, policy:read, policy:simulate, findings:read]
|
||||
policy-reviewer:
|
||||
scopes: [policy:review, policy:read, policy:simulate, findings:read]
|
||||
policy-approver:
|
||||
scopes: [policy:approve, policy:review, policy:read, policy:simulate, findings:read]
|
||||
policy-operator:
|
||||
scopes: [policy:operate, policy:run, policy:activate, policy:read, policy:simulate, findings:read]
|
||||
policy-auditor:
|
||||
scopes: [policy:audit, policy:read, policy:simulate, findings:read]
|
||||
policy-engine:
|
||||
scopes: [effective:write, findings:read]
|
||||
exceptions-service:
|
||||
scopes: [exceptions:read, exceptions:write]
|
||||
exceptions-approver:
|
||||
scopes: [exceptions:read, exceptions:approve]
|
||||
```
|
||||
|
||||
> **MFA requirement:** When any `exceptions.routingTemplates` entry sets `requireMfa: true`, Authority refuses to mint tokens containing `exceptions:approve` unless the authenticating identity provider advertises MFA support. Password/OIDC flows produce `authority.password.grant` audit events with `reason="Exception approval scope requires an MFA-capable identity provider."` when the requirement is violated.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Tenancy enforcement
|
||||
@@ -64,15 +119,18 @@ Tokens now include:
|
||||
Authority rejects requests when:
|
||||
|
||||
- `tenant` is missing while requesting `advisory:ingest`, `advisory:read`, `vex:ingest`, `vex:read`, or `aoc:verify` scopes.
|
||||
- `aoc:verify` is absent while tokens request `advisory:read`, `vex:read`, or any `signals:*` scope (`invalid_scope` with deterministic message).
|
||||
- `service_identity != policy-engine` but `effective:write` is present (`ERR_AOC_006` enforcement).
|
||||
- `service_identity != cartographer` but `graph:write` is present (graph pipeline enforcement).
|
||||
- Tokens attempt to combine `advisory:ingest` with `effective:write` (separation of duties).
|
||||
- `exceptions:approve` is requested by a client without a tenant assignment or via an identity provider lacking MFA when `RequireMfaForApprovals=true`.
|
||||
|
||||
### 2.2 Propagation
|
||||
|
||||
- API Gateway forwards `tenant` claim as header (`X-Stella-Tenant`). Services refuse requests lacking the header.
|
||||
- Concelier/Excititor stamp tenant into raw documents and structured logs.
|
||||
- Policy Engine copies `tenant` from tokens into `effective_finding_*` collections.
|
||||
- Exception lifecycle services persist tenant and the selected routing template identifier alongside approval decisions. Authority audit events (`authority.password.grant`, `authority.client_credentials.grant`) surface `audit.scopes` and, on denials, a `scope.invalid` metadata entry so operators can trace exception approval attempts without inspecting downstream services.
|
||||
|
||||
### 2.3 Cross-tenant scenarios
|
||||
|
||||
|
||||
@@ -49,15 +49,35 @@ The console client is registered in Authority as `console-ui` with scopes:
|
||||
|--------------|----------------|-------|
|
||||
| Base navigation (Dashboard, Findings, SBOM, Runs) | `ui.read`, `findings:read`, `advisory:read`, `vex:read`, `aoc:verify` | `findings:read` enables Policy Engine overlays; `advisory:read`/`vex:read` load ingestion panes; `aoc:verify` allows on-demand guard runs. |
|
||||
| Admin workspace | `ui.admin`, `authority:tenants.read`, `authority:tenants.write`, `authority:roles.read`, `authority:roles.write`, `authority:tokens.read`, `authority:tokens.revoke`, `authority:clients.read`, `authority:clients.write`, `authority:audit.read` | Scope combinations are tenant constrained. Role changes require fresh-auth. |
|
||||
| Policy approvals | `policy:read`, `policy:review`, `policy:approve`, `policy:activate`, `policy:runs` | `policy:activate` gated behind fresh-auth. |
|
||||
| Policy approvals | `policy:read`, `policy:review`, `policy:approve`, `policy:operate`, `policy:simulate` | `policy:operate` (promote/activate/run) requires fresh-auth. |
|
||||
| Observability panes (status ticker, telemetry) | `ui.telemetry`, `scheduler:runs.read`, `advisory:read`, `vex:read` | `ui.telemetry` drives OTLP export toggles. |
|
||||
| Orchestrator dashboard (queues, workers, rate limits) | `orch:read` | Provision via `Orch.Viewer` role; read-only access to job state and telemetry. |
|
||||
| Orchestrator control actions (pause/resume, retry, sync-now, backfill) | `orch:operate` (plus `orch:read`) | CLI/Console must request tokens with `operator_reason` and `operator_ticket`; Authority denies issuance when either value is missing. |
|
||||
| Downloads parity (SBOM, attestation) | `downloads:read`, `attestation:verify`, `sbom:export` | Console surfaces digests only; download links require CLI parity for write operations. |
|
||||
|
||||
Guidance:
|
||||
|
||||
- **Role mapping**: Provision Authority role `role/ui-console-admin` encapsulating the admin scopes above.
|
||||
- **Orchestrator viewers**: Assign Authority role `role/orch-viewer` (Authority role string `Orch.Viewer`) to consoles that require read-only access to Orchestrator telemetry.
|
||||
- **Orchestrator operators**: Assign Authority role `role/orch-operator` (Authority role string `Orch.Operator`) to identities allowed to pause/resume or backfill. Tokens must include `operator_reason` (≤256 chars) and `operator_ticket` (≤128 chars); Authority records the values in audit logs.
|
||||
- **Tenant enforcement**: Gateway injects `X-Stella-Tenant` from token claims. Requests missing the header must be rejected by downstream services (Concelier, Excititor, Policy Engine) and logged.
|
||||
- **Separation of duties**: Never grant `ui.admin` and `policy:approve` to the same human role without SOC sign-off; automation accounts should use least-privilege dedicated clients.
|
||||
- **Separation of duties**: Never grant `ui.admin` and `policy:approve`/`policy:operate` to the same human role without SOC sign-off; automation accounts should use least-privilege dedicated clients.
|
||||
|
||||
---
|
||||
|
||||
### 3.1 Console Authority endpoints
|
||||
|
||||
Console uses dedicated Authority endpoints scoped under `/console/*`. All requests must include the tenant header injected by the gateway (`X-Stella-Tenant`); calls without the header fail with `tenant_header_missing` and emit a structured audit event. Keep reverse proxies configured to pass the header end-to-end.
|
||||
|
||||
| Endpoint | Required scopes | Purpose | Notes |
|
||||
|----------|-----------------|---------|-------|
|
||||
| `GET /console/tenants` | `authority:tenants.read` | Returns the tenant catalogue for the authenticated principal. | Validates `X-Stella-Tenant`; rejects tenants not configured in Authority. |
|
||||
| `GET /console/profile` | `ui.read` | Surfaces subject metadata (roles, scopes, session id, fresh-auth state). | Response includes `freshAuth` (bool) based on a 300 s window since `auth_time`. |
|
||||
| `POST /console/token/introspect` | `ui.read` | Introspects the access token currently in use and reports expiry + tenant. | Console polls this endpoint to drive session inactivity prompts; intended for SPA usage via fetch POST. |
|
||||
|
||||
**Fresh-auth & session inactivity:** Authority stamps `auth_time` on issued tokens and considers privileged actions “fresh” for five minutes. When `/console/profile` returns `freshAuth: false`, the UI must require an interactive re-authentication before allowing admin operations (`ui.admin`, `authority:*` mutations, `policy:activate`, `exceptions:approve`). Access tokens remain short-lived (`00:02:00` by default); pair this with Console session timeouts so idle dashboards prompt the user before two minutes of inactivity.
|
||||
|
||||
**DPoP + tenant binding:** All `/console/*` endpoints require DPoP-bound access tokens. Audit events include `tenant.resolved`, `scope`, `correlationId`, and (when applicable) `token.expires_at`. Staple the same headers into downstream services so cross-component troubleshooting uses the same correlation identifiers.
|
||||
|
||||
---
|
||||
|
||||
@@ -148,15 +168,16 @@ Document gaps and remediation hooks in `SEC5.*` backlog as they are addressed.
|
||||
|
||||
## 9 · Compliance checklist
|
||||
|
||||
- [ ] Authority client `console-ui` registered with PKCE, DPoP, tenant claim requirement, and scopes from §3.
|
||||
- [ ] CSP enforced per §4 with overrides documented in deployment manifests.
|
||||
- [ ] Fresh-auth timer (300 s) validated for admin and policy actions; audit events captured.
|
||||
- [ ] DPoP binding tested (replay attempt blocked; logs show `ui_dpop_failure_total` increment).
|
||||
- [ ] Offline mode exercises performed (banner, CLI guidance, manifest verification).
|
||||
- [ ] Evidence download parity verified with CLI scripts; console never caches sensitive artefacts.
|
||||
- [ ] Monitoring dashboards show metrics and alerts outlined in §6; alert runbooks reviewed with Security Guild.
|
||||
- [ ] Security review sign-off recorded in sprint log with links to Authority threat model references.
|
||||
- [x] Authority client `console-ui` registered with PKCE, DPoP, tenant claim requirement, and scopes from §3. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#authority-client-validation))
|
||||
- [x] CSP enforced per §4 with overrides documented in deployment manifests. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#csp-enforcement))
|
||||
- [x] Fresh-auth timer (300 s) validated for admin and policy actions; audit events captured. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#fresh-auth-timer))
|
||||
- [x] DPoP binding tested (replay attempt blocked; logs show `ui_dpop_failure_total` increment). (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#dpop-binding-test))
|
||||
- [x] Offline mode exercises performed (banner, CLI guidance, manifest verification). (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#offline-mode-exercise))
|
||||
- [x] Evidence download parity verified with CLI scripts; console never caches sensitive artefacts. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#evidence-parity))
|
||||
- [x] Monitoring dashboards show metrics and alerts outlined in §6; alert runbooks reviewed with Security Guild. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#monitoring--alerts))
|
||||
- [x] Security review sign-off recorded in sprint log with links to Authority threat model references. (see [console security sign-off](../updates/2025-10-27-console-security-signoff.md#sign-off))
|
||||
- [x] `/console` Authority endpoints validated for tenant header enforcement, fresh-auth prompts, and introspection flows (Audit IDs `authority.console.tenants.read`, `authority.console.profile.read`, `authority.console.token.introspect`). (see [console security sign-off](../updates/2025-10-31-console-security-refresh.md))
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-28 (Sprint 23).*
|
||||
*Last updated: 2025-10-31 (Sprint 23).*
|
||||
|
||||
165
docs/security/pack-signing-and-rbac.md
Normal file
165
docs/security/pack-signing-and-rbac.md
Normal file
@@ -0,0 +1,165 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Pack Signing & RBAC Controls
|
||||
|
||||
This document defines signing, verification, and authorization requirements for Task Packs across the CLI, Packs Registry, Task Runner, and Offline Kit. It aligns with Authority sprint tasks (`AUTH-PACKS-41-001`, `AUTH-PACKS-43-001`) and security guild expectations.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Threat Model Highlights
|
||||
|
||||
| Threat | Mitigation |
|
||||
|--------|------------|
|
||||
| Unsigned or tampered pack uploaded to registry | Mandatory cosign/DSSE verification before acceptance. |
|
||||
| Unauthorized user publishing or promoting packs | Authority scopes (`Packs.Write`) + registry policy checks. |
|
||||
| Privilege escalation during approvals | Approval gates require `Packs.Approve` + audit logging; fresh-auth recommended. |
|
||||
| Secret exfiltration via pack steps | Secrets injection sandbox with redaction, sealed-mode network guardrails, evidence review. |
|
||||
| Replay of old approval tokens | Approval payloads carry plan hash + expiry; Task Runner rejects mismatches. |
|
||||
| Malicious pack in Offline Kit | Mirror verification using signed manifest and DSSE provenance. |
|
||||
|
||||
---
|
||||
|
||||
## 2 · Signing Requirements
|
||||
|
||||
- **Cosign** signatures required for all bundles. Keys can be:
|
||||
- Keyless (Fulcio OIDC).
|
||||
- KMS-backed (HSM, cloud KMS).
|
||||
- Offline keys stored in secure vault (air-gapped mode).
|
||||
- **DSSE Attestations** recommended to embed:
|
||||
- Manifest digest.
|
||||
- Build metadata (repo, commit, CI run).
|
||||
- CLI version (`stella/pack`).
|
||||
- Signatures stored alongside bundle in registry object storage.
|
||||
- `stella pack push` refuses to publish without signature (unless `--insecure-publish` used in dev).
|
||||
- Registry enforces trust policy:
|
||||
|
||||
| Policy | Description |
|
||||
|--------|-------------|
|
||||
| `anyOf` | Accepts any key in configured trust store. |
|
||||
| `keyRef` | Accepts specific key ID (`kid`). |
|
||||
| `oidcIssuer` | Accepts Fulcio certificates from allowed issuers (e.g., `https://fulcio.sigstore.dev`). |
|
||||
| `threshold` | Requires N-of-M signatures (future release). |
|
||||
|
||||
---
|
||||
|
||||
## 3 · RBAC & Scopes
|
||||
|
||||
Authority exposes pack-related scopes:
|
||||
|
||||
| Scope | Description |
|
||||
|-------|-------------|
|
||||
| `Packs.Read` | View packs, download manifests/bundles. |
|
||||
| `Packs.Write` | Publish, promote, deprecate packs. |
|
||||
| `Packs.Run` | Execute packs (Task Runner, CLI). |
|
||||
| `Packs.Approve` | Approve pack gates, override tenant visibility. |
|
||||
|
||||
### 3.1 Role Mapping
|
||||
|
||||
| Role | Scopes | Use Cases |
|
||||
|------|--------|-----------|
|
||||
| `pack.viewer` | `Packs.Read` | Inspect packs, plan runs. |
|
||||
| `pack.publisher` | `Packs.Read`, `Packs.Write` | Publish new versions, manage channels. |
|
||||
| `pack.operator` | `Packs.Read`, `Packs.Run` | Execute packs, monitor runs. |
|
||||
| `pack.approver` | `Packs.Read`, `Packs.Approve` | Fulfil approvals, authorize promotions. |
|
||||
| `pack.admin` | All | Full lifecycle management (rare). |
|
||||
|
||||
Roles are tenant-scoped; cross-tenant access requires explicit addition.
|
||||
|
||||
### 3.2 CLI Enforcement
|
||||
|
||||
- CLI requests scopes based on command:
|
||||
- `stella pack plan` → `Packs.Read`.
|
||||
- `stella pack run` → `Packs.Run`.
|
||||
- `stella pack push` → `Packs.Write`.
|
||||
- `stella pack approve` → `Packs.Approve`.
|
||||
- Offline tokens must include same scopes; CLI warns if missing.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Approvals & Fresh Auth
|
||||
|
||||
- Approval commands require recent fresh-auth (< 5 minutes). CLI prompts automatically; Console enforces via Authority.
|
||||
- Approval payload includes:
|
||||
- `runId`
|
||||
- `gateId`
|
||||
- `planHash`
|
||||
- `approver`
|
||||
- `timestamp`
|
||||
- Task Runner logs approval event and verifies plan hash to prevent rerouting.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Secret Management
|
||||
|
||||
- Secrets defined in pack manifest map to Authority secret providers (e.g., HSM, Vault).
|
||||
- Task Runner obtains secrets using service account with scoped access; CLI may prompt or read from profile.
|
||||
- Secret audit trail:
|
||||
- `secretRequested` event with reason, pack, step.
|
||||
- `secretDelivered` event omitted (only aggregate metrics) to avoid leakage.
|
||||
- Evidence bundle includes hashed secret metadata (no values).
|
||||
|
||||
Sealed mode requires secrets to originate from sealed vault; external endpoints blocked.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Audit & Evidence
|
||||
|
||||
- Registry, Task Runner, and Authority emit audit events to central timeline.
|
||||
- Required events:
|
||||
- `pack.version.published`
|
||||
- `pack.version.promoted`
|
||||
- `pack.run.started/completed`
|
||||
- `pack.approval.requested/granted`
|
||||
- `pack.secret.requested`
|
||||
- Evidence Locker stores DSSE attestations and run bundles for 90 days (configurable).
|
||||
- Auditors can use `stella pack audit --run <id>` to retrieve audit trail.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Offline / Air-Gap Policies
|
||||
|
||||
- Offline Kit includes:
|
||||
- Pack bundles + signatures.
|
||||
- Trusted key store (`trust-bundle.pem`).
|
||||
- Approval workflow instructions for manual signing.
|
||||
- Air-gapped approvals:
|
||||
- CLI generates approval request file (`.approval-request.json`).
|
||||
- Approver uses offline CLI to sign with offline key.
|
||||
- Response imported to Task Runner.
|
||||
- Mirror process verifies signatures prior to import; failure aborts import with `ERR_PACK_SIGNATURE_INVALID`.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Incident Response
|
||||
|
||||
- Compromised pack signature:
|
||||
- Revoke key via Authority trust store.
|
||||
- Deprecate affected versions (`registry deprecate`).
|
||||
- Notify consumers via Notifier (`pack.security.alert`).
|
||||
- Forensically review run evidence for impacted tenants.
|
||||
- Unauthorized approval:
|
||||
- Review audit log for `Packs.Approve` events.
|
||||
- Trigger `pack.run.freeze` (pauses run pending investigation).
|
||||
- Rotate approver credentials and require fresh-auth.
|
||||
- Secret leak suspicion:
|
||||
- Quarantine evidence bundles.
|
||||
- Rotate secrets referenced by pack.
|
||||
- Run sealed-mode audit script to confirm guardrails.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Compliance Checklist
|
||||
|
||||
- [ ] Signing requirements (cosign/DSSE, trust policies) documented.
|
||||
- [ ] Authority scope mapping and CLI enforcement captured.
|
||||
- [ ] Approval workflow + fresh-auth expectations defined.
|
||||
- [ ] Secret lifecycle (request, injection, audit) described.
|
||||
- [ ] Audit/evidence integration noted (timeline, Evidence Locker).
|
||||
- [ ] Offline/air-gap controls outlined.
|
||||
- [ ] Incident response playbook provided.
|
||||
- [ ] Imposed rule reminder retained at top.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
@@ -21,16 +21,14 @@
|
||||
| Scope | Description | Recommended role |
|
||||
|-------|-------------|------------------|
|
||||
| `policy:read` | View policies, revisions, runs, findings. | Readers, auditors. |
|
||||
| `policy:write` | Create/edit drafts, run lint/compile. | Authors (SecOps engineers). |
|
||||
| `policy:submit` | Move draft → submitted, attach simulations. | Authors with submission rights. |
|
||||
| `policy:review` | Comment/approve/request changes (non-final). | Reviewers (peer security, product). |
|
||||
| `policy:approve` | Final approval; can archive. | Approval board/security lead. |
|
||||
| `policy:activate` | Promote approved version, schedule activation. | Runtime operators / release managers. |
|
||||
| `policy:run` | Trigger runs, inspect live status. | Operators, automation bots. |
|
||||
| `policy:runs` | Read run history, replay bundles. | Operators, auditors. |
|
||||
| `policy:archive` | Retire versions, perform rollbacks. | Approvers, operators. |
|
||||
| `policy:author` | Create/edit drafts, lint/compile, quick simulate. | `role/policy-author`. |
|
||||
| `policy:review` | Comment, request changes, approve in-progress drafts. | `role/policy-reviewer`. |
|
||||
| `policy:approve` | Final approval; archive decisions. | `role/policy-approver`. |
|
||||
| `policy:operate` | Promote revisions, trigger runs, manage rollouts. | `role/policy-operator`, automation bots. |
|
||||
| `policy:audit` | Access immutable history and evidence bundles. | `role/policy-auditor`, compliance teams. |
|
||||
| `policy:simulate` | Execute simulations via API/CLI. | Authors, reviewers, CI. |
|
||||
| `policy:operate` | Activate incident mode, toggle sampling. | SRE/on-call. |
|
||||
| `policy:run` | Trigger runs, inspect live status. | Operators, automation bots. |
|
||||
| `policy:activate` | Promote approved version, schedule activation. | Runtime operators / release managers. |
|
||||
| `findings:read` | View effective findings/explain. | Analysts, auditors, CLI. |
|
||||
| `effective:write` | **Service only** – materialise findings. | Policy Engine service principal. |
|
||||
|
||||
|
||||
208
docs/task-packs/authoring-guide.md
Normal file
208
docs/task-packs/authoring-guide.md
Normal file
@@ -0,0 +1,208 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Task Pack Authoring Guide
|
||||
|
||||
This guide teaches engineers how to design, validate, and publish Task Packs that align with the Sprint 43 specification. Follow these steps to ensure deterministic behaviour, secure approvals, and smooth hand-off to operators.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Prerequisites
|
||||
|
||||
- StellaOps CLI `>= 2025.10.0` with pack commands enabled.
|
||||
- Authority client configured with `Packs.Write` (publish) and `Packs.Run` (local testing) scopes.
|
||||
- Access to Task Runner staging environment for validation runs.
|
||||
- Familiarity with the [Task Pack Specification](spec.md) and [Packs Registry](registry.md).
|
||||
- Optional: connection to DevOps staging registry or Offline Kit mirror for publishing.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Design Checklist
|
||||
|
||||
1. **Define objective.** Document the operational need, inputs, expected outputs, and rollback strategy.
|
||||
2. **Identify approvals.** Determine which scopes/roles must sign off (`Packs.Approve` assignments).
|
||||
3. **Plan security posture.** Limit secrets usage, set tenant visibility, and note network constraints (sealed mode).
|
||||
4. **Model observability.** Decide which metrics, logs, and evidence artifacts are critical for post-run audits.
|
||||
5. **Reuse libraries.** Prefer built-in modules or shared pack fragments to reduce drift.
|
||||
|
||||
Capture the above in `docs/summary.md` (optional but recommended) for future maintainers.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Authoring Workflow
|
||||
|
||||
### 3.1 Scaffold project
|
||||
|
||||
```bash
|
||||
mkdir my-pack
|
||||
cd my-pack
|
||||
stella pack init --name sbom-remediation
|
||||
```
|
||||
|
||||
`stella pack init` creates baseline files:
|
||||
|
||||
- `pack.yaml` with metadata placeholders.
|
||||
- `schemas/inputs.schema.json` (sample).
|
||||
- `docs/usage.md` (template for human instructions).
|
||||
- `.packignore` to exclude build artifacts.
|
||||
|
||||
### 3.2 Define inputs & schemas
|
||||
|
||||
- Use JSON Schema (`draft-2020-12`) for input validation.
|
||||
- Avoid optional inputs unless there is a deterministic default.
|
||||
- Store schemas under `schemas/` and reference via relative paths.
|
||||
|
||||
### 3.3 Compose steps
|
||||
|
||||
- Break workflow into small deterministic steps.
|
||||
- Name each step with stable `id`.
|
||||
- Wrap scripts/tools using built-in modules; copy scripts to `assets/` if necessary.
|
||||
- Use `when` expressions for branch logic; ensure expressions rely solely on inputs or previous outputs.
|
||||
- For loops, adopt `map` with capped iteration count; avoid data-dependent randomness.
|
||||
|
||||
### 3.4 Configure approvals
|
||||
|
||||
- Add `spec.approvals` entries for each required review.
|
||||
- Provide informative `reasonTemplate` with placeholders.
|
||||
- Set `expiresAfter` to match operational policy (e.g., 4 h for security reviews).
|
||||
- Document fallback contacts in `docs/runbook.md`.
|
||||
|
||||
### 3.5 Manage secrets
|
||||
|
||||
- Declare secrets under `spec.secrets`.
|
||||
- Reference secrets via expressions (e.g., `{{ secrets.jiraToken.value }}`) inside modules that support secure injection.
|
||||
- Never bake secrets or tokens into pack assets.
|
||||
- If secret optional, set `optional: true` and handle absence in step logic.
|
||||
|
||||
### 3.6 Document outputs
|
||||
|
||||
- List expected artifacts under `spec.outputs`.
|
||||
- Include human-friendly docs (Markdown) describing each output and how to access it through CLI or Console.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Validation
|
||||
|
||||
### 4.1 Static validation
|
||||
|
||||
```bash
|
||||
stella pack validate
|
||||
```
|
||||
|
||||
Checks performed:
|
||||
|
||||
- Schema compliance (YAML, JSON Schema).
|
||||
- Determinism guard (forbidden functions, clock usage, network allowlist).
|
||||
- Reference integrity (assets, schemas, documentation).
|
||||
- Approval/secret scope availability.
|
||||
|
||||
### 4.2 Simulation & plan hash
|
||||
|
||||
```bash
|
||||
stella pack plan --inputs samples/inputs.json --output .artifacts/plan.json
|
||||
stella pack simulate --inputs samples/inputs.json --output .artifacts/sim.json
|
||||
```
|
||||
|
||||
- Review plan graph to ensure step ordering and gating align with expectations.
|
||||
- Store simulation output with pack metadata for future audits.
|
||||
|
||||
### 4.3 Local rehearsal
|
||||
|
||||
```bash
|
||||
stella pack run \
|
||||
--inputs samples/inputs.json \
|
||||
--secrets jiraToken=@secrets/jira.txt \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
- Use `--dry-run` to verify approvals and outputs without side effects.
|
||||
- Real runs require `Packs.Run` and all approval gates satisfied (e.g., via CLI prompts or Console).
|
||||
|
||||
### 4.4 Unit tests (optional but encouraged)
|
||||
|
||||
- Create a `tests/` folder with CLI-driven regression scripts (e.g., using `stella pack plan` + `jq` assertions).
|
||||
- Integrate into CI pipelines; ensure tests run offline using cached assets.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Publishing
|
||||
|
||||
### 5.1 Build bundle
|
||||
|
||||
```bash
|
||||
stella pack build \
|
||||
--output dist/sbom-remediation-1.3.0.stella-pack.tgz \
|
||||
--manifest pack.yaml
|
||||
```
|
||||
|
||||
### 5.2 Sign bundle
|
||||
|
||||
```bash
|
||||
cosign sign-blob \
|
||||
--yes \
|
||||
--output-signature dist/sbom-remediation-1.3.0.sig \
|
||||
dist/sbom-remediation-1.3.0.stella-pack.tgz
|
||||
```
|
||||
|
||||
Store signature alongside bundle; DSSE optional but recommended (see [security guidance](../security/pack-signing-and-rbac.md)).
|
||||
|
||||
### 5.3 Publish to registry
|
||||
|
||||
```bash
|
||||
stella pack push \
|
||||
registry.stella-ops.org/packs/sbom-remediation:1.3.0 \
|
||||
--bundle dist/sbom-remediation-1.3.0.stella-pack.tgz \
|
||||
--signature dist/sbom-remediation-1.3.0.sig
|
||||
```
|
||||
|
||||
Registry verifies signature, stores provenance, and updates index.
|
||||
|
||||
### 5.4 Offline distribution
|
||||
|
||||
- Export bundle + signature + provenance into Offline Kit using `stella pack bundle export`.
|
||||
- Update mirror manifest (`manifest/offline-manifest.json`) with new pack entries.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Versioning & Compatibility
|
||||
|
||||
- Follow SemVer (increment major when breaking schema/behaviour).
|
||||
- Document compatibility in `docs/compatibility.md` (recommended).
|
||||
- Registry retains immutable history; use `metadata.deprecated: true` to indicate retirement.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Best Practices
|
||||
|
||||
- **Keep steps idempotent.** Support manual retries without side effects.
|
||||
- **Surface evidence early.** Export intermediate artifacts (plans, logs) for operators.
|
||||
- **Localize messages.** Provide `locales/en-US.json` for CLI/Console strings (Sprint 43 requirement).
|
||||
- **Avoid long-running commands.** Split heavy tasks into smaller steps with progress telemetry.
|
||||
- **Guard network usage.** Use `when: "{{ env.isSealed }}"` to block disallowed network operations or provide offline instructions.
|
||||
- **Document fallbacks.** Include manual recovery instructions in `docs/runbook.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Hand-off & Review
|
||||
|
||||
- Submit PR including pack bundle metadata, docs, and validation evidence.
|
||||
- Request review from Task Runner + Security + DevOps stakeholders.
|
||||
- Attach `stella pack plan` output and signature digest to review notes.
|
||||
- After approval, update change log (`docs/CHANGELOG.md`) and notify Task Runner operations.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Compliance Checklist
|
||||
|
||||
- [ ] Metadata, inputs, steps, approvals, secrets, and outputs defined per spec.
|
||||
- [ ] Schemas provided for all object inputs and outputs.
|
||||
- [ ] Determinism validation (`stella pack validate`) executed with evidence stored.
|
||||
- [ ] Plan + simulation artifacts committed in `.artifacts/` or CI evidence store.
|
||||
- [ ] Bundle signed (cosign/DSSE) and signature recorded.
|
||||
- [ ] Runbook and troubleshooting notes documented.
|
||||
- [ ] Offline distribution steps prepared (bundle export + manifest update).
|
||||
- [ ] Imposed rule reminder retained at top.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
174
docs/task-packs/registry.md
Normal file
174
docs/task-packs/registry.md
Normal file
@@ -0,0 +1,174 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Packs Registry Architecture & Operations
|
||||
|
||||
The Packs Registry stores, verifies, and serves Task Pack bundles across environments. It integrates with Authority for RBAC, Task Runner for execution, DevOps for release automation, and Offline Kit for air-gapped distribution.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Service Overview
|
||||
|
||||
- **Service name:** `StellaOps.PacksRegistry`
|
||||
- **Interfaces:** REST/GraphQL API, OCI-compatible registry endpoints, event streams for mirroring.
|
||||
- **Data stores:** MongoDB (`packs`, `pack_versions`, `pack_provenance`), object storage (bundle blobs, signatures), timeline events.
|
||||
- **Dependencies:** Authority scopes (`Packs.*`), Export Center (manifests), DevOps signing service, Notifications (optional).
|
||||
|
||||
---
|
||||
|
||||
## 2 · Core Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| **Pack record** | Immutable entry representing a pack version; includes metadata, digest, signatures, tenant visibility. |
|
||||
| **Channel** | Logical distribution channel (`stable`, `edge`, `beta`, custom). Controls mirroring/promotion flows. |
|
||||
| **Provenance** | DSSE statements + SBOM linking pack bundle to source repo, CLI build, and Task Runner compatibility. |
|
||||
| **Mirroring policy** | Rules specifying which packs replicate to downstream registries or Offline Kit bundles. |
|
||||
| **Audit trail** | Append-only log capturing publish/update/delete actions, approvals, and policy evaluations. |
|
||||
|
||||
---
|
||||
|
||||
## 3 · API Surface
|
||||
|
||||
### 3.1 REST Endpoints
|
||||
|
||||
| Method | Path | Description | Scopes |
|
||||
|--------|------|-------------|--------|
|
||||
| `GET` | `/api/packs` | List packs with filters (`name`, `channel`, `tenant`, `tag`). | `Packs.Read` |
|
||||
| `GET` | `/api/packs/{packId}/versions` | List versions with metadata, provenance. | `Packs.Read` |
|
||||
| `GET` | `/api/packs/{packId}/versions/{version}` | Retrieve manifest, signatures, compatibility matrix. | `Packs.Read` |
|
||||
| `POST` | `/api/packs/{packId}/versions` | Publish new version (bundle upload or OCI reference). | `Packs.Write` |
|
||||
| `POST` | `/api/packs/{packId}/promote` | Promote version between channels (edge→stable). | `Packs.Write` + approval policy |
|
||||
| `DELETE` | `/api/packs/{packId}/versions/{version}` | Deprecate version (soft delete, immutability preserved). | `Packs.Write` |
|
||||
| `GET` | `/api/packs/{packId}/events` | Stream audit events (SSE). | `Packs.Read` |
|
||||
|
||||
### 3.2 OCI Endpoints
|
||||
|
||||
The registry exposes OCI-compatible endpoints (`/v2/<namespace>/<pack>/...`) supporting:
|
||||
|
||||
- `PUT`/`PATCH`/`GET` for manifests and blobs.
|
||||
- Content-addressed digests using SHA-256.
|
||||
- Annotations for pack metadata (`org.opencontainers.image.title`, `io.stellaops.pack.metadata`).
|
||||
|
||||
### 3.3 GraphQL (Optional)
|
||||
|
||||
GraphQL endpoint (`/api/graphql`) enables advanced queries (filter by approvals, tags, compatibility). Under active development; reference API schema once published.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Publishing Workflow
|
||||
|
||||
1. CLI/CI calls `POST /api/packs/{id}/versions` with signed bundle.
|
||||
2. Registry verifies:
|
||||
- Manifest schema compliance.
|
||||
- Signature (cosign/DSSE) validity.
|
||||
- Authority scopes (`Packs.Write`).
|
||||
- Tenant visibility constraints.
|
||||
3. On success, registry stores bundle, provenance, and emits event (`pack.version.published`).
|
||||
4. Optional promotion requires additional approvals or integration with DevOps release boards.
|
||||
|
||||
All actions recorded in audit log:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "evt_01HF...",
|
||||
"type": "pack.version.published",
|
||||
"packId": "sbom-remediation",
|
||||
"version": "1.3.0",
|
||||
"actor": "user:alice",
|
||||
"tenant": "west-prod",
|
||||
"source": "cli/2025.10.0",
|
||||
"signatures": ["sha256:..."],
|
||||
"metadataHash": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5 · Mirroring & Offline Support
|
||||
|
||||
- **Automatic mirroring:** Configure policies to push packs to secondary registries (edge clusters, regional mirrors) or object stores.
|
||||
- **Offline Kit integration:** `ops/offline-kit` pipeline pulls packs matching specified channels and writes them to `offline/packs/manifest.json` with signatures.
|
||||
- **Checksum manifest:** Registry maintains `digestmap.json` listing pack digests + signatures; offline installers verify before import.
|
||||
- **Sealed mode:** Registry can operate in read-only mode for sealed environments; publishing disabled except via offline import command (`stella pack mirror import`).
|
||||
|
||||
---
|
||||
|
||||
## 6 · Security & Compliance
|
||||
|
||||
- Enforce Authority scopes; tokens without tenant or required scope are rejected (`ERR_PACK_SCOPE`).
|
||||
- Signatures verified using trusted Fulcio/KMS roots; optional mirror trust bundles configured via `registry.trustBundle`.
|
||||
- RBAC mapping:
|
||||
|
||||
| Role | Scopes | Capabilities |
|
||||
|------|--------|--------------|
|
||||
| `PackViewer` | `Packs.Read` | Browse, fetch manifests/bundles. |
|
||||
| `PackPublisher` | `Packs.Read`, `Packs.Write` | Publish/promote, manage channels (subject to policy). |
|
||||
| `PackApprover` | `Packs.Read`, `Packs.Approve` | Approve promotions, override tenant visibility (with audit logging). |
|
||||
| `PackOperator` | `Packs.Read`, `Packs.Run` | Execute packs (via CLI/Task Runner). |
|
||||
|
||||
- Audit events forwarded to Authority + Evidence Locker.
|
||||
- Built-in malware/secret scanning runs on bundle upload (configurable via DevOps pipeline).
|
||||
|
||||
See [pack signing & RBAC guidance](../security/pack-signing-and-rbac.md) for deeper controls.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Observability
|
||||
|
||||
- Metrics (`registry` namespace):
|
||||
- `pack_publish_total{result}` – success/failure counts.
|
||||
- `pack_signature_verify_seconds` – verification latency.
|
||||
- `pack_channel_promotions_total` – promotions per channel.
|
||||
- `pack_mirror_queue_depth` – pending mirror jobs.
|
||||
- Logs (structured JSON with `packId`, `version`, `actor`, `tenant`, `digest`).
|
||||
- Traces instrument bundle verification, storage writes, and mirror pushes.
|
||||
- Alerting suggestions:
|
||||
- Publish failure rate > 5 % (5 m window) triggers DevOps escalation.
|
||||
- Mirror lag > 15 m surfaces to Ops dashboard.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Schema & Metadata Extensions
|
||||
|
||||
- Default metadata stored under `metadata.*` from manifest.
|
||||
- Registry supplements with:
|
||||
- `compatibility.cli` (supported CLI versions).
|
||||
- `compatibility.runner` (Task Runner build requirements).
|
||||
- `provenance.attestations[]` (URIs).
|
||||
- `channels[]` (current channel assignments).
|
||||
- `tenantVisibility[]`.
|
||||
- `deprecated` flag + replacement hints.
|
||||
|
||||
Extensions must be deterministic and derived from signed bundle data.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Operations
|
||||
|
||||
- **Backups:** Daily snapshots of Mongo collections + object storage, retained for 30 days.
|
||||
- **Retention:** Old versions retained indefinitely; mark as `deprecated` instead of deleting.
|
||||
- **Maintenance:**
|
||||
- Run `registry vacuum` weekly to prune orphaned blobs.
|
||||
- Rotate signing keys per security policy (document in `pack-signing-and-rbac`).
|
||||
- Validate trust bundles quarterly.
|
||||
- **Disaster recovery:**
|
||||
- Restore database + object storage.
|
||||
- Rebuild OCI indexes (`registry rebuild-index`).
|
||||
- Replay audit events for downstream systems.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Compliance Checklist
|
||||
|
||||
- [ ] REST + OCI endpoints documented with required scopes.
|
||||
- [ ] Publishing flow covers signature verification, audit logging, and promotion policies.
|
||||
- [ ] Mirroring/offline strategy recorded (policies, manifests, sealed mode notes).
|
||||
- [ ] RBAC roles and scope mapping defined.
|
||||
- [ ] Observability metrics, logs, and alerts described.
|
||||
- [ ] Operations guidance covers backups, rotation, disaster recovery.
|
||||
- [ ] Imposed rule reminder included at top of document.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
162
docs/task-packs/runbook.md
Normal file
162
docs/task-packs/runbook.md
Normal file
@@ -0,0 +1,162 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Task Pack Operations Runbook
|
||||
|
||||
This runbook guides SREs and on-call engineers through executing, monitoring, and troubleshooting Task Packs using the Task Runner service, Packs Registry, and StellaOps CLI. It aligns with Sprint 43 deliverables (approvals workflow, notifications, chaos resilience).
|
||||
|
||||
---
|
||||
|
||||
## 1 · Quick Reference
|
||||
|
||||
| Action | Command / UI | Notes |
|
||||
|--------|--------------|-------|
|
||||
| Validate pack | `stella pack validate --bundle <file>` | Run before publishing or importing. |
|
||||
| Plan pack run | `stella pack plan --inputs inputs.json` | Outputs plan hash, required approvals, secret summary. |
|
||||
| Execute pack | `stella pack run --pack <id>:<version>` | Streams logs; prompts for secrets/approvals if allowed. |
|
||||
| Approve gate | Console notifications or `stella pack approve --run <id> --gate <gate>` | Requires `Packs.Approve`. |
|
||||
| View run | Console `/console/packs/runs/:id` or `stella pack runs show <id>` | SSE stream available for live status. |
|
||||
| Export evidence | `stella pack runs export --run <id>` | Produces bundle with plan, logs, artifacts, attestations. |
|
||||
|
||||
---
|
||||
|
||||
## 2 · Run Lifecycle
|
||||
|
||||
1. **Submission**
|
||||
- CLI/Orchestrator submits run with inputs, pack version, tenant context.
|
||||
- Task Runner validates pack hash, scopes, sealed-mode constraints.
|
||||
2. **Plan & Simulation**
|
||||
- Runner caches plan graph; optional simulation diff recorded.
|
||||
3. **Approvals**
|
||||
- Gates emit notifications (`NOTIFY-SVC-40-001`).
|
||||
- Approvers can approve/resume via CLI, Console, or API.
|
||||
4. **Execution**
|
||||
- Steps executed per plan (sequential/parallel).
|
||||
- Logs streamed via SSE (`/task-runner/runs/{id}/logs`).
|
||||
5. **Evidence & Attestation**
|
||||
- On completion, DSSE attestation + evidence bundle stored.
|
||||
- Exports available via Export Center.
|
||||
6. **Cleanup**
|
||||
- Artifacts retained per retention policy (default 30 d).
|
||||
- Mirror pack run manifest to Offline Kit if configured.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Monitoring & Telemetry
|
||||
|
||||
- **Metrics dashboards:** `task-runner` Grafana board.
|
||||
- `pack_run_active` – active runs per tenant.
|
||||
- `pack_step_duration_seconds` – histograms per step type.
|
||||
- `pack_gate_wait_seconds` – approval wait time (alert > 30 m).
|
||||
- `pack_run_success_ratio` – success vs failure rate.
|
||||
- **Logs:** Search by `runId`, `packId`, `tenant`, `stepId`.
|
||||
- **Traces:** Query `taskrunner.run` span in Tempo/Jaeger.
|
||||
- **Notifications:** Subscribe to `pack.run.*` topics via Notifier for Slack/email/PagerDuty hooks.
|
||||
|
||||
Observability configuration referenced in Task Runner tasks (OBS-50-001..55-001).
|
||||
|
||||
---
|
||||
|
||||
## 4 · Approvals Workflow
|
||||
|
||||
- Approvals may be requested via Console banner, CLI prompt, or email/Slack.
|
||||
- Approver roles: `Packs.Approve` + tenant membership.
|
||||
- CLI command:
|
||||
|
||||
```bash
|
||||
stella pack approve \
|
||||
--run run:tenant:timestamp \
|
||||
--gate security-review \
|
||||
--comment "Validated remediation scope; proceeding."
|
||||
```
|
||||
|
||||
- Auto-expiry triggers run cancellation (configurable per gate).
|
||||
- Approval events logged and included in evidence bundle.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Secrets Handling
|
||||
|
||||
- Secrets retrieved via Authority secure channel or CLI profile.
|
||||
- Task Runner injects secrets into isolated environment variables or temp files (auto-shredded).
|
||||
- Logs redact secrets; evidence bundles include only secret metadata (name, scope, last four characters).
|
||||
- For sealed mode, secrets must originate from sealed vault (configured via `TASKRUNNER_SEALED_VAULT_URL`).
|
||||
|
||||
---
|
||||
|
||||
## 6 · Failure Recovery
|
||||
|
||||
| Scenario | Symptom | Resolution |
|
||||
|----------|---------|------------|
|
||||
| **Plan hash mismatch** | Run aborted with `ERR_PACK_HASH_MISMATCH`. | Re-run `stella pack plan`; ensure pack not modified post-plan. |
|
||||
| **Approval timeout** | `ERR_PACK_APPROVAL_TIMEOUT`. | Requeue run with extended TTL or escalate to approver; verify notifications delivered. |
|
||||
| **Secret missing** | Run fails at injection step. | Provide secret via CLI (`--secrets`) or configure profile; check Authority scope. |
|
||||
| **Network blocked (sealed)** | `ERR_PACK_NETWORK_BLOCKED`. | Update pack to avoid external calls or whitelist domain via AirGap policy. |
|
||||
| **Artifact upload failure** | Evidence missing, logs show storage errors. | Retry run with `--resume` (if supported); verify object storage health. |
|
||||
| **Runner chaos trigger** | Run paused with chaos event note. | Review chaos test plan; resume if acceptable or cancel run. |
|
||||
|
||||
`stella pack runs resume --run <id>` resumes paused runs post-remediation (approvals or transient failures).
|
||||
|
||||
---
|
||||
|
||||
## 7 · Chaos & Resilience
|
||||
|
||||
- Chaos hooks pause runs, drop network, or delay approvals to test resilience.
|
||||
- Track chaos events via `pack.chaos.injected` timeline entries.
|
||||
- Post-chaos, ensure metrics return to baseline; record findings in Ops log.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Offline & Air-Gapped Execution
|
||||
|
||||
- Use `stella pack mirror pull` to import packs into sealed registry.
|
||||
- CLI caches bundles under `~/.stella/packs/` for offline runs.
|
||||
- Approvals require offline process:
|
||||
- Generate approval request bundle (`stella pack approve --offline-request`).
|
||||
- Approver signs bundle using offline CLI.
|
||||
- Import approval via `stella pack approve --offline-response`.
|
||||
- Evidence bundles exported to removable media; verify checksums before upload to online systems.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Runbooks for Common Packs
|
||||
|
||||
Maintain per-pack playbooks in `docs/task-packs/runbook/<pack-name>.md`. Include:
|
||||
|
||||
- Purpose and scope.
|
||||
- Required inputs and secrets.
|
||||
- Approval stakeholders.
|
||||
- Pre-checks and post-checks.
|
||||
- Rollback procedures.
|
||||
|
||||
The Docs Guild can use this root runbook as a template.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Escalation Matrix
|
||||
|
||||
| Issue | Primary | Secondary | Notes |
|
||||
|-------|---------|-----------|-------|
|
||||
| Pack validation errors | DevEx/CLI Guild | Task Runner Guild | Provide pack bundle + validation output. |
|
||||
| Approval pipeline failure | Task Runner Guild | Authority Core | Confirm scope/role mapping. |
|
||||
| Registry outage | Packs Registry Guild | DevOps Guild | Use mirror fallback if possible. |
|
||||
| Evidence integrity issues | Evidence Locker Guild | Security Guild | Validate DSSE attestations, escalate if tampered. |
|
||||
|
||||
Escalations must include run ID, tenant, pack version, plan hash, and timestamps.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Compliance Checklist
|
||||
|
||||
- [ ] Run lifecycle documented (submission → evidence).
|
||||
- [ ] Monitoring metrics, logs, traces, and notifications captured.
|
||||
- [ ] Approvals workflow instructions provided (CLI + Console).
|
||||
- [ ] Secret handling, sealed-mode constraints, and offline process described.
|
||||
- [ ] Failure scenarios + recovery steps listed.
|
||||
- [ ] Chaos/resilience guidance included.
|
||||
- [ ] Escalation matrix defined.
|
||||
- [ ] Imposed rule reminder included at top.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
249
docs/task-packs/spec.md
Normal file
249
docs/task-packs/spec.md
Normal file
@@ -0,0 +1,249 @@
|
||||
> **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
|
||||
# Task Pack Specification (Sprint 43 Draft)
|
||||
|
||||
The Task Pack specification defines a deterministic, auditable format that enables operators to encode multi-step maintenance, validation, and deployment workflows. Packs are executed by the Task Runner service, distributed through the Packs Registry, and invoked via the StellaOps CLI (`stella pack ...`) or Orchestrator integrations.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Goals & Scope
|
||||
|
||||
- **Deterministic execution.** Identical inputs yield identical run graphs, output manifests, and evidence bundles across environments (online, sealed, or offline).
|
||||
- **Secure-by-default.** Pack metadata must capture provenance, signatures, RBAC requirements, and secret usage; execution enforces tenant scopes and approvals.
|
||||
- **Portable.** Packs are distributed as signed OCI artifacts or tarballs that work in connected and air-gapped deployments, including Offline Kit mirrors.
|
||||
- **Composable.** Packs can reference reusable steps, expressions, and shared libraries without sacrificing determinism or auditability.
|
||||
|
||||
Non-goals: full-blown workflow orchestration, unbounded scripting, or remote code injection. All logic is declarative and constrained to Task Runner capabilities.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Terminology
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Pack manifest** | Primary YAML document (`pack.yaml`) describing metadata, inputs, steps, policies, and evidence expectations. |
|
||||
| **Step** | Atomic unit of work executed by Task Runner (e.g., command, API call, policy gate, approval). Steps can be sequential or parallel. |
|
||||
| **Expression** | Deterministic evaluation (JMESPath-like) used for branching, templating, and conditionals. |
|
||||
| **Policy gate** | Declarative rule that blocks execution until conditions are met (e.g., approval recorded, external signal received). |
|
||||
| **Artifact** | File, JSON blob, or OCI object produced by a step, referenced in manifests and evidence bundles. |
|
||||
| **Pack bundle** | Distribution archive (`.stella-pack.tgz` or OCI ref) containing manifest, assets, schemas, and provenance metadata. |
|
||||
|
||||
---
|
||||
|
||||
## 3 · Pack Layout
|
||||
|
||||
```
|
||||
my-pack/
|
||||
├─ pack.yaml # Required manifest
|
||||
├─ assets/ # Optional static assets (scripts, templates)
|
||||
├─ schemas/ # JSON schemas for inputs/outputs
|
||||
├─ docs/ # Markdown docs rendered in Console/CLI help
|
||||
├─ provenance/ # DSSE statements, SBOM, attestations
|
||||
└─ README.md # Author-facing summary (optional)
|
||||
```
|
||||
|
||||
Publishing via Packs Registry or OCI ensures the directory is canonical and hashed.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Manifest Schema (v1.0)
|
||||
|
||||
```yaml
|
||||
apiVersion: stellaops.io/pack.v1
|
||||
kind: TaskPack
|
||||
metadata:
|
||||
name: sbom-remediation
|
||||
version: 1.3.0
|
||||
description: >
|
||||
Audit SBOM drift, quiet high-risk findings, and export mitigation evidence.
|
||||
tags: [sbom, remediation, policy]
|
||||
tenantVisibility: ["west-prod", "east-stage"] # optional allowlist
|
||||
maintainers:
|
||||
- name: Jane Doe
|
||||
email: jane@example.com
|
||||
license: AGPL-3.0-or-later
|
||||
annotations:
|
||||
imposedRuleReminder: true
|
||||
|
||||
spec:
|
||||
inputs:
|
||||
- name: sbomBundle
|
||||
type: object
|
||||
schema: schemas/sbom-bundle.schema.json
|
||||
required: true
|
||||
- name: dryRun
|
||||
type: boolean
|
||||
default: false
|
||||
secrets:
|
||||
- name: jiraToken
|
||||
scope: Packs.Run # Authority scope required
|
||||
description: Optional token for ticket automation
|
||||
approvals:
|
||||
- id: security-review
|
||||
grants: ["Packs.Approve"]
|
||||
expiresAfter: PT4H
|
||||
reasonTemplate: "Approve remediation for SBOM {{ inputs.sbomBundle.metadata.image }}"
|
||||
steps:
|
||||
- id: validate-input
|
||||
run:
|
||||
uses: builtin:validate-schema
|
||||
with:
|
||||
target: "{{ inputs.sbomBundle }}"
|
||||
schema: schemas/sbom-bundle.schema.json
|
||||
- id: plan-remediation
|
||||
when: "{{ not inputs.dryRun }}"
|
||||
run:
|
||||
uses: builtin:policy-simulate
|
||||
with:
|
||||
sbom: "{{ inputs.sbomBundle }}"
|
||||
policy: "policies/remediation.yaml"
|
||||
- id: approval-gate
|
||||
gate:
|
||||
approval: security-review
|
||||
message: "Security must approve remediation before changes apply."
|
||||
- id: apply-remediation
|
||||
run:
|
||||
uses: builtin:cli-command
|
||||
with:
|
||||
command: ["stella", "policy", "promote", "--from-pack"]
|
||||
- id: export-evidence
|
||||
run:
|
||||
uses: builtin:evidence-export
|
||||
with:
|
||||
includeArtifacts: ["{{ steps.plan-remediation.outputs.planPath }}"]
|
||||
outputs:
|
||||
- name: evidenceBundle
|
||||
type: file
|
||||
path: "{{ steps.export-evidence.outputs.bundlePath }}"
|
||||
success:
|
||||
message: "Remediation applied; evidence bundle ready."
|
||||
failure:
|
||||
retries:
|
||||
maxAttempts: 1
|
||||
backoffSeconds: 0
|
||||
message: "Remediation failed; see evidence bundle for context."
|
||||
```
|
||||
|
||||
### 4.1 Field Summary
|
||||
|
||||
| Field | Description | Requirements |
|
||||
|-------|-------------|--------------|
|
||||
| `metadata` | Human-facing metadata; used for registry listings and RBAC hints. | `name` (DNS-1123), `version` (SemVer), `description` ≤ 2048 chars. |
|
||||
| `spec.inputs` | Declarative inputs validated at plan time. | Must include type; custom schema optional but recommended. |
|
||||
| `spec.secrets` | Secrets requested at runtime; never stored in pack bundle. | Each secret references Authority scope; CLI prompts or injects from profiles. |
|
||||
| `spec.approvals` | Named approval gates with required grants and TTL. | ID unique per pack; `grants` map to Authority roles. |
|
||||
| `spec.steps` | Execution graph; each step is `run`, `gate`, `parallel`, or `map`. | Steps must declare deterministic `uses` module and `id`. |
|
||||
| `spec.outputs` | Declared artifacts for downstream automation. | `type` can be `file`, `object`, or `url`; path/expression required. |
|
||||
| `success` / `failure` | Messages + retry policy. | `failure.retries.maxAttempts` + `backoffSeconds` default to 0. |
|
||||
|
||||
---
|
||||
|
||||
## 5 · Step Types
|
||||
|
||||
| Type | Schema | Notes |
|
||||
|------|--------|-------|
|
||||
| `run` | Executes a built-in module (`builtin:*`) or registry-provided module. | Modules must be deterministic, side-effect constrained, and versioned. |
|
||||
| `parallel` | Executes sub-steps concurrently; `maxParallel` optional. | Results aggregated; failures trigger abort unless `continueOnError`. |
|
||||
| `map` | Iterates over deterministic list; each iteration spawns sub-step. | Sequence derived from expression result; ordering stable. |
|
||||
| `gate.approval` | Blocks until approval recorded with required grants. | Supports `autoExpire` to cancel run on timeout. |
|
||||
| `gate.policy` | Calls Policy Engine to ensure criteria met (e.g., no critical findings). | Fails run if gate not satisfied. |
|
||||
|
||||
`when` expressions must be pure (no side effects) and rely only on declared inputs or prior outputs.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Determinism & Validation
|
||||
|
||||
1. **Plan phase** (`stella pack plan`, `TaskRunner.Plan` API) parses manifest, resolves expressions, validates schemas, and emits canonical graph with hash.
|
||||
2. **Simulation** compares plan vs dry-run results, capturing differences in `planDiff`. Required for approvals in sealed environments.
|
||||
3. **Execution** uses plan hash to ensure runtime graph matches simulation. Divergence aborts run.
|
||||
4. **Evidence**: Task Runner emits DSSE attestation referencing plan hash, input digests, and output artifacts.
|
||||
|
||||
Validation pipeline:
|
||||
|
||||
```text
|
||||
pack.yaml ──▶ schema validation ──▶ expression audit ──▶ determinism guard ──▶ signing
|
||||
```
|
||||
|
||||
Packs must pass CLI validation before publishing.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Signatures & Provenance
|
||||
|
||||
- Pack bundles are signed with **cosign** (keyless Fulcio/KMS supported) and optionally DSSE envelopes.
|
||||
- `provenance/` directory stores signed statements (SLSA Build L1+) linking source repo, CI run, and manifest hash.
|
||||
- Registry verifies signatures on push/pull; Task Runner refuses unsigned packs unless in development mode.
|
||||
- Attestations include:
|
||||
- Pack manifest digest (`sha256`)
|
||||
- Pack bundle digest
|
||||
- Build metadata (`git.ref`, `ci.workflow`, `cli.version`)
|
||||
|
||||
---
|
||||
|
||||
## 8 · RBAC & Scopes
|
||||
|
||||
Authority scopes introduced by `AUTH-PACKS-41-001`:
|
||||
|
||||
| Scope | Purpose |
|
||||
|-------|---------|
|
||||
| `Packs.Read` | Discover packs, download manifests. |
|
||||
| `Packs.Write` | Publish/update packs in registry (requires signature). |
|
||||
| `Packs.Run` | Execute packs via CLI/Task Runner. |
|
||||
| `Packs.Approve` | Fulfil approval gates defined in packs. |
|
||||
|
||||
Task Runner enforces scopes per tenant; pack metadata may further restrict tenant visibility (`metadata.tenantVisibility`).
|
||||
|
||||
---
|
||||
|
||||
## 9 · Observability & Evidence
|
||||
|
||||
- Metrics: `pack_run_duration_seconds`, `pack_step_retry_total`, `pack_gate_wait_seconds`.
|
||||
- Logs: Structured JSON per step with scrubbed inputs (`secretMask` applied).
|
||||
- Timeline events: `pack.started`, `pack.approval.requested`, `pack.approval.granted`, `pack.completed`.
|
||||
- Evidence bundle includes:
|
||||
- Plan manifest (canonical JSON)
|
||||
- Step transcripts (redacted)
|
||||
- Artifacts manifest (sha256, size)
|
||||
- Attestations references
|
||||
|
||||
---
|
||||
|
||||
## 10 · Compatibility Matrix
|
||||
|
||||
| CLI Version | Pack API | Task Runner | Notes |
|
||||
|-------------|----------|-------------|-------|
|
||||
| 2025.10.x | `pack.v1` | Runner build `>=2025.10.0` | Approvals optional, loops disabled. |
|
||||
| 2025.12.x | `pack.v1` | Runner build `>=2025.12.0` | Approvals resume, secrets injection, localization strings. |
|
||||
| Future | `pack.v2` | TBD | Will introduce typed outputs & partial replay (track in Epic 13). |
|
||||
|
||||
CLI enforces compatibility: running pack with unsupported features yields `ERR_PACK_UNSUPPORTED`.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Publishing Workflow
|
||||
|
||||
1. Author pack (`pack.yaml`, assets, docs).
|
||||
2. Run `stella pack validate` (schema + determinism).
|
||||
3. Generate bundle: `stella pack build --output my-pack.stella-pack.tgz`.
|
||||
4. Sign: `cosign sign-blob my-pack.stella-pack.tgz`.
|
||||
5. Publish: `stella pack push registry.example.com/org/my-pack:1.3.0`.
|
||||
6. Registry verifies signature, records provenance, and exposes pack via API.
|
||||
|
||||
---
|
||||
|
||||
## 12 · Compliance Checklist
|
||||
|
||||
- [ ] Manifest schema documented for all fields, including approvals, secrets, and outputs.
|
||||
- [ ] Determinism requirements outlined with plan/simulate semantics and CLI validation steps.
|
||||
- [ ] Signing + provenance expectations spelled out with cosign/DSSE references.
|
||||
- [ ] RBAC scopes (`Packs.*`) and tenant visibility rules captured.
|
||||
- [ ] Observability (metrics, logs, evidence) described for Task Runner integrations.
|
||||
- [ ] Compatibility matrix enumerates CLI/Runner requirements.
|
||||
- [ ] Publishing workflow documented with CLI commands.
|
||||
- [ ] Imposed rule reminder included at top of document.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-27 (Sprint 43).*
|
||||
|
||||
@@ -13,12 +13,13 @@ The Policies workspace centralises authoring, simulation, review, and promotion
|
||||
- `/console/policies` (list)
|
||||
- `/console/policies/:policyId` (details)
|
||||
- `/console/policies/:policyId/:revision` (editor, approvals, runs)
|
||||
- **Scopes:**
|
||||
- **Scopes / roles:**
|
||||
- `policy:read` (list and details)
|
||||
- `policy:write` (edit drafts, run lint/compile)
|
||||
- `policy:submit`, `policy:review`, `policy:approve` (workflow actions)
|
||||
- `policy:runs` (view run history)
|
||||
- `policy:author` (edit drafts, run lint/compile)
|
||||
- `policy:review`, `policy:approve` (workflow actions)
|
||||
- `policy:operate` (promotions, run orchestration)
|
||||
- `policy:simulate` (run simulations)
|
||||
- `policy:audit` (download audit bundles)
|
||||
- `effective:write` (promotion visibility only; actual write remains server-side)
|
||||
- **Feature flags:** `policy.studio.enabled`, `policy.simulation.diff`, `policy.runCharts.enabled`, `policy.offline.bundleUpload`.
|
||||
- **Dependencies:** Policy Engine v2 APIs (`/policies`, `/policy/runs`, `/policy/simulations`), Policy Studio Monaco assets, Authority fresh-auth flows for critical operations.
|
||||
@@ -112,10 +113,11 @@ The editor view reuses the structure documented in `/docs/ui/policy-editor.md` a
|
||||
|
||||
| Role | Scopes | Capabilities |
|
||||
|------|--------|--------------|
|
||||
| **Author** | `policy:read`, `policy:write`, `policy:simulate` | Create drafts, run lint/simulations, comment. |
|
||||
| **Author** | `policy:read`, `policy:author`, `policy:simulate` | Create drafts, run lint/simulations, comment. |
|
||||
| **Reviewer** | `policy:read`, `policy:review`, `policy:simulate` | Leave review comments, request changes. |
|
||||
| **Approver** | `policy:read`, `policy:approve`, `policy:runs`, `policy:simulate` | Approve/promote, trigger runs, view run history. |
|
||||
| **Operator** | `policy:read`, `policy:runs`, `policy:simulate`, `effective:write` | Schedule promotions, monitor runs (no editing). |
|
||||
| **Approver** | `policy:read`, `policy:approve`, `policy:operate`, `policy:simulate` | Approve/promote, trigger runs, view run history. |
|
||||
| **Operator** | `policy:read`, `policy:operate`, `policy:simulate`, `effective:write` | Schedule promotions, monitor runs (no editing). |
|
||||
| **Auditor** | `policy:read`, `policy:audit`, `policy:simulate` | View immutable history, export audit bundles. |
|
||||
| **Admin** | Above plus Authority admin scopes | Manage roles, configure escalation chains. |
|
||||
|
||||
UI disables controls not allowed by current scope and surfaces tooltip with required scope names. Audit log captures denied attempts (`policy.ui.action_denied`).
|
||||
@@ -188,4 +190,3 @@ UI disables controls not allowed by current scope and surfaces tooltip with requ
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 23).*
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ The Policy Editor is the primary Console workspace for composing, simulating, an
|
||||
## 1 · Access & Prerequisites
|
||||
|
||||
- **Routes:** `/console/policy` (list) → `/console/policy/:policyId/:version?`.
|
||||
- **Scopes:**
|
||||
- `policy:write` to edit drafts, run lint/compile, attach simulations.
|
||||
- `policy:submit` / `policy:review` / `policy:approve` for workflow actions.
|
||||
- `policy:run` to trigger runs, `policy:runs` to inspect history.
|
||||
- `findings:read` to open explain drawers.
|
||||
- **Scopes / roles:**
|
||||
- `policy:author` (role `policy-author`) to edit drafts, run lint/compile, and execute quick simulations.
|
||||
- `policy:review` (role `policy-reviewer`) to review drafts, leave comments, and request changes.
|
||||
- `policy:approve` (role `policy-approver`) to approve or reject submissions.
|
||||
- `policy:operate` (role `policy-operator`) to trigger batch simulations, promotions, and canary runs.
|
||||
- `policy:audit` (role `policy-auditor`) to access immutable history and audit exports.
|
||||
- `policy:simulate` to run simulations from Console; `findings:read` to open explain drawers.
|
||||
- **Feature flags:** `policyStudio.enabled` (defaults true once Policy Engine v2 API available).
|
||||
- **Browser support:** Evergreen Chrome, Edge, Firefox, Safari (last two versions). Uses WASM OPA sandbox; ensure COOP/COEP enabled per [UI architecture](../ARCHITECTURE_UI.md).
|
||||
|
||||
@@ -175,4 +177,3 @@ The Policy Editor is the primary Console workspace for composing, simulating, an
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
|
||||
48
docs/updates/2025-10-27-console-security-signoff.md
Normal file
48
docs/updates/2025-10-27-console-security-signoff.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Console Security Checklist Sign-off — 2025-10-27
|
||||
|
||||
## Summary
|
||||
|
||||
- Security Guild completed the console security compliance checklist from [`docs/security/console-security.md`](../security/console-security.md) against the Sprint 23 build.
|
||||
- No blocking findings. One observability note (raise Grafana burn-rate alert to SLO board) was addressed during the run; no follow-up tickets required.
|
||||
- Result: **PASS** – console may progress with Sprint 23 release gating.
|
||||
|
||||
## Authority client validation
|
||||
|
||||
- Ran `stella authority clients show console-ui` in staging; confirmed `pkce.enforced=true`, `dpop.required=true`, and `claim.requireTenant=true`.
|
||||
- Verified scope bundle matches §3 (baseline `ui.read`, admin set, and per-feature scopes). Results archived under `ops/evidence/console-ui-client-2025-10-27.json`.
|
||||
|
||||
## CSP enforcement
|
||||
|
||||
- Inspected rendered response headers via `curl -I https://console.stg.stellaops.local/` – CSP matches §4 defaults (`default-src 'self'`, `connect-src 'self' https://*.internal`), HSTS + Referrer-Policy present.
|
||||
- Helm overrides reviewed (`deploy/helm/stellaops/values-prod.yaml`); no extra origins declared.
|
||||
|
||||
## Fresh-auth timer
|
||||
|
||||
- Executed Playwright admin flow: promoted policy revisions twice; observed fresh-auth modal after 5 minutes idle.
|
||||
- Authority audit feed shows `authority.fresh_auth.success` and `authority.policy.promote` entries sharing correlation IDs.
|
||||
|
||||
## DPoP binding test
|
||||
|
||||
- Replayed captured bearer token without DPoP proof; Gateway returned `401` and incremented `ui_dpop_failure_total`.
|
||||
- Confirmed logs contain `ui.security.anomaly` event with matching `traceId`.
|
||||
|
||||
## Offline mode exercise
|
||||
|
||||
- Deployed console with `console.offlineMode=true`; Offline banner rendered, SSE disabled, CLI guidance surfaced on runs/downloads pages.
|
||||
- Imported Offline Kit manifest; parity checks report `OK` status.
|
||||
|
||||
## Evidence parity
|
||||
|
||||
- Downloaded run evidence bundle via UI, re-exported via CLI `stella runs export --run <id>`; SHA-256 digests match.
|
||||
- Verified Downloads workspace never caches bundle contents (only manifest metadata stored).
|
||||
|
||||
## Monitoring & alerts
|
||||
|
||||
- Grafana board `console-security.json` linked to alerts: `ui_request_duration_seconds` burn-rate, DPoP failure count, downloads manifest verification failures.
|
||||
- PagerDuty playbook references `docs/security/console-security.md` §6 for incident steps.
|
||||
|
||||
## Sign-off
|
||||
|
||||
- Reviewed by **Security Guild** (lead: `@sec-lfox`).
|
||||
- Sign-off recorded in Sprint 23 tracker (`SPRINTS.md`, `DOCS-CONSOLE-23-018`).
|
||||
|
||||
15
docs/updates/2025-10-27-orch-operator-scope.md
Normal file
15
docs/updates/2025-10-27-orch-operator-scope.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 2025-10-27 — Orchestrator operator scope & audit metadata
|
||||
|
||||
## Summary
|
||||
|
||||
- Introduced the `orch:operate` scope and `Orch.Operator` role in Authority to unlock Orchestrator control actions while keeping read-only access under `Orch.Viewer`.
|
||||
- Authority now enforces `operator_reason` and `operator_ticket` parameters on `/token` requests that include `orch:operate`; missing values yield `invalid_request` and no token is issued.
|
||||
- Client credentials audit events capture both fields (`request.reason`, `request.ticket`), giving SecOps traceability for every control action.
|
||||
|
||||
## Next steps
|
||||
|
||||
| Team | Follow-up | Target |
|
||||
|------|-----------|--------|
|
||||
| Console Guild | Wire UI control panels to request `operator_reason`/`operator_ticket` when exchanging tokens for orchestrator actions. | Sprint 23 stand-up |
|
||||
| CLI Guild | Add flags to `stella orch` subcommands to pass reason/ticket metadata before enabling mutations. | Sprint 23 stand-up |
|
||||
| Orchestrator Service | Enforce presence of `X-Stella-Reason`/`X-Stella-Ticket` (or equivalent metadata) on mutate endpoints and align audit logging. | ORCH-SVC-33-001 implementation |
|
||||
15
docs/updates/2025-10-27-policy-scope-migration.md
Normal file
15
docs/updates/2025-10-27-policy-scope-migration.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 2025-10-27 — Policy scope migration guidance
|
||||
|
||||
## Summary
|
||||
|
||||
- Updated Authority defaults (`etc/authority.yaml`) to register a `policy-cli` client using the fine-grained scope set introduced by AUTH-POLICY-23-001 (`policy:read`, `policy:author`, `policy:review`, `policy:simulate`, `findings:read`).
|
||||
- Added release/CI documentation call-outs instructing operators to reissue tokens that previously relied on `policy:write`/`policy:submit`/`policy:run` scopes.
|
||||
- Introduced a repo verification script so future config changes fail CI when policy clients regress to the legacy scope bundles.
|
||||
|
||||
## Next steps
|
||||
|
||||
| Team | Follow-up | Target |
|
||||
|------|-----------|--------|
|
||||
| Authority Core | Rotate long-lived policy CLI tokens in staging to confirm new scope set before freezing release 2025.10. | 2025-10-29 |
|
||||
| DevOps Guild | Update automation secrets (CI/CD, offline kit) to point at the regenerated `policy-cli` credentials. | Sprint 23 stand-up |
|
||||
| Docs Guild | Fold the broader scope matrix refresh into AUTH-POLICY-23-003 once the dual-approval workflow lands. | Blocked on AUTH-POLICY-23-002 |
|
||||
15
docs/updates/2025-10-27-task-packs-docs.md
Normal file
15
docs/updates/2025-10-27-task-packs-docs.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Docs Guild Update — Task Pack Docs (2025-10-27)
|
||||
|
||||
- Added Task Pack core documentation set:
|
||||
- `/docs/task-packs/spec.md`
|
||||
- `/docs/task-packs/authoring-guide.md`
|
||||
- `/docs/task-packs/registry.md`
|
||||
- `/docs/task-packs/runbook.md`
|
||||
- `/docs/security/pack-signing-and-rbac.md`
|
||||
- `/docs/operations/cli-release-and-packaging.md`
|
||||
- Each doc includes imposed-rule reminder, compliance checklist, and cross-links to Task Runner, Packs Registry, CLI release tasks.
|
||||
- Created asset staging instructions at `docs/assets/ui/tours/README.md` (shared with CLI enablement).
|
||||
- Circulated spec + authoring guide links to Task Runner, Packs Registry, Authority, and DevOps guild channels for technical review (2025-10-27). Target follow-up review once CLI parity tasks (`CLI-PACKS-42-001`, `CLI-PACKS-43-001`) land; tentative sync held for 2025-11-03 (Docs Guild to confirm).
|
||||
- Sprint tracker `DOCS-PACKS-43-001` marked DOING→DONE; follow-up reviews scheduled with Task Runner and Security guilds.
|
||||
|
||||
Artifacts: [Spec](../task-packs/spec.md), [Authoring guide](../task-packs/authoring-guide.md), [Registry](../task-packs/registry.md), [Runbook](../task-packs/runbook.md), [Signing/RBAC](../security/pack-signing-and-rbac.md), [CLI release runbook](../operations/cli-release-and-packaging.md).
|
||||
9
docs/updates/2025-10-29-export-center-provenance.md
Normal file
9
docs/updates/2025-10-29-export-center-provenance.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 2025-10-29 – Export Center provenance/signing doc
|
||||
|
||||
## Summary
|
||||
- Authored `docs/export-center/provenance-and-signing.md`, covering manifest/provenance artefacts, cosign/SLSA signing pipeline, verification workflows (CLI/CI/offline), and compliance checklist.
|
||||
- Cross-linked the new guide from the docs index (`docs/README.md`) and referenced outstanding CLI automation (`CLI-EXPORT-37-001`) to keep verification guidance aligned with upcoming tooling.
|
||||
|
||||
## Follow-ups
|
||||
- [ ] Revisit once `CLI-EXPORT-37-001` lands to confirm command names/flags and update the verification section if necessary.
|
||||
- [ ] Sync with DevOps (`DEVOPS-EXPORT-37-001`) after dashboards/alerts ship to embed direct links in the failure handling section.
|
||||
10
docs/updates/2025-10-29-notify-docs.md
Normal file
10
docs/updates/2025-10-29-notify-docs.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 2025-10-29 – Notifications Studio docs sync prep
|
||||
|
||||
## Summary
|
||||
- Published Notifications Studio overview (`notifications/overview.md`) and architecture dossier (`notifications/architecture.md`), complementing the rules/templates/digests deep dives landed earlier in Sprint 39.
|
||||
- Captured action items to validate connector metadata, quiet-hours semantics, and simulation endpoints once `NOTIFY-SVC-39-001..004` merge.
|
||||
- Alerted Notifications Service Guild that documentation handoff is pending those feature drops; ready to iterate as soon as the implementation surfaces schemas.
|
||||
|
||||
## Follow-ups
|
||||
- [ ] Review merged notifier correlation/quiet-hours work (`NOTIFY-SVC-39-001..004`) and refresh overview + architecture docs with any new persistence/API details.
|
||||
- [ ] Coordinate with DevOps dashboards work (`DEVOPS-NOTIFY-39-002`) to document alert references once metrics names are finalised.
|
||||
21
docs/updates/2025-10-29-scheduler-policy-doc-refresh.md
Normal file
21
docs/updates/2025-10-29-scheduler-policy-doc-refresh.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 2025-10-29 — Scheduler/Policy Guild Doc Refresh
|
||||
|
||||
## Summary
|
||||
- Extended `SCHED-MODELS-20-001` with environment metadata guidance, lifecycle semantics, and diff payload breakdown for Policy Engine runs.
|
||||
- Confirmed `StellaOps.Scheduler.Models.Tests` to keep sample fixtures in sync with the documentation.
|
||||
- Ready for distribution to Scheduler (Models/Worker/WebService) and Policy Engine guilds; link this update when posting to internal channels.
|
||||
|
||||
## Suggested announcement
|
||||
> **Channel(s):** `#scheduler-guild`, `#policy-engine`
|
||||
> **Message:**
|
||||
> ```
|
||||
> Policy Engine run DTO docs just picked up a refresh (environment metadata, lifecycle+retry table, diff payload notes).
|
||||
> • Doc: src/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md
|
||||
> • Samples: samples/api/scheduler/policy-*.json
|
||||
> • Tests: dotnet test src/StellaOps.Scheduler.Models.Tests
|
||||
> Please review for orchestration + API consumer work; ping back if other fields need coverage.
|
||||
> ```
|
||||
|
||||
## Follow-up
|
||||
- [ ] Confirm both guilds acknowledge the documentation update.
|
||||
- [ ] Fold any feedback into Sprint 21 scheduler schema backlog if additional DTO changes are requested.
|
||||
12
docs/updates/2025-10-31-console-security-refresh.md
Normal file
12
docs/updates/2025-10-31-console-security-refresh.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 2025-10-31 — Console Security Docs Refresh
|
||||
|
||||
## Summary
|
||||
- Documented the new Authority `/console` endpoints (`/tenants`, `/profile`, `/token/introspect`) including tenant header enforcement, DPoP requirements, and five-minute fresh-auth behaviour.
|
||||
- Reduced the default Authority access-token lifetime to 120 seconds to match OpTok guidance and updated tests accordingly.
|
||||
- Updated Console security guidance to cover the newly issued `orch:read` scope and clarified session inactivity expectations.
|
||||
- Annotated `authority.yaml.sample` and the Authority ops runbook so operators forward `X-Stella-Tenant` and understand fresh-auth prompts.
|
||||
|
||||
## Impact
|
||||
- Console release notes now reference the dedicated `/console` endpoints and their audit identifiers.
|
||||
- Security Guild can rely on the updated compliance checklist when executing Sprint 23 sign-off.
|
||||
- Deployment teams have explicit configuration reminders for tenants and orchestrator dashboard access.
|
||||
@@ -6,7 +6,7 @@ schemaVersion: 1
|
||||
|
||||
issuer: "https://authority.localtest.me"
|
||||
|
||||
accessTokenLifetime: "00:15:00"
|
||||
accessTokenLifetime: "00:02:00"
|
||||
refreshTokenLifetime: "30.00:00:00"
|
||||
identityTokenLifetime: "00:05:00"
|
||||
authorizationCodeLifetime: "00:05:00"
|
||||
@@ -61,6 +61,17 @@ clients:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/policy-engine.secret"
|
||||
|
||||
- clientId: "policy-cli"
|
||||
displayName: "Policy Automation CLI"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://policy-engine" ]
|
||||
scopes: [ "policy:read", "policy:author", "policy:review", "policy:simulate", "findings:read" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/policy-cli.secret"
|
||||
|
||||
- clientId: "cartographer-service"
|
||||
displayName: "Cartographer Service"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
@@ -84,6 +95,26 @@ clients:
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/graph-api.secret"
|
||||
- clientId: "export-center-operator"
|
||||
displayName: "Export Center Operator"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://export-center" ]
|
||||
scopes: [ "export.viewer", "export.operator" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/export-center-operator.secret"
|
||||
- clientId: "export-center-admin"
|
||||
displayName: "Export Center Admin"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://export-center" ]
|
||||
scopes: [ "export.viewer", "export.operator", "export.admin" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/export-center-admin.secret"
|
||||
|
||||
- clientId: "concelier-ingest"
|
||||
displayName: "Concelier Ingestion"
|
||||
@@ -118,6 +149,30 @@ clients:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/graph-api-cli.secret"
|
||||
|
||||
tenants:
|
||||
- name: "tenant-default"
|
||||
roles:
|
||||
orch-viewer:
|
||||
scopes: [ "orch:read" ]
|
||||
orch-operator:
|
||||
scopes: [ "orch:read", "orch:operate" ]
|
||||
export-viewer:
|
||||
scopes: [ "export.viewer" ]
|
||||
export-operator:
|
||||
scopes: [ "export.viewer", "export.operator" ]
|
||||
export-admin:
|
||||
scopes: [ "export.viewer", "export.operator", "export.admin" ]
|
||||
policy-author:
|
||||
scopes: [ "policy:author", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-reviewer:
|
||||
scopes: [ "policy:review", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-approver:
|
||||
scopes: [ "policy:approve", "policy:review", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-operator:
|
||||
scopes: [ "policy:operate", "policy:run", "policy:activate", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-auditor:
|
||||
scopes: [ "policy:audit", "policy:read", "policy:simulate", "findings:read" ]
|
||||
|
||||
security:
|
||||
rateLimiting:
|
||||
token:
|
||||
|
||||
@@ -11,7 +11,7 @@ schemaVersion: 1
|
||||
issuer: "https://authority.stella-ops.local"
|
||||
|
||||
# Token lifetimes expressed as HH:MM:SS or DD.HH:MM:SS.
|
||||
accessTokenLifetime: "00:15:00"
|
||||
accessTokenLifetime: "00:02:00"
|
||||
refreshTokenLifetime: "30.00:00:00"
|
||||
identityTokenLifetime: "00:05:00"
|
||||
authorizationCodeLifetime: "00:05:00"
|
||||
@@ -151,12 +151,39 @@ clients:
|
||||
displayName: "Policy Automation CLI"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://policy-engine" ]
|
||||
scopes: [ "policy:write", "policy:submit", "policy:run", "findings:read" ]
|
||||
scopes: [ "policy:read", "policy:author", "policy:review", "policy:simulate", "findings:read" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/policy-cli.secret"
|
||||
- clientId: "exceptions-service"
|
||||
displayName: "Policy Engine Exceptions Worker"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://policy-engine" ]
|
||||
scopes: [ "exceptions:read", "exceptions:write" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/exceptions-service.secret"
|
||||
- clientId: "console-web"
|
||||
displayName: "StellaOps Console"
|
||||
grantTypes: [ "authorization_code", "refresh_token" ]
|
||||
audiences: [ "console" ]
|
||||
scopes: [ "openid", "profile", "email", "ui.read", "authority:tenants.read", "advisory:read", "vex:read", "exceptions:read", "exceptions:approve", "aoc:verify", "findings:read", "orch:read", "vuln:read" ]
|
||||
# exceptions:approve is elevated via fresh-auth and requires an MFA-capable identity provider.
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
redirectUris:
|
||||
- "https://console.stella-ops.local/oidc/callback"
|
||||
postLogoutRedirectUris:
|
||||
- "https://console.stella-ops.local/"
|
||||
# Gateway must forward X-Stella-Tenant for /console endpoints; fresh-auth window (300s)
|
||||
# returned by /console/profile governs admin actions in the Console UI.
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/console-web.secret"
|
||||
- clientId: "cartographer-service"
|
||||
displayName: "Cartographer Service"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
@@ -179,6 +206,26 @@ clients:
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/graph-api.secret"
|
||||
- clientId: "export-center-operator"
|
||||
displayName: "Export Center Operator"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://export-center" ]
|
||||
scopes: [ "export.viewer", "export.operator" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/export-center-operator.secret"
|
||||
- clientId: "export-center-admin"
|
||||
displayName: "Export Center Admin"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://export-center" ]
|
||||
scopes: [ "export.viewer", "export.operator", "export.admin" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/export-center-admin.secret"
|
||||
- clientId: "vuln-explorer-ui"
|
||||
displayName: "Vuln Explorer UI"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
@@ -189,6 +236,53 @@ clients:
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/vuln-explorer-ui.secret"
|
||||
# Signals sensors must request aoc:verify alongside write scope.
|
||||
- clientId: "signals-uploader"
|
||||
displayName: "Signals Sensor"
|
||||
grantTypes: [ "client_credentials" ]
|
||||
audiences: [ "api://signals" ]
|
||||
scopes: [ "signals:write", "signals:read", "aoc:verify" ]
|
||||
tenant: "tenant-default"
|
||||
senderConstraint: "dpop"
|
||||
auth:
|
||||
type: "client_secret"
|
||||
secretFile: "../secrets/signals-uploader.secret"
|
||||
|
||||
tenants:
|
||||
- name: "tenant-default"
|
||||
roles:
|
||||
orch-viewer:
|
||||
scopes: [ "orch:read" ]
|
||||
orch-operator:
|
||||
scopes: [ "orch:read", "orch:operate" ]
|
||||
policy-author:
|
||||
scopes: [ "policy:author", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-reviewer:
|
||||
scopes: [ "policy:review", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-approver:
|
||||
scopes: [ "policy:approve", "policy:review", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-operator:
|
||||
scopes: [ "policy:operate", "policy:run", "policy:activate", "policy:read", "policy:simulate", "findings:read" ]
|
||||
policy-auditor:
|
||||
scopes: [ "policy:audit", "policy:read", "policy:simulate", "findings:read" ]
|
||||
export-viewer:
|
||||
scopes: [ "export.viewer" ]
|
||||
export-operator:
|
||||
scopes: [ "export.viewer", "export.operator" ]
|
||||
export-admin:
|
||||
scopes: [ "export.viewer", "export.operator", "export.admin" ]
|
||||
|
||||
# Exception approval routing templates used by Policy Engine and Console.
|
||||
exceptions:
|
||||
routingTemplates:
|
||||
- id: "secops"
|
||||
authorityRouteId: "approvals/secops"
|
||||
requireMfa: true
|
||||
description: "Security Operations approval chain"
|
||||
- id: "governance"
|
||||
authorityRouteId: "approvals/governance"
|
||||
requireMfa: false
|
||||
description: "Governance review (non-production)"
|
||||
|
||||
# CIDR ranges that bypass network-sensitive policies (e.g. on-host cron jobs).
|
||||
# Keep the list tight: localhost is sufficient for most air-gapped installs.
|
||||
|
||||
39
etc/policy-gateway.yaml.sample
Normal file
39
etc/policy-gateway.yaml.sample
Normal file
@@ -0,0 +1,39 @@
|
||||
# StellaOps Policy Gateway configuration template.
|
||||
# Copy to ../etc/policy-gateway.yaml (relative to the gateway content root)
|
||||
# and adjust values to fit your environment. Environment variables prefixed with
|
||||
# STELLAOPS_POLICY_GATEWAY_ override these values at runtime.
|
||||
|
||||
schemaVersion: 1
|
||||
|
||||
telemetry:
|
||||
minimumLogLevel: Information
|
||||
|
||||
resourceServer:
|
||||
authority: "https://authority.stella-ops.local"
|
||||
metadataAddress: "https://authority.stella-ops.local/.well-known/openid-configuration"
|
||||
audiences: [ "api://policy-gateway" ]
|
||||
requiredScopes: [ "policy:read", "policy:author", "policy:review", "policy:approve", "policy:operate", "policy:simulate", "policy:run", "policy:activate" ]
|
||||
requiredTenants: [ ]
|
||||
bypassNetworks:
|
||||
- "127.0.0.1/32"
|
||||
- "::1/128"
|
||||
requireHttpsMetadata: true
|
||||
backchannelTimeoutSeconds: 30
|
||||
tokenClockSkewSeconds: 60
|
||||
|
||||
policyEngine:
|
||||
baseAddress: "https://policy-engine.stella-ops.local"
|
||||
audience: "api://policy-engine"
|
||||
clientCredentials:
|
||||
enabled: true
|
||||
clientId: "policy-gateway"
|
||||
clientSecret: "change-me"
|
||||
scopes: [ "policy:read", "policy:author", "policy:review", "policy:approve", "policy:operate", "policy:simulate", "policy:run", "policy:activate" ]
|
||||
backchannelTimeoutSeconds: 30
|
||||
dpop:
|
||||
enabled: false
|
||||
keyPath: "../etc/policy-gateway-dpop.pem"
|
||||
keyPassphrase: ""
|
||||
algorithm: "ES256"
|
||||
proofLifetime: "00:02:00"
|
||||
clockSkew: "00:00:30"
|
||||
2
etc/secrets/console-web.secret
Normal file
2
etc/secrets/console-web.secret
Normal file
@@ -0,0 +1,2 @@
|
||||
# replace with a strong shared secret for the console-web client
|
||||
console-web-secret-change-me
|
||||
2
etc/secrets/policy-cli.secret
Normal file
2
etc/secrets/policy-cli.secret
Normal file
@@ -0,0 +1,2 @@
|
||||
# generated 2025-10-27T21:55:11Z via scripts/rotate-policy-cli-secret.sh
|
||||
policy-cli-iOHhrE+K1sx+iyWQOd9pqYh0LwbXRauO/zdv0AeFUvLKAtZsc1wTIB5qZ8YIfKEo
|
||||
28
etc/signals.yaml.sample
Normal file
28
etc/signals.yaml.sample
Normal file
@@ -0,0 +1,28 @@
|
||||
# Signals service configuration template.
|
||||
# Copy to ../etc/signals.yaml (relative to the Signals content root)
|
||||
# and adjust values to fit your environment.
|
||||
|
||||
schemaVersion: 1
|
||||
|
||||
Signals:
|
||||
Authority:
|
||||
Enabled: true
|
||||
Issuer: "https://authority.stella-ops.local"
|
||||
AllowAnonymousFallback: false
|
||||
Audiences:
|
||||
- "api://signals"
|
||||
RequiredTenants:
|
||||
- "tenant-default"
|
||||
RequiredScopes:
|
||||
- "signals:read"
|
||||
- "signals:write"
|
||||
- "signals:admin"
|
||||
BypassNetworks:
|
||||
- "127.0.0.1/32"
|
||||
- "::1/128"
|
||||
Mongo:
|
||||
ConnectionString: "mongodb://localhost:27017/signals"
|
||||
Database: "signals"
|
||||
CallgraphsCollection: "callgraphs"
|
||||
Storage:
|
||||
RootPath: "../data/signals-artifacts"
|
||||
Binary file not shown.
BIN
local-nuget/Mongo2Go.3.1.3.nupkg
Normal file
BIN
local-nuget/Mongo2Go.3.1.3.nupkg
Normal file
Binary file not shown.
@@ -40,6 +40,8 @@ For additional options, see `etc/authority.yaml.sample`.
|
||||
|
||||
> **Graph Explorer reminder:** When enabling Cartographer or Graph API components, update `etc/authority.yaml` so the `cartographer-service` client includes `properties.serviceIdentity: "cartographer"` and a tenant hint. Authority now rejects `graph:write` tokens that lack this marker, so existing deployments must apply the update before rolling out the new build.
|
||||
|
||||
> **Console endpoint reminder:** The Console UI now calls `/console/tenants`, `/console/profile`, and `/console/token/introspect`. Reverse proxies must forward the `X-Stella-Tenant` header (derived from the access token) so Authority can enforce tenancy; audit events are logged under `authority.console.*`. Admin actions obey a five-minute fresh-auth window reported by `/console/profile`, so keep session timeout prompts aligned with that value.
|
||||
|
||||
## Key rotation automation (OPS3)
|
||||
|
||||
The `key-rotation.sh` helper wraps the `/internal/signing/rotate` endpoint delivered with CORE10. It can run in CI/CD once the new PEM key is staged on the Authority host volume.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DEVOPS-OPS-14-003 | DONE (2025-10-26) | Deployment Guild | DEVOPS-REL-14-001 | Document and script upgrade/rollback flows, channel management, and compatibility matrices per architecture. | Helm/Compose guides updated with digest pinning, automated checks committed, rollback drill recorded. |
|
||||
| DOWNLOADS-CONSOLE-23-001 | TODO | Deployment Guild, DevOps Guild | DEVOPS-CONSOLE-23-002 | Maintain signed downloads manifest pipeline (images, Helm, offline bundles), publish JSON under `deploy/downloads/manifest.json`, and document sync cadence for Console + docs parity. | Pipeline generates signed manifest with checksums, automated PR updates manifest, docs updated with sync workflow, parity check in CI passes. |
|
||||
| DEPLOY-NOTIFY-38-001 | BLOCKED (2025-10-29) | Deployment Guild, DevOps Guild | NOTIFY-SVC-38-001..004 | Package notifier API/worker Helm overlays (email/chat/webhook), secrets templates, rollout guide. | Overlays committed; smoke deploy executed; rollback steps recorded; secrets templates provided. |
|
||||
| DEPLOY-POLICY-27-001 | TODO | Deployment Guild, Policy Registry Guild | REGISTRY-API-27-001, DEVOPS-POLICY-27-003 | Produce Helm/Compose overlays for Policy Registry + simulation workers, including Mongo migrations, object storage buckets, signing key secrets, and tenancy defaults. | Overlays committed with deterministic digests; install docs updated; smoke deploy validated in staging. |
|
||||
| DEPLOY-POLICY-27-002 | TODO | Deployment Guild, Policy Guild | DEPLOY-POLICY-27-001, WEB-POLICY-27-004 | Document rollout/rollback playbooks for policy publish/promote (canary strategy, emergency freeze toggle, evidence retrieval) under `/docs/runbooks/policy-incident.md`. | Runbook published with decision tree; checklist appended; rehearsal recorded. |
|
||||
| DEPLOY-VULN-29-001 | TODO | Deployment Guild, Findings Ledger Guild | LEDGER-29-009 | Produce Helm/Compose overlays for Findings Ledger + projector, including DB migrations, Merkle anchor jobs, and scaling guidance. | Overlays committed; migrations documented; smoke deploy executed; rollback steps recorded. |
|
||||
@@ -12,7 +13,7 @@
|
||||
| DEPLOY-VEX-30-002 | TODO | Deployment Guild, Issuer Directory Guild | ISSUER-30-006 | Package Issuer Directory deployment manifests, backups, and security hardening guidance. | Deployment docs merged; backup tested; hardening checklist appended. |
|
||||
| DEPLOY-AIAI-31-001 | TODO | Deployment Guild, Advisory AI Guild | AIAI-31-008 | Provide Helm/Compose manifests, GPU toggle, scaling/runbook, and offline kit instructions for Advisory AI service + inference container. | Deployment docs merged; smoke deploy executed; offline kit updated; runbooks published. |
|
||||
| DEPLOY-ORCH-34-001 | TODO | Deployment Guild, Orchestrator Service Guild | ORCH-SVC-34-004 | Provide orchestrator Helm/Compose manifests, scaling defaults, secret templates, offline kit instructions, and GA rollout/rollback playbook. | Manifests committed with digests; scaling guidance documented; smoke deploy/rollback rehearsed; offline kit instructions updated. |
|
||||
| DEPLOY-EXPORT-35-001 | TODO | Deployment Guild, Exporter Service Guild | EXPORT-SVC-35-001..006 | Package exporter service/worker Helm overlays (download-only), document rollout/rollback, and integrate signing KMS secrets. | Overlays committed; smoke deploy executed; rollback steps recorded; secrets templates provided. |
|
||||
| DEPLOY-EXPORT-35-001 | BLOCKED (2025-10-29) | Deployment Guild, Exporter Service Guild | EXPORT-SVC-35-001..006 | Package exporter service/worker Helm overlays (download-only), document rollout/rollback, and integrate signing KMS secrets. | Overlays committed; smoke deploy executed; rollback steps recorded; secrets templates provided. |
|
||||
| DEPLOY-EXPORT-36-001 | TODO | Deployment Guild, Exporter Service Guild | DEPLOY-EXPORT-35-001, EXPORT-SVC-36-003 | Document OCI/object storage distribution workflows, registry credential automation, and monitoring hooks for exports. | Documentation merged; automation scripts validated; monitoring instructions added. |
|
||||
|
||||
## CLI Parity & Task Packs
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
> Blocked: waiting on CLI verifier command and Concelier/Excititor guard endpoints to land (CLI-AOC-19-002, CONCELIER-WEB-AOC-19-004, EXCITITOR-WEB-AOC-19-004).
|
||||
| DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | DevOps Guild, QA Guild | CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003 | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. | Coverage report includes guard projects, threshold gate passes/fails as expected, dashboards refreshed with new metrics. |
|
||||
> Blocked: guard coverage suites and exporter hooks pending in Concelier/Excititor (CONCELIER-WEB-AOC-19-003, EXCITITOR-WEB-AOC-19-003).
|
||||
| DEVOPS-AOC-19-101 | TODO (2025-10-28) | DevOps Guild, Concelier Storage Guild | CONCELIER-STORE-AOC-19-002 | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. | Runbook committed in `docs/deploy/containers.md` + Offline Kit notes, staging rehearsal scheduled with dependencies captured in SPRINTS. |
|
||||
| DEVOPS-OBS-50-001 | DONE (2025-10-26) | DevOps Guild, Observability Guild | TELEMETRY-OBS-50-001 | Deliver default OpenTelemetry collector deployment (Compose/Helm manifests), OTLP ingestion endpoints, and secure pipeline (authN, mTLS, tenant partitioning). Provide smoke test verifying traces/logs/metrics ingestion. | Collector manifests committed; smoke test green; docs updated; imposed rule banner reminder noted. |
|
||||
| DEVOPS-OBS-50-002 | DOING (2025-10-26) | DevOps Guild, Security Guild | DEVOPS-OBS-50-001, TELEMETRY-OBS-51-002 | Stand up multi-tenant storage backends (Prometheus, Tempo/Jaeger, Loki) with retention policies, tenant isolation, and redaction guard rails. Integrate with Authority scopes for read paths. | Storage stack deployed with auth; retention configured; integration tests verify tenant isolation; runbook drafted. |
|
||||
> Coordination started with Observability Guild (2025-10-26) to schedule staging rollout and provision service accounts. Staging bootstrap commands and secret names documented in `docs/ops/telemetry-storage.md`.
|
||||
@@ -114,8 +115,8 @@
|
||||
## Export Center
|
||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DEVOPS-EXPORT-35-001 | TODO | DevOps Guild, Exporter Service Guild | EXPORT-SVC-35-001..006 | Establish exporter CI pipeline (lint/test/perf smoke), configure object storage fixtures, seed Grafana dashboards, and document bootstrap steps. | CI pipeline running; smoke export job seeded; dashboards live; runbook updated. |
|
||||
| DEVOPS-EXPORT-36-001 | TODO | DevOps Guild, Exporter Service Guild | DEVOPS-EXPORT-35-001, EXPORT-SVC-36-001..004 | Integrate Trivy compatibility validation, OCI push smoke tests, and throughput/error dashboards. | CI executes Trivy validation; OCI push smoke passes; dashboards/alerts configured. |
|
||||
| DEVOPS-EXPORT-35-001 | BLOCKED (2025-10-29) | DevOps Guild, Exporter Service Guild | EXPORT-SVC-35-001..006 | Establish exporter CI pipeline (lint/test/perf smoke), configure object storage fixtures, seed Grafana dashboards, and document bootstrap steps. | CI pipeline running; smoke export job seeded; dashboards live; runbook updated. |
|
||||
| DEVOPS-EXPORT-36-001 | TODO | DevOps Guild, Exporter Service Guild | DEVOPS-EXPORT-35-001, EXPORT-SVC-36-001..004 | Integrate Trivy compatibility validation, cosign signature checks, `trivy module db import` smoke tests, OCI distribution verification, and throughput/error dashboards. | CI executes cosign + Trivy import validation; OCI push smoke passes; dashboards/alerts configured. |
|
||||
| DEVOPS-EXPORT-37-001 | TODO | DevOps Guild, Exporter Service Guild | DEVOPS-EXPORT-36-001, EXPORT-SVC-37-001..004 | Finalize exporter monitoring (failure alerts, verify metrics, retention jobs) and chaos/latency tests ahead of GA. | Alerts tuned; chaos tests documented; retention monitoring active; runbook updated. |
|
||||
|
||||
## CLI Parity & Task Packs
|
||||
@@ -124,7 +125,10 @@
|
||||
|----|--------|----------|------------|-------------|---------------|
|
||||
| DEVOPS-CLI-41-001 | TODO | DevOps Guild, DevEx/CLI Guild | CLI-CORE-41-001 | Establish CLI build pipeline (multi-platform binaries, SBOM, checksums), parity matrix CI enforcement, and release artifact signing. | Build pipeline operational; SBOM/checksums published; parity gate failing on drift; docs updated. |
|
||||
| DEVOPS-CLI-42-001 | TODO | DevOps Guild | DEVOPS-CLI-41-001, CLI-PARITY-41-001 | Add CLI golden output tests, parity diff automation, pack run CI harness, and artifact cache for remote mode. | Golden tests running; parity diff automation in CI; pack run harness executes sample packs; documentation updated. |
|
||||
| DEVOPS-CLI-43-001 | TODO | DevOps Guild | DEVOPS-CLI-42-001, TASKRUN-42-001 | Finalize multi-platform release automation, SBOM signing, parity gate enforcement, and Task Pack chaos tests. | Release automation verified; SBOM signed; parity gate enforced; chaos tests documented. |
|
||||
| DEVOPS-CLI-43-001 | DOING (2025-10-27) | DevOps Guild | DEVOPS-CLI-42-001, TASKRUN-42-001 | Finalize multi-platform release automation, SBOM signing, parity gate enforcement, and Task Pack chaos tests. | Release automation verified; SBOM signed; parity gate enforced; chaos tests documented. |
|
||||
> 2025-10-27: Release pipeline now packages CLI multi-platform artefacts with SBOM/signature coverage and enforces the CLI parity gate (`ops/devops/check_cli_parity.py`). Task Pack chaos smoke still pending CLI pack command delivery.
|
||||
| DEVOPS-CLI-43-002 | TODO | DevOps Guild, Task Runner Guild | CLI-PACKS-43-001, TASKRUN-43-001 | Implement Task Pack chaos smoke in CI (random failure injection, resume, sealed-mode toggle) and publish evidence bundles for review. | Chaos smoke job runs nightly; failures alert Slack; evidence stored in `out/pack-chaos`; runbook updated. |
|
||||
| DEVOPS-CLI-43-003 | TODO | DevOps Guild, DevEx/CLI Guild | CLI-PARITY-41-001, CLI-PACKS-42-001 | Integrate CLI golden output/parity diff automation into release gating; export parity report artifact consumed by Console Downloads workspace. | `check_cli_parity.py` wired to compare parity matrix and CLI outputs; artifact uploaded; release fails on regressions.
|
||||
|
||||
## Containerized Distribution (Epic 13)
|
||||
|
||||
|
||||
53
ops/devops/check_cli_parity.py
Normal file
53
ops/devops/check_cli_parity.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ensure CLI parity matrix contains no outstanding blockers before release."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||
PARITY_DOC = REPO_ROOT / "docs/cli-vs-ui-parity.md"
|
||||
|
||||
BLOCKERS = {
|
||||
"🟥": "blocking gap",
|
||||
"❌": "missing feature",
|
||||
"🚫": "unsupported",
|
||||
}
|
||||
WARNINGS = {
|
||||
"🟡": "partial support",
|
||||
"⚠️": "warning",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not PARITY_DOC.exists():
|
||||
print(f"❌ Parity matrix not found at {PARITY_DOC}", file=sys.stderr)
|
||||
return 1
|
||||
text = PARITY_DOC.read_text(encoding="utf-8")
|
||||
blockers: list[str] = []
|
||||
warnings: list[str] = []
|
||||
for line in text.splitlines():
|
||||
for symbol, label in BLOCKERS.items():
|
||||
if symbol in line:
|
||||
blockers.append(f"{label}: {line.strip()}")
|
||||
for symbol, label in WARNINGS.items():
|
||||
if symbol in line:
|
||||
warnings.append(f"{label}: {line.strip()}")
|
||||
if blockers:
|
||||
print("❌ CLI parity gate failed — blocking items present:", file=sys.stderr)
|
||||
for item in blockers:
|
||||
print(f" - {item}", file=sys.stderr)
|
||||
return 1
|
||||
if warnings:
|
||||
print("⚠️ CLI parity gate warnings detected:", file=sys.stderr)
|
||||
for item in warnings:
|
||||
print(f" - {item}", file=sys.stderr)
|
||||
print("Treat warnings as failures until parity matrix is fully green.", file=sys.stderr)
|
||||
return 1
|
||||
print("✅ CLI parity matrix has no blocking or warning entries.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,11 +23,13 @@ import pathlib
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import uuid
|
||||
import zipfile
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, Sequence, Tuple
|
||||
|
||||
@@ -190,6 +192,8 @@ class ReleaseBuilder:
|
||||
self.metadata_dir = ensure_directory(self.artifacts_dir / "metadata")
|
||||
self.debug_dir = ensure_directory(self.output_dir / "debug")
|
||||
self.debug_store_dir = ensure_directory(self.debug_dir / ".build-id")
|
||||
self.cli_config = config.get("cli")
|
||||
self.cli_output_dir = ensure_directory(self.output_dir / "cli") if self.cli_config else None
|
||||
self.temp_dir = pathlib.Path(tempfile.mkdtemp(prefix="stellaops-release-"))
|
||||
self.skip_signing = skip_signing
|
||||
self.tlog_upload = tlog_upload
|
||||
@@ -220,7 +224,8 @@ class ReleaseBuilder:
|
||||
helm_meta = self._package_helm()
|
||||
compose_meta = self._digest_compose_files()
|
||||
debug_meta = self._collect_debug_store(components_result)
|
||||
manifest = self._compose_manifest(components_result, helm_meta, compose_meta, debug_meta)
|
||||
cli_meta = self._build_cli_artifacts()
|
||||
manifest = self._compose_manifest(components_result, helm_meta, compose_meta, debug_meta, cli_meta)
|
||||
return manifest
|
||||
|
||||
def _prime_buildx_plugin(self) -> None:
|
||||
@@ -262,6 +267,12 @@ class ReleaseBuilder:
|
||||
def _component_ref(self, repo: str, digest: str) -> str:
|
||||
return f"{self.registry}/{repo}@{digest}"
|
||||
|
||||
def _relative_path(self, path: pathlib.Path) -> str:
|
||||
try:
|
||||
return str(path.relative_to(self.output_dir.parent))
|
||||
except ValueError:
|
||||
return str(path)
|
||||
|
||||
def _build_component(self, component: Mapping[str, Any]) -> Mapping[str, Any]:
|
||||
name = component["name"]
|
||||
repo = component.get("repository", name)
|
||||
@@ -601,6 +612,165 @@ class ReleaseBuilder:
|
||||
("directory", store_rel),
|
||||
))
|
||||
|
||||
# ----------------
|
||||
# CLI packaging
|
||||
# ----------------
|
||||
def _build_cli_artifacts(self) -> List[Mapping[str, Any]]:
|
||||
if not self.cli_config or self.dry_run:
|
||||
return []
|
||||
project_rel = self.cli_config.get("project")
|
||||
if not project_rel:
|
||||
return []
|
||||
project_path = (self.repo_root / project_rel).resolve()
|
||||
if not project_path.exists():
|
||||
raise FileNotFoundError(f"CLI project not found at {project_path}")
|
||||
runtimes: Sequence[str] = self.cli_config.get("runtimes", [])
|
||||
if not runtimes:
|
||||
runtimes = ("linux-x64",)
|
||||
package_prefix = self.cli_config.get("packagePrefix", "stella")
|
||||
ensure_directory(self.cli_output_dir or (self.output_dir / "cli"))
|
||||
|
||||
cli_entries: List[Mapping[str, Any]] = []
|
||||
for runtime in runtimes:
|
||||
entry = self._build_cli_for_runtime(project_path, runtime, package_prefix)
|
||||
cli_entries.append(entry)
|
||||
return cli_entries
|
||||
|
||||
def _build_cli_for_runtime(
|
||||
self,
|
||||
project_path: pathlib.Path,
|
||||
runtime: str,
|
||||
package_prefix: str,
|
||||
) -> Mapping[str, Any]:
|
||||
publish_dir = ensure_directory(self.temp_dir / f"cli-publish-{runtime}")
|
||||
publish_cmd = [
|
||||
"dotnet",
|
||||
"publish",
|
||||
str(project_path),
|
||||
"--configuration",
|
||||
"Release",
|
||||
"--runtime",
|
||||
runtime,
|
||||
"--self-contained",
|
||||
"true",
|
||||
"/p:PublishSingleFile=true",
|
||||
"/p:IncludeNativeLibrariesForSelfExtract=true",
|
||||
"/p:EnableCompressionInSingleFile=true",
|
||||
"/p:InvariantGlobalization=true",
|
||||
"--output",
|
||||
str(publish_dir),
|
||||
]
|
||||
run(publish_cmd, cwd=self.repo_root)
|
||||
|
||||
original_name = "StellaOps.Cli"
|
||||
if runtime.startswith("win"):
|
||||
source = publish_dir / f"{original_name}.exe"
|
||||
target = publish_dir / "stella.exe"
|
||||
else:
|
||||
source = publish_dir / original_name
|
||||
target = publish_dir / "stella"
|
||||
if source.exists():
|
||||
if target.exists():
|
||||
target.unlink()
|
||||
source.rename(target)
|
||||
if not runtime.startswith("win"):
|
||||
target.chmod(target.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
|
||||
package_dir = self.cli_output_dir or (self.output_dir / "cli")
|
||||
ensure_directory(package_dir)
|
||||
archive_name = f"{package_prefix}-{self.version}-{runtime}"
|
||||
if runtime.startswith("win"):
|
||||
package_path = package_dir / f"{archive_name}.zip"
|
||||
self._archive_zip(publish_dir, package_path)
|
||||
else:
|
||||
package_path = package_dir / f"{archive_name}.tar.gz"
|
||||
self._archive_tar(publish_dir, package_path)
|
||||
|
||||
digest = compute_sha256(package_path)
|
||||
sha_path = package_path.with_suffix(package_path.suffix + ".sha256")
|
||||
sha_path.write_text(f"{digest} {package_path.name}\n", encoding="utf-8")
|
||||
|
||||
archive_info = OrderedDict((
|
||||
("path", self._relative_path(package_path)),
|
||||
("sha256", digest),
|
||||
))
|
||||
signature_info = self._sign_file(package_path)
|
||||
if signature_info:
|
||||
archive_info["signature"] = signature_info
|
||||
|
||||
sbom_info = self._generate_cli_sbom(runtime, publish_dir)
|
||||
|
||||
entry = OrderedDict((
|
||||
("runtime", runtime),
|
||||
("archive", archive_info),
|
||||
))
|
||||
if sbom_info:
|
||||
entry["sbom"] = sbom_info
|
||||
return entry
|
||||
|
||||
def _archive_tar(self, source_dir: pathlib.Path, archive_path: pathlib.Path) -> None:
|
||||
with tarfile.open(archive_path, "w:gz") as tar:
|
||||
for item in sorted(source_dir.rglob("*")):
|
||||
arcname = item.relative_to(source_dir)
|
||||
tar.add(item, arcname=arcname)
|
||||
|
||||
def _archive_zip(self, source_dir: pathlib.Path, archive_path: pathlib.Path) -> None:
|
||||
with zipfile.ZipFile(archive_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
|
||||
for item in sorted(source_dir.rglob("*")):
|
||||
if item.is_dir():
|
||||
continue
|
||||
arcname = item.relative_to(source_dir).as_posix()
|
||||
zip_info = zipfile.ZipInfo(arcname)
|
||||
zip_info.external_attr = (item.stat().st_mode & 0xFFFF) << 16
|
||||
with item.open("rb") as handle:
|
||||
zipf.writestr(zip_info, handle.read())
|
||||
|
||||
def _generate_cli_sbom(self, runtime: str, publish_dir: pathlib.Path) -> Optional[Mapping[str, Any]]:
|
||||
if self.dry_run:
|
||||
return None
|
||||
sbom_dir = ensure_directory(self.sboms_dir / "cli")
|
||||
sbom_path = sbom_dir / f"cli-{runtime}.cyclonedx.json"
|
||||
run([
|
||||
"syft",
|
||||
f"dir:{publish_dir}",
|
||||
"--output",
|
||||
f"cyclonedx-json={sbom_path}",
|
||||
])
|
||||
entry = OrderedDict((
|
||||
("path", self._relative_path(sbom_path)),
|
||||
("sha256", compute_sha256(sbom_path)),
|
||||
))
|
||||
signature_info = self._sign_file(sbom_path)
|
||||
if signature_info:
|
||||
entry["signature"] = signature_info
|
||||
return entry
|
||||
|
||||
def _sign_file(self, path: pathlib.Path) -> Optional[Mapping[str, Any]]:
|
||||
if self.skip_signing:
|
||||
return None
|
||||
if not (self.cosign_key_ref or self.cosign_identity_token):
|
||||
raise ValueError(
|
||||
"Signing requested but no cosign key or identity token provided. Use --skip-signing to bypass."
|
||||
)
|
||||
signature_path = path.with_suffix(path.suffix + ".sig")
|
||||
sha_path = path.with_suffix(path.suffix + ".sha256")
|
||||
digest = compute_sha256(path)
|
||||
sha_path.write_text(f"{digest} {path.name}\n", encoding="utf-8")
|
||||
cmd = ["cosign", "sign-blob", "--yes", str(path)]
|
||||
if self.cosign_key_ref:
|
||||
cmd.extend(["--key", self.cosign_key_ref])
|
||||
if self.cosign_identity_token:
|
||||
cmd.extend(["--identity-token", self.cosign_identity_token])
|
||||
if not self.tlog_upload:
|
||||
cmd.append("--tlog-upload=false")
|
||||
signature_data = run(cmd, env=self.cosign_env).strip()
|
||||
signature_path.write_text(signature_data + "\n", encoding="utf-8")
|
||||
return OrderedDict((
|
||||
("path", self._relative_path(signature_path)),
|
||||
("sha256", compute_sha256(signature_path)),
|
||||
("tlogUploaded", self.tlog_upload),
|
||||
))
|
||||
|
||||
def _extract_debug_entries(self, component_name: str, image_ref: str) -> List[OrderedDict[str, Any]]:
|
||||
if self.dry_run:
|
||||
return []
|
||||
@@ -832,6 +1002,7 @@ class ReleaseBuilder:
|
||||
helm_meta: Optional[Mapping[str, Any]],
|
||||
compose_meta: List[Mapping[str, Any]],
|
||||
debug_meta: Optional[Mapping[str, Any]],
|
||||
cli_meta: Sequence[Mapping[str, Any]],
|
||||
) -> Dict[str, Any]:
|
||||
manifest = OrderedDict()
|
||||
manifest["release"] = OrderedDict((
|
||||
@@ -847,6 +1018,8 @@ class ReleaseBuilder:
|
||||
manifest["compose"] = compose_meta
|
||||
if debug_meta:
|
||||
manifest["debugStore"] = debug_meta
|
||||
if cli_meta:
|
||||
manifest["cli"] = list(cli_meta)
|
||||
return manifest
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,18 @@
|
||||
"dockerfile": "ops/devops/release/docker/Dockerfile.angular-ui"
|
||||
}
|
||||
],
|
||||
"cli": {
|
||||
"project": "src/StellaOps.Cli/StellaOps.Cli.csproj",
|
||||
"runtimes": [
|
||||
"linux-x64",
|
||||
"linux-arm64",
|
||||
"osx-x64",
|
||||
"osx-arm64",
|
||||
"win-x64"
|
||||
],
|
||||
"packagePrefix": "stella",
|
||||
"outputDir": "out/release/cli"
|
||||
},
|
||||
"helm": {
|
||||
"chartPath": "deploy/helm/stellaops",
|
||||
"outputDir": "out/release/helm"
|
||||
|
||||
@@ -238,6 +238,60 @@ def verify_debug_store(manifest: Mapping[str, Any], release_dir: pathlib.Path, e
|
||||
f"(recorded {artefact_sha}, computed {actual_sha})"
|
||||
)
|
||||
|
||||
def verify_signature(signature: Mapping[str, Any], release_dir: pathlib.Path, label: str, component_name: str, errors: list[str]) -> None:
|
||||
sig_path_value = signature.get("path")
|
||||
if not sig_path_value:
|
||||
errors.append(f"{component_name}: {label} signature missing path.")
|
||||
return
|
||||
sig_path = resolve_path(str(sig_path_value), release_dir)
|
||||
if not sig_path.exists():
|
||||
errors.append(f"{component_name}: {label} signature missing → {sig_path}")
|
||||
return
|
||||
recorded_sha = signature.get("sha256")
|
||||
if recorded_sha:
|
||||
actual_sha = compute_sha256(sig_path)
|
||||
if actual_sha != recorded_sha:
|
||||
errors.append(
|
||||
f"{component_name}: {label} signature SHA mismatch for {sig_path} "
|
||||
f"(recorded {recorded_sha}, computed {actual_sha})"
|
||||
)
|
||||
|
||||
|
||||
def verify_cli_entries(manifest: Mapping[str, Any], release_dir: pathlib.Path, errors: list[str]) -> None:
|
||||
cli_entries = manifest.get("cli")
|
||||
if not cli_entries:
|
||||
return
|
||||
if not isinstance(cli_entries, list):
|
||||
errors.append("CLI manifest section must be a list.")
|
||||
return
|
||||
for entry in cli_entries:
|
||||
if not isinstance(entry, Mapping):
|
||||
errors.append("CLI entry must be a mapping.")
|
||||
continue
|
||||
runtime = entry.get("runtime", "<unknown>")
|
||||
component_name = f"cli[{runtime}]"
|
||||
archive = entry.get("archive")
|
||||
if not isinstance(archive, Mapping):
|
||||
errors.append(f"{component_name}: archive metadata missing or invalid.")
|
||||
else:
|
||||
verify_artifact_entry(archive, release_dir, "archive", component_name, errors)
|
||||
signature = archive.get("signature")
|
||||
if isinstance(signature, Mapping):
|
||||
verify_signature(signature, release_dir, "archive", component_name, errors)
|
||||
elif signature is not None:
|
||||
errors.append(f"{component_name}: archive signature must be an object.")
|
||||
sbom = entry.get("sbom")
|
||||
if sbom:
|
||||
if not isinstance(sbom, Mapping):
|
||||
errors.append(f"{component_name}: sbom entry must be a mapping.")
|
||||
else:
|
||||
verify_artifact_entry(sbom, release_dir, "sbom", component_name, errors)
|
||||
signature = sbom.get("signature")
|
||||
if isinstance(signature, Mapping):
|
||||
verify_signature(signature, release_dir, "sbom", component_name, errors)
|
||||
elif signature is not None:
|
||||
errors.append(f"{component_name}: sbom signature must be an object.")
|
||||
|
||||
|
||||
def verify_release(release_dir: pathlib.Path) -> None:
|
||||
if not release_dir.exists():
|
||||
@@ -246,6 +300,7 @@ def verify_release(release_dir: pathlib.Path) -> None:
|
||||
errors: list[str] = []
|
||||
verify_manifest_hashes(manifest, release_dir, errors)
|
||||
verify_components(manifest, release_dir, errors)
|
||||
verify_cli_entries(manifest, release_dir, errors)
|
||||
verify_collections(manifest, release_dir, errors)
|
||||
verify_debug_store(manifest, release_dir, errors)
|
||||
if errors:
|
||||
|
||||
77
ops/devops/scripts/check-advisory-raw-duplicates.js
Normal file
77
ops/devops/scripts/check-advisory-raw-duplicates.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Aggregation helper that surfaces advisory_raw duplicate candidates prior to enabling the
|
||||
* idempotency unique index. Intended for staging/offline snapshots.
|
||||
*
|
||||
* Usage:
|
||||
* mongo concelier ops/devops/scripts/check-advisory-raw-duplicates.js
|
||||
*
|
||||
* Environment variables:
|
||||
* LIMIT - optional cap on number of duplicate groups to print (default 50).
|
||||
*/
|
||||
(function () {
|
||||
function toInt(value, fallback) {
|
||||
var parsed = parseInt(value, 10);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
var limit = typeof LIMIT !== "undefined" ? toInt(LIMIT, 50) : 50;
|
||||
var database = db.getName ? db.getSiblingDB(db.getName()) : db;
|
||||
if (!database) {
|
||||
throw new Error("Unable to resolve database handle");
|
||||
}
|
||||
|
||||
print("");
|
||||
print("== advisory_raw duplicate audit ==");
|
||||
print("Database: " + database.getName());
|
||||
print("Limit : " + limit);
|
||||
print("");
|
||||
|
||||
var pipeline = [
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
vendor: "$source.vendor",
|
||||
upstreamId: "$upstream.upstream_id",
|
||||
contentHash: "$upstream.content_hash",
|
||||
tenant: "$tenant"
|
||||
},
|
||||
ids: { $addToSet: "$_id" },
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
},
|
||||
{ $match: { count: { $gt: 1 } } },
|
||||
{
|
||||
$project: {
|
||||
_id: 0,
|
||||
vendor: "$_id.vendor",
|
||||
upstreamId: "$_id.upstreamId",
|
||||
contentHash: "$_id.contentHash",
|
||||
tenant: "$_id.tenant",
|
||||
count: 1,
|
||||
ids: 1
|
||||
}
|
||||
},
|
||||
{ $sort: { count: -1, vendor: 1, upstreamId: 1 } },
|
||||
{ $limit: limit }
|
||||
];
|
||||
|
||||
var cursor = database.getCollection("advisory_raw").aggregate(pipeline, { allowDiskUse: true });
|
||||
var any = false;
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
any = true;
|
||||
print("---");
|
||||
print("vendor : " + doc.vendor);
|
||||
print("upstream_id : " + doc.upstreamId);
|
||||
print("tenant : " + doc.tenant);
|
||||
print("content_hash: " + doc.contentHash);
|
||||
print("count : " + doc.count);
|
||||
print("ids : " + doc.ids.join(", "));
|
||||
}
|
||||
|
||||
if (!any) {
|
||||
print("No duplicate advisory_raw documents detected.");
|
||||
}
|
||||
|
||||
print("");
|
||||
})();
|
||||
@@ -29,14 +29,14 @@ import markdown
|
||||
|
||||
# Enable fenced code blocks, tables, and definition lists. These cover the
|
||||
# Markdown constructs heavily used across the documentation set.
|
||||
MD_EXTENSIONS = [
|
||||
"fenced_code",
|
||||
"codehilite",
|
||||
"tables",
|
||||
"toc",
|
||||
"def_list",
|
||||
"admonition",
|
||||
]
|
||||
MD_EXTENSIONS = [
|
||||
"markdown.extensions.fenced_code",
|
||||
"markdown.extensions.codehilite",
|
||||
"markdown.extensions.tables",
|
||||
"markdown.extensions.toc",
|
||||
"markdown.extensions.def_list",
|
||||
"markdown.extensions.admonition",
|
||||
]
|
||||
|
||||
HTML_TEMPLATE = """<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
|
||||
63
scripts/rotate-policy-cli-secret.sh
Normal file
63
scripts/rotate-policy-cli-secret.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: rotate-policy-cli-secret.sh [--output <path>] [--dry-run]
|
||||
|
||||
Generates a new random shared secret suitable for the Authority
|
||||
`policy-cli` client and optionally writes it to the target file
|
||||
in `etc/secrets/` with the standard header comment.
|
||||
|
||||
Options:
|
||||
--output <path> Destination file (default: etc/secrets/policy-cli.secret)
|
||||
--dry-run Print the generated secret to stdout without writing.
|
||||
-h, --help Show this help.
|
||||
EOF
|
||||
}
|
||||
|
||||
OUTPUT="etc/secrets/policy-cli.secret"
|
||||
DRY_RUN=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--output)
|
||||
OUTPUT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--dry-run)
|
||||
DRY_RUN=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
echo "openssl is required to generate secrets" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Generate a 48-byte random secret, base64 encoded without padding.
|
||||
RAW_SECRET=$(openssl rand -base64 48 | tr -d '\n=')
|
||||
SECRET="policy-cli-${RAW_SECRET}"
|
||||
|
||||
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||
echo "$SECRET"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cat <<EOF > "$OUTPUT"
|
||||
# generated $(date -u +%Y-%m-%dT%H:%M:%SZ) via scripts/rotate-policy-cli-secret.sh
|
||||
$SECRET
|
||||
EOF
|
||||
|
||||
echo "Wrote new policy-cli secret to $OUTPUT"
|
||||
86
scripts/verify-policy-scopes.py
Normal file
86
scripts/verify-policy-scopes.py
Normal file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ensure Authority policy client configs use the fine-grained scope set."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
EXPECTED_SCOPES = (
|
||||
"policy:read",
|
||||
"policy:author",
|
||||
"policy:review",
|
||||
"policy:simulate",
|
||||
"findings:read",
|
||||
)
|
||||
|
||||
|
||||
def extract_scopes(lines: list[str], start_index: int) -> tuple[str, ...] | None:
|
||||
for offset in range(1, 12):
|
||||
if start_index + offset >= len(lines):
|
||||
break
|
||||
line = lines[start_index + offset].strip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith("scopes:"):
|
||||
try:
|
||||
raw = line.split("[", 1)[1].rsplit("]", 1)[0]
|
||||
except IndexError:
|
||||
return None
|
||||
scopes = tuple(scope.strip().strip('"') for scope in raw.split(","))
|
||||
scopes = tuple(scope for scope in scopes if scope)
|
||||
return scopes
|
||||
return None
|
||||
|
||||
|
||||
def validate(path: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except FileNotFoundError:
|
||||
return [f"{path}: missing file"]
|
||||
|
||||
if "policy:write" in text or "policy:submit" in text:
|
||||
errors.append(f"{path}: contains legacy policy scope names (policy:write/policy:submit)")
|
||||
|
||||
lines = text.splitlines()
|
||||
client_indices = [idx for idx, line in enumerate(lines) if 'clientId: "policy-cli"' in line]
|
||||
if not client_indices:
|
||||
errors.append(f"{path}: policy-cli client registration not found")
|
||||
return errors
|
||||
|
||||
for idx in client_indices:
|
||||
scopes = extract_scopes(lines, idx)
|
||||
if scopes is None:
|
||||
errors.append(f"{path}: unable to parse scopes for policy-cli client")
|
||||
continue
|
||||
if tuple(sorted(scopes)) != tuple(sorted(EXPECTED_SCOPES)):
|
||||
errors.append(
|
||||
f"{path}: unexpected policy-cli scopes {scopes}; expected {EXPECTED_SCOPES}"
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
repo_root = Path(__file__).resolve().parents[1]
|
||||
targets = [
|
||||
repo_root / "etc" / "authority.yaml",
|
||||
repo_root / "etc" / "authority.yaml.sample",
|
||||
]
|
||||
|
||||
failures: list[str] = []
|
||||
for target in targets:
|
||||
failures.extend(validate(target))
|
||||
|
||||
if failures:
|
||||
for message in failures:
|
||||
print(f"error: {message}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("policy scope verification passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
689
src/StellaOps.Api.OpenApi/authority/openapi.yaml
Normal file
689
src/StellaOps.Api.OpenApi/authority/openapi.yaml
Normal file
@@ -0,0 +1,689 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: StellaOps Authority Authentication API
|
||||
summary: Token issuance, introspection, revocation, and key discovery endpoints exposed by the Authority service.
|
||||
description: |
|
||||
The Authority service issues OAuth 2.1 access tokens for StellaOps components, enforcing tenant and scope
|
||||
restrictions configured per client. This specification describes the authentication surface only; domain APIs
|
||||
are documented by their owning services.
|
||||
version: 0.1.0
|
||||
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
|
||||
servers:
|
||||
- url: https://authority.stellaops.local
|
||||
description: Example Authority deployment
|
||||
tags:
|
||||
- name: Authentication
|
||||
description: OAuth 2.1 token exchange, introspection, and revocation flows.
|
||||
- name: Keys
|
||||
description: JSON Web Key Set discovery.
|
||||
components:
|
||||
securitySchemes:
|
||||
ClientSecretBasic:
|
||||
type: http
|
||||
scheme: basic
|
||||
description: HTTP Basic authentication with `client_id` and `client_secret`.
|
||||
OAuthPassword:
|
||||
type: oauth2
|
||||
description: Resource owner password exchange for Authority-managed identities.
|
||||
flows:
|
||||
password:
|
||||
tokenUrl: /token
|
||||
refreshUrl: /token
|
||||
scopes:
|
||||
advisory:ingest: Submit advisory ingestion payloads.
|
||||
advisory:read: Read advisory ingestion data.
|
||||
aoc:verify: Execute Aggregation-Only Contract verification workflows.
|
||||
authority.audit.read: Read Authority audit logs.
|
||||
authority.clients.manage: Manage Authority client registrations.
|
||||
authority.users.manage: Manage Authority users.
|
||||
authority:tenants.read: Read the Authority tenant catalog.
|
||||
concelier.jobs.trigger: Trigger Concelier aggregation jobs.
|
||||
concelier.merge: Manage Concelier merge operations.
|
||||
effective:write: Write effective findings (Policy Engine service identity only).
|
||||
email: Access email claim data.
|
||||
exceptions:approve: Approve exception workflows.
|
||||
findings:read: Read effective findings emitted by Policy Engine.
|
||||
graph:export: Export graph artefacts.
|
||||
graph:read: Read graph explorer data.
|
||||
graph:simulate: Run graph what-if simulations.
|
||||
graph:write: Enqueue or mutate graph build jobs.
|
||||
offline_access: Request refresh tokens for offline access.
|
||||
openid: Request OpenID Connect identity tokens.
|
||||
orch:operate: Execute privileged Orchestrator control actions.
|
||||
orch:read: Read Orchestrator job state.
|
||||
policy:author: Author Policy Studio drafts and workspaces.
|
||||
policy:activate: Activate policy revisions.
|
||||
policy:approve: Approve or reject policy drafts.
|
||||
policy:audit: Inspect Policy Studio audit history.
|
||||
policy:edit: Edit policy definitions.
|
||||
policy:operate: Operate Policy Studio promotions and runs.
|
||||
policy:read: Read policy definitions and metadata.
|
||||
policy:run: Trigger policy executions.
|
||||
policy:submit: Submit policy drafts for review.
|
||||
policy:review: Review Policy Studio drafts and leave feedback.
|
||||
policy:simulate: Execute Policy Studio simulations.
|
||||
policy:write: Create or update policy drafts.
|
||||
profile: Access profile claim data.
|
||||
signals:admin: Administer Signals ingestion and routing settings.
|
||||
signals:read: Read Signals events and state.
|
||||
signals:write: Publish Signals events or mutate state.
|
||||
stellaops.bypass: Bypass trust boundary protections (restricted identities only).
|
||||
ui.read: Read Console UX resources.
|
||||
vex:ingest: Submit VEX ingestion payloads.
|
||||
vex:read: Read VEX ingestion data.
|
||||
vuln:read: Read vulnerability permalinks and overlays.
|
||||
authorizationCode:
|
||||
authorizationUrl: /authorize
|
||||
tokenUrl: /token
|
||||
refreshUrl: /token
|
||||
scopes:
|
||||
advisory:ingest: Submit advisory ingestion payloads.
|
||||
advisory:read: Read advisory ingestion data.
|
||||
aoc:verify: Execute Aggregation-Only Contract verification workflows.
|
||||
authority.audit.read: Read Authority audit logs.
|
||||
authority.clients.manage: Manage Authority client registrations.
|
||||
authority.users.manage: Manage Authority users.
|
||||
authority:tenants.read: Read the Authority tenant catalog.
|
||||
concelier.jobs.trigger: Trigger Concelier aggregation jobs.
|
||||
concelier.merge: Manage Concelier merge operations.
|
||||
effective:write: Write effective findings (Policy Engine service identity only).
|
||||
email: Access email claim data.
|
||||
exceptions:approve: Approve exception workflows.
|
||||
findings:read: Read effective findings emitted by Policy Engine.
|
||||
graph:export: Export graph artefacts.
|
||||
graph:read: Read graph explorer data.
|
||||
graph:simulate: Run graph what-if simulations.
|
||||
graph:write: Enqueue or mutate graph build jobs.
|
||||
offline_access: Request refresh tokens for offline access.
|
||||
openid: Request OpenID Connect identity tokens.
|
||||
orch:operate: Execute privileged Orchestrator control actions.
|
||||
orch:read: Read Orchestrator job state.
|
||||
policy:author: Author Policy Studio drafts and workspaces.
|
||||
policy:activate: Activate policy revisions.
|
||||
policy:approve: Approve or reject policy drafts.
|
||||
policy:audit: Inspect Policy Studio audit history.
|
||||
policy:edit: Edit policy definitions.
|
||||
policy:operate: Operate Policy Studio promotions and runs.
|
||||
policy:read: Read policy definitions and metadata.
|
||||
policy:run: Trigger policy executions.
|
||||
policy:submit: Submit policy drafts for review.
|
||||
policy:review: Review Policy Studio drafts and leave feedback.
|
||||
policy:simulate: Execute Policy Studio simulations.
|
||||
policy:write: Create or update policy drafts.
|
||||
profile: Access profile claim data.
|
||||
signals:admin: Administer Signals ingestion and routing settings.
|
||||
signals:read: Read Signals events and state.
|
||||
signals:write: Publish Signals events or mutate state.
|
||||
stellaops.bypass: Bypass trust boundary protections (restricted identities only).
|
||||
ui.read: Read Console UX resources.
|
||||
vex:ingest: Submit VEX ingestion payloads.
|
||||
vex:read: Read VEX ingestion data.
|
||||
vuln:read: Read vulnerability permalinks and overlays.
|
||||
OAuthClientCredentials:
|
||||
type: oauth2
|
||||
description: Client credential exchange for machine-to-machine identities.
|
||||
flows:
|
||||
clientCredentials:
|
||||
tokenUrl: /token
|
||||
scopes:
|
||||
advisory:ingest: Submit advisory ingestion payloads.
|
||||
advisory:read: Read advisory ingestion data.
|
||||
aoc:verify: Execute Aggregation-Only Contract verification workflows.
|
||||
authority.audit.read: Read Authority audit logs.
|
||||
authority.clients.manage: Manage Authority client registrations.
|
||||
authority.users.manage: Manage Authority users.
|
||||
authority:tenants.read: Read the Authority tenant catalog.
|
||||
concelier.jobs.trigger: Trigger Concelier aggregation jobs.
|
||||
concelier.merge: Manage Concelier merge operations.
|
||||
effective:write: Write effective findings (Policy Engine service identity only).
|
||||
email: Access email claim data.
|
||||
exceptions:approve: Approve exception workflows.
|
||||
findings:read: Read effective findings emitted by Policy Engine.
|
||||
graph:export: Export graph artefacts.
|
||||
graph:read: Read graph explorer data.
|
||||
graph:simulate: Run graph what-if simulations.
|
||||
graph:write: Enqueue or mutate graph build jobs.
|
||||
offline_access: Request refresh tokens for offline access.
|
||||
openid: Request OpenID Connect identity tokens.
|
||||
orch:operate: Execute privileged Orchestrator control actions.
|
||||
orch:read: Read Orchestrator job state.
|
||||
policy:author: Author Policy Studio drafts and workspaces.
|
||||
policy:activate: Activate policy revisions.
|
||||
policy:approve: Approve or reject policy drafts.
|
||||
policy:audit: Inspect Policy Studio audit history.
|
||||
policy:edit: Edit policy definitions.
|
||||
policy:operate: Operate Policy Studio promotions and runs.
|
||||
policy:read: Read policy definitions and metadata.
|
||||
policy:run: Trigger policy executions.
|
||||
policy:submit: Submit policy drafts for review.
|
||||
policy:review: Review Policy Studio drafts and leave feedback.
|
||||
policy:simulate: Execute Policy Studio simulations.
|
||||
policy:write: Create or update policy drafts.
|
||||
profile: Access profile claim data.
|
||||
signals:admin: Administer Signals ingestion and routing settings.
|
||||
signals:read: Read Signals events and state.
|
||||
signals:write: Publish Signals events or mutate state.
|
||||
stellaops.bypass: Bypass trust boundary protections (restricted identities only).
|
||||
ui.read: Read Console UX resources.
|
||||
vex:ingest: Submit VEX ingestion payloads.
|
||||
vex:read: Read VEX ingestion data.
|
||||
vuln:read: Read vulnerability permalinks and overlays.
|
||||
schemas:
|
||||
TokenResponse:
|
||||
type: object
|
||||
description: OAuth 2.1 bearer token response.
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
description: Access token encoded as JWT.
|
||||
token_type:
|
||||
type: string
|
||||
description: Token type indicator. Always `Bearer`.
|
||||
expires_in:
|
||||
type: integer
|
||||
description: Lifetime of the access token, in seconds.
|
||||
minimum: 1
|
||||
refresh_token:
|
||||
type: string
|
||||
description: Refresh token issued when the grant allows offline access.
|
||||
scope:
|
||||
type: string
|
||||
description: Space-delimited scopes granted in the response.
|
||||
id_token:
|
||||
type: string
|
||||
description: ID token issued for authorization-code flows.
|
||||
required:
|
||||
- access_token
|
||||
- token_type
|
||||
- expires_in
|
||||
OAuthErrorResponse:
|
||||
type: object
|
||||
description: RFC 6749 compliant error envelope.
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
description: Machine-readable error code.
|
||||
error_description:
|
||||
type: string
|
||||
description: Human-readable error description.
|
||||
error_uri:
|
||||
type: string
|
||||
format: uri
|
||||
description: Link to documentation about the error.
|
||||
required:
|
||||
- error
|
||||
PasswordGrantRequest:
|
||||
type: object
|
||||
required:
|
||||
- grant_type
|
||||
- client_id
|
||||
- username
|
||||
- password
|
||||
properties:
|
||||
grant_type:
|
||||
type: string
|
||||
const: password
|
||||
client_id:
|
||||
type: string
|
||||
description: Registered client identifier. May also be supplied via HTTP Basic auth.
|
||||
client_secret:
|
||||
type: string
|
||||
description: Client secret. Required for confidential clients when not using HTTP Basic auth.
|
||||
scope:
|
||||
type: string
|
||||
description: Space-delimited scopes being requested.
|
||||
username:
|
||||
type: string
|
||||
description: Resource owner username.
|
||||
password:
|
||||
type: string
|
||||
description: Resource owner password.
|
||||
authority_provider:
|
||||
type: string
|
||||
description: Optional identity provider hint. Required when multiple password-capable providers are registered.
|
||||
description: Form-encoded payload for password grant exchange.
|
||||
ClientCredentialsGrantRequest:
|
||||
type: object
|
||||
required:
|
||||
- grant_type
|
||||
- client_id
|
||||
properties:
|
||||
grant_type:
|
||||
type: string
|
||||
const: client_credentials
|
||||
client_id:
|
||||
type: string
|
||||
description: Registered client identifier. May also be supplied via HTTP Basic auth.
|
||||
client_secret:
|
||||
type: string
|
||||
description: Client secret. Required for confidential clients when not using HTTP Basic auth.
|
||||
scope:
|
||||
type: string
|
||||
description: Space-delimited scopes being requested.
|
||||
authority_provider:
|
||||
type: string
|
||||
description: Optional identity provider hint for plugin-backed clients.
|
||||
operator_reason:
|
||||
type: string
|
||||
description: Required when requesting `orch:operate`; explains the operator action.
|
||||
maxLength: 256
|
||||
operator_ticket:
|
||||
type: string
|
||||
description: Required when requesting `orch:operate`; tracks the external change ticket or incident.
|
||||
maxLength: 128
|
||||
description: Form-encoded payload for client credentials exchange.
|
||||
RefreshTokenGrantRequest:
|
||||
type: object
|
||||
required:
|
||||
- grant_type
|
||||
- refresh_token
|
||||
properties:
|
||||
grant_type:
|
||||
type: string
|
||||
const: refresh_token
|
||||
client_id:
|
||||
type: string
|
||||
description: Registered client identifier. May also be supplied via HTTP Basic auth.
|
||||
client_secret:
|
||||
type: string
|
||||
description: Client secret. Required for confidential clients when not using HTTP Basic auth.
|
||||
refresh_token:
|
||||
type: string
|
||||
description: Previously issued refresh token.
|
||||
scope:
|
||||
type: string
|
||||
description: Optional scope list to narrow the requested access.
|
||||
description: Form-encoded payload for refresh token exchange.
|
||||
RevocationRequest:
|
||||
type: object
|
||||
required:
|
||||
- token
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: Token value or token identifier to revoke.
|
||||
token_type_hint:
|
||||
type: string
|
||||
description: Optional token type hint (`access_token` or `refresh_token`).
|
||||
description: Form-encoded payload for token revocation.
|
||||
IntrospectionRequest:
|
||||
type: object
|
||||
required:
|
||||
- token
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
description: Token value whose state should be introspected.
|
||||
token_type_hint:
|
||||
type: string
|
||||
description: Optional token type hint (`access_token` or `refresh_token`).
|
||||
description: Form-encoded payload for token introspection.
|
||||
IntrospectionResponse:
|
||||
type: object
|
||||
description: Active token descriptor compliant with RFC 7662.
|
||||
properties:
|
||||
active:
|
||||
type: boolean
|
||||
description: Indicates whether the token is currently active.
|
||||
scope:
|
||||
type: string
|
||||
description: Space-delimited list of scopes granted to the token.
|
||||
client_id:
|
||||
type: string
|
||||
description: Client identifier associated with the token.
|
||||
sub:
|
||||
type: string
|
||||
description: Subject identifier when the token represents an end-user.
|
||||
username:
|
||||
type: string
|
||||
description: Preferred username associated with the subject.
|
||||
token_type:
|
||||
type: string
|
||||
description: Type of the token (e.g., `Bearer`).
|
||||
exp:
|
||||
type: integer
|
||||
description: Expiration timestamp (seconds since UNIX epoch).
|
||||
iat:
|
||||
type: integer
|
||||
description: Issued-at timestamp (seconds since UNIX epoch).
|
||||
nbf:
|
||||
type: integer
|
||||
description: Not-before timestamp (seconds since UNIX epoch).
|
||||
aud:
|
||||
type: array
|
||||
description: Audience values associated with the token.
|
||||
items:
|
||||
type: string
|
||||
iss:
|
||||
type: string
|
||||
description: Issuer identifier.
|
||||
jti:
|
||||
type: string
|
||||
description: JWT identifier corresponding to the token.
|
||||
tenant:
|
||||
type: string
|
||||
description: Tenant associated with the token, when assigned.
|
||||
confirmation:
|
||||
type: object
|
||||
description: Sender-constrained confirmation data (e.g., mTLS thumbprint, DPoP JWK thumbprint).
|
||||
required:
|
||||
- active
|
||||
JwksDocument:
|
||||
type: object
|
||||
description: JSON Web Key Set published by the Authority.
|
||||
properties:
|
||||
keys:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Jwk'
|
||||
required:
|
||||
- keys
|
||||
Jwk:
|
||||
type: object
|
||||
description: Public key material for token signature validation.
|
||||
properties:
|
||||
kid:
|
||||
type: string
|
||||
description: Key identifier.
|
||||
kty:
|
||||
type: string
|
||||
description: Key type (e.g., `EC`, `RSA`).
|
||||
use:
|
||||
type: string
|
||||
description: Intended key use (`sig`).
|
||||
alg:
|
||||
type: string
|
||||
description: Signing algorithm (e.g., `ES384`).
|
||||
crv:
|
||||
type: string
|
||||
description: Elliptic curve identifier when applicable.
|
||||
x:
|
||||
type: string
|
||||
description: X coordinate for EC keys.
|
||||
y:
|
||||
type: string
|
||||
description: Y coordinate for EC keys.
|
||||
status:
|
||||
type: string
|
||||
description: Operational status metadata for the key (e.g., `active`, `retiring`).
|
||||
paths:
|
||||
/token:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Exchange credentials for tokens
|
||||
description: |
|
||||
Issues OAuth 2.1 bearer tokens for StellaOps clients. Supports password, client credentials,
|
||||
authorization-code, device, and refresh token grants. Confidential clients must authenticate using
|
||||
HTTP Basic auth or `client_secret` form fields.
|
||||
security:
|
||||
- ClientSecretBasic: []
|
||||
- {}
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/PasswordGrantRequest'
|
||||
- $ref: '#/components/schemas/ClientCredentialsGrantRequest'
|
||||
- $ref: '#/components/schemas/RefreshTokenGrantRequest'
|
||||
encoding:
|
||||
authority_provider:
|
||||
style: form
|
||||
explode: false
|
||||
examples:
|
||||
passwordGrant:
|
||||
summary: Password grant for tenant-scoped ingestion bot
|
||||
value:
|
||||
grant_type: password
|
||||
client_id: ingest-cli
|
||||
client_secret: s3cr3t
|
||||
username: ingest-bot
|
||||
password: pa55w0rd!
|
||||
scope: advisory:ingest vex:ingest
|
||||
authority_provider: primary-directory
|
||||
authorizationCode:
|
||||
summary: Authorization code exchange for Console UI session
|
||||
value:
|
||||
grant_type: authorization_code
|
||||
client_id: console-ui
|
||||
code: 2Lba1WtwPLfZ2b0Z9uPrsQ
|
||||
redirect_uri: https://console.stellaops.local/auth/callback
|
||||
code_verifier: g3ZnL91QJ6i4zO_86oI4CDnZ7gS0bSeK
|
||||
clientCredentials:
|
||||
summary: Client credentials exchange for Policy Engine
|
||||
value:
|
||||
grant_type: client_credentials
|
||||
client_id: policy-engine
|
||||
client_secret: 9c39f602-2f2b-4f29
|
||||
scope: effective:write findings:read
|
||||
operator_reason: Deploying policy change 1234
|
||||
operator_ticket: CHG-004211
|
||||
refreshToken:
|
||||
summary: Refresh token rotation for console session
|
||||
value:
|
||||
grant_type: refresh_token
|
||||
client_id: console-ui
|
||||
refresh_token: 0.rg9pVlsGzXE8Q
|
||||
responses:
|
||||
'200':
|
||||
description: Token exchange succeeded.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TokenResponse'
|
||||
examples:
|
||||
passwordGrant:
|
||||
summary: Password grant success response
|
||||
value:
|
||||
access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9...
|
||||
token_type: Bearer
|
||||
expires_in: 3600
|
||||
refresh_token: OxGdVtZJ-mk49cFd38uRUw
|
||||
scope: advisory:ingest vex:ingest
|
||||
clientCredentials:
|
||||
summary: Client credentials success response
|
||||
value:
|
||||
access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9...
|
||||
token_type: Bearer
|
||||
expires_in: 900
|
||||
scope: effective:write findings:read
|
||||
authorizationCode:
|
||||
summary: Authorization code success response
|
||||
value:
|
||||
access_token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9...
|
||||
token_type: Bearer
|
||||
expires_in: 900
|
||||
refresh_token: VxKpc9Vj9QjYV6gLrhQHTw
|
||||
scope: ui.read authority:tenants.read
|
||||
id_token: eyJhbGciOiJFUzM4NCIsImtpZCI6ImNvbnNvbGUifQ...
|
||||
'400':
|
||||
description: Malformed request, unsupported grant type, or invalid credentials.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
invalidProvider:
|
||||
summary: Unknown identity provider hint
|
||||
value:
|
||||
error: invalid_request
|
||||
error_description: "Unknown identity provider 'legacy-directory'."
|
||||
invalidScope:
|
||||
summary: Scope not permitted for client
|
||||
value:
|
||||
error: invalid_scope
|
||||
error_description: Scope 'effective:write' is not permitted for this client.
|
||||
'401':
|
||||
description: Client authentication failed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
badClientSecret:
|
||||
summary: Invalid client secret
|
||||
value:
|
||||
error: invalid_client
|
||||
error_description: Client authentication failed.
|
||||
/revoke:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Revoke an access or refresh token
|
||||
security:
|
||||
- ClientSecretBasic: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RevocationRequest'
|
||||
examples:
|
||||
revokeRefreshToken:
|
||||
summary: Revoke refresh token after logout
|
||||
value:
|
||||
token: 0.rg9pVlsGzXE8Q
|
||||
token_type_hint: refresh_token
|
||||
responses:
|
||||
'200':
|
||||
description: Token revoked or already invalid. The response body is intentionally blank.
|
||||
'400':
|
||||
description: Malformed request.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
missingToken:
|
||||
summary: Token parameter omitted
|
||||
value:
|
||||
error: invalid_request
|
||||
error_description: The revocation request is missing the token parameter.
|
||||
'401':
|
||||
description: Client authentication failed.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
badClientSecret:
|
||||
summary: Invalid client credentials
|
||||
value:
|
||||
error: invalid_client
|
||||
error_description: Client authentication failed.
|
||||
/introspect:
|
||||
post:
|
||||
tags:
|
||||
- Authentication
|
||||
summary: Introspect token state
|
||||
description: Returns the active status and claims for a given token. Requires a privileged client.
|
||||
security:
|
||||
- ClientSecretBasic: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IntrospectionRequest'
|
||||
examples:
|
||||
introspectToken:
|
||||
summary: Validate an access token issued to Orchestrator
|
||||
value:
|
||||
token: eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9...
|
||||
token_type_hint: access_token
|
||||
responses:
|
||||
'200':
|
||||
description: Token state evaluated.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/IntrospectionResponse'
|
||||
examples:
|
||||
activeToken:
|
||||
summary: Active token response
|
||||
value:
|
||||
active: true
|
||||
scope: orch:operate orch:read
|
||||
client_id: orch-control
|
||||
sub: operator-7f12
|
||||
username: ops.engineer@tenant.example
|
||||
token_type: Bearer
|
||||
exp: 1761628800
|
||||
iat: 1761625200
|
||||
nbf: 1761625200
|
||||
iss: https://authority.stellaops.local
|
||||
aud:
|
||||
- https://orch.stellaops.local
|
||||
jti: 01J8KYRAMG7FWBPRRV5XG20T7S
|
||||
tenant: tenant-alpha
|
||||
confirmation:
|
||||
mtls_thumbprint: 079871b8c9a0f2e6
|
||||
inactiveToken:
|
||||
summary: Revoked token response
|
||||
value:
|
||||
active: false
|
||||
'400':
|
||||
description: Malformed request.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
missingToken:
|
||||
summary: Token missing
|
||||
value:
|
||||
error: invalid_request
|
||||
error_description: token parameter is required.
|
||||
'401':
|
||||
description: Client authentication failed or client lacks introspection permission.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/OAuthErrorResponse'
|
||||
examples:
|
||||
unauthorizedClient:
|
||||
summary: Client not allowed to introspect tokens
|
||||
value:
|
||||
error: invalid_client
|
||||
error_description: Client authentication failed.
|
||||
/jwks:
|
||||
get:
|
||||
tags:
|
||||
- Keys
|
||||
summary: Retrieve signing keys
|
||||
description: Returns the JSON Web Key Set used to validate Authority-issued tokens.
|
||||
responses:
|
||||
'200':
|
||||
description: JWKS document.
|
||||
headers:
|
||||
Cache-Control:
|
||||
schema:
|
||||
type: string
|
||||
description: Standard caching headers apply; keys rotate infrequently.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/JwksDocument'
|
||||
examples:
|
||||
ecKeySet:
|
||||
summary: EC signing keys
|
||||
value:
|
||||
keys:
|
||||
- kid: auth-tokens-es384-202510
|
||||
kty: EC
|
||||
use: sig
|
||||
alg: ES384
|
||||
crv: P-384
|
||||
x: 7UchU5R77LtChrJx6uWg9mYjFvV6RIpSgZPDIj7d1q0
|
||||
y: v98nHe8a7mGZ9Fn1t4Jp9PTJv1ma35QPmhUrE4pH7H0
|
||||
status: active
|
||||
- kid: auth-tokens-es384-202409
|
||||
kty: EC
|
||||
use: sig
|
||||
alg: ES384
|
||||
crv: P-384
|
||||
x: hjdKc0r8jvVHJ7S9mP0y0mU9bqN7v5PxS21SwclTzfc
|
||||
y: yk6J3pz4TUpymN4mG-6th3dYvJ5N1lQvDK0PLuFv3Pg
|
||||
status: retiring
|
||||
@@ -11,10 +11,18 @@ public class StellaOpsScopesTests
|
||||
[InlineData(StellaOpsScopes.VexRead)]
|
||||
[InlineData(StellaOpsScopes.VexIngest)]
|
||||
[InlineData(StellaOpsScopes.AocVerify)]
|
||||
[InlineData(StellaOpsScopes.SignalsRead)]
|
||||
[InlineData(StellaOpsScopes.SignalsWrite)]
|
||||
[InlineData(StellaOpsScopes.SignalsAdmin)]
|
||||
[InlineData(StellaOpsScopes.PolicyWrite)]
|
||||
[InlineData(StellaOpsScopes.PolicyAuthor)]
|
||||
[InlineData(StellaOpsScopes.PolicySubmit)]
|
||||
[InlineData(StellaOpsScopes.PolicyApprove)]
|
||||
[InlineData(StellaOpsScopes.PolicyReview)]
|
||||
[InlineData(StellaOpsScopes.PolicyOperate)]
|
||||
[InlineData(StellaOpsScopes.PolicyAudit)]
|
||||
[InlineData(StellaOpsScopes.PolicyRun)]
|
||||
[InlineData(StellaOpsScopes.PolicySimulate)]
|
||||
[InlineData(StellaOpsScopes.FindingsRead)]
|
||||
[InlineData(StellaOpsScopes.EffectiveWrite)]
|
||||
[InlineData(StellaOpsScopes.GraphRead)]
|
||||
@@ -22,6 +30,11 @@ public class StellaOpsScopesTests
|
||||
[InlineData(StellaOpsScopes.GraphWrite)]
|
||||
[InlineData(StellaOpsScopes.GraphExport)]
|
||||
[InlineData(StellaOpsScopes.GraphSimulate)]
|
||||
[InlineData(StellaOpsScopes.OrchRead)]
|
||||
[InlineData(StellaOpsScopes.OrchOperate)]
|
||||
[InlineData(StellaOpsScopes.ExportViewer)]
|
||||
[InlineData(StellaOpsScopes.ExportOperator)]
|
||||
[InlineData(StellaOpsScopes.ExportAdmin)]
|
||||
public void All_IncludesNewScopes(string scope)
|
||||
{
|
||||
Assert.Contains(scope, StellaOpsScopes.All);
|
||||
@@ -31,6 +44,9 @@ public class StellaOpsScopesTests
|
||||
[InlineData("Advisory:Read", StellaOpsScopes.AdvisoryRead)]
|
||||
[InlineData(" VEX:Ingest ", StellaOpsScopes.VexIngest)]
|
||||
[InlineData("AOC:VERIFY", StellaOpsScopes.AocVerify)]
|
||||
[InlineData(" Signals:Write ", StellaOpsScopes.SignalsWrite)]
|
||||
[InlineData("Policy:Author", StellaOpsScopes.PolicyAuthor)]
|
||||
[InlineData("Export.Admin", StellaOpsScopes.ExportAdmin)]
|
||||
public void Normalize_NormalizesToLowerCase(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, StellaOpsScopes.Normalize(input));
|
||||
|
||||
@@ -15,6 +15,11 @@ public static class StellaOpsClaimTypes
|
||||
/// </summary>
|
||||
public const string Tenant = "stellaops:tenant";
|
||||
|
||||
/// <summary>
|
||||
/// StellaOps project identifier claim (optional project scoping within a tenant).
|
||||
/// </summary>
|
||||
public const string Project = "stellaops:project";
|
||||
|
||||
/// <summary>
|
||||
/// OAuth2/OIDC client identifier claim (maps to <c>client_id</c>).
|
||||
/// </summary>
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Auth.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical scope names supported by StellaOps services.
|
||||
/// </summary>
|
||||
public static class StellaOpsScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Scope required to trigger Concelier jobs.
|
||||
/// </summary>
|
||||
public const string ConcelierJobsTrigger = "concelier.jobs.trigger";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required to manage Concelier merge operations.
|
||||
/// </summary>
|
||||
public const string ConcelierMerge = "concelier.merge";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting administrative access to Authority user management.
|
||||
/// </summary>
|
||||
public const string AuthorityUsersManage = "authority.users.manage";
|
||||
|
||||
/// <summary>
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Auth.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Canonical scope names supported by StellaOps services.
|
||||
/// </summary>
|
||||
public static class StellaOpsScopes
|
||||
{
|
||||
/// <summary>
|
||||
/// Scope required to trigger Concelier jobs.
|
||||
/// </summary>
|
||||
public const string ConcelierJobsTrigger = "concelier.jobs.trigger";
|
||||
|
||||
/// <summary>
|
||||
/// Scope required to manage Concelier merge operations.
|
||||
/// </summary>
|
||||
public const string ConcelierMerge = "concelier.merge";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting administrative access to Authority user management.
|
||||
/// </summary>
|
||||
public const string AuthorityUsersManage = "authority.users.manage";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting administrative access to Authority client registrations.
|
||||
/// </summary>
|
||||
public const string AuthorityClientsManage = "authority.clients.manage";
|
||||
@@ -38,6 +38,16 @@ public static class StellaOpsScopes
|
||||
/// </summary>
|
||||
public const string Bypass = "stellaops.bypass";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to console UX features.
|
||||
/// </summary>
|
||||
public const string UiRead = "ui.read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to approve exceptions.
|
||||
/// </summary>
|
||||
public const string ExceptionsApprove = "exceptions:approve";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to raw advisory ingestion data.
|
||||
/// </summary>
|
||||
@@ -63,11 +73,46 @@ public static class StellaOpsScopes
|
||||
/// </summary>
|
||||
public const string AocVerify = "aoc:verify";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to reachability signals.
|
||||
/// </summary>
|
||||
public const string SignalsRead = "signals:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to write reachability signals.
|
||||
/// </summary>
|
||||
public const string SignalsWrite = "signals:write";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting administrative access to reachability signal ingestion.
|
||||
/// </summary>
|
||||
public const string SignalsAdmin = "signals:admin";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to create or edit policy drafts.
|
||||
/// </summary>
|
||||
public const string PolicyWrite = "policy:write";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to author Policy Studio workspaces.
|
||||
/// </summary>
|
||||
public const string PolicyAuthor = "policy:author";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to edit policy configurations.
|
||||
/// </summary>
|
||||
public const string PolicyEdit = "policy:edit";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to policy metadata.
|
||||
/// </summary>
|
||||
public const string PolicyRead = "policy:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to review Policy Studio drafts.
|
||||
/// </summary>
|
||||
public const string PolicyReview = "policy:review";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to submit drafts for review.
|
||||
/// </summary>
|
||||
@@ -78,16 +123,36 @@ public static class StellaOpsScopes
|
||||
/// </summary>
|
||||
public const string PolicyApprove = "policy:approve";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to operate Policy Studio promotions and runs.
|
||||
/// </summary>
|
||||
public const string PolicyOperate = "policy:operate";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to audit Policy Studio activity.
|
||||
/// </summary>
|
||||
public const string PolicyAudit = "policy:audit";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to trigger policy runs and activation workflows.
|
||||
/// </summary>
|
||||
public const string PolicyRun = "policy:run";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to activate policies.
|
||||
/// </summary>
|
||||
public const string PolicyActivate = "policy:activate";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to effective findings materialised by Policy Engine.
|
||||
/// </summary>
|
||||
public const string FindingsRead = "findings:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to run Policy Studio simulations.
|
||||
/// </summary>
|
||||
public const string PolicySimulate = "policy:simulate";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granted to Policy Engine service identity for writing effective findings.
|
||||
/// </summary>
|
||||
@@ -103,6 +168,21 @@ public static class StellaOpsScopes
|
||||
/// </summary>
|
||||
public const string VulnRead = "vuln:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to export center runs and bundles.
|
||||
/// </summary>
|
||||
public const string ExportViewer = "export.viewer";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to operate export center scheduling and run execution.
|
||||
/// </summary>
|
||||
public const string ExportOperator = "export.operator";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting administrative control over export center retention, encryption keys, and scheduling policies.
|
||||
/// </summary>
|
||||
public const string ExportAdmin = "export.admin";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to enqueue or mutate graph build jobs.
|
||||
/// </summary>
|
||||
@@ -118,6 +198,21 @@ public static class StellaOpsScopes
|
||||
/// </summary>
|
||||
public const string GraphSimulate = "graph:simulate";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to Orchestrator job state and telemetry.
|
||||
/// </summary>
|
||||
public const string OrchRead = "orch:read";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting permission to execute Orchestrator control actions.
|
||||
/// </summary>
|
||||
public const string OrchOperate = "orch:operate";
|
||||
|
||||
/// <summary>
|
||||
/// Scope granting read-only access to Authority tenant catalog APIs.
|
||||
/// </summary>
|
||||
public const string AuthorityTenantsRead = "authority:tenants.read";
|
||||
|
||||
private static readonly HashSet<string> KnownScopes = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
ConcelierJobsTrigger,
|
||||
@@ -126,50 +221,69 @@ public static class StellaOpsScopes
|
||||
AuthorityClientsManage,
|
||||
AuthorityAuditRead,
|
||||
Bypass,
|
||||
UiRead,
|
||||
ExceptionsApprove,
|
||||
AdvisoryRead,
|
||||
AdvisoryIngest,
|
||||
VexRead,
|
||||
VexIngest,
|
||||
AocVerify,
|
||||
SignalsRead,
|
||||
SignalsWrite,
|
||||
SignalsAdmin,
|
||||
PolicyWrite,
|
||||
PolicyAuthor,
|
||||
PolicyEdit,
|
||||
PolicyRead,
|
||||
PolicyReview,
|
||||
PolicySubmit,
|
||||
PolicyApprove,
|
||||
PolicyOperate,
|
||||
PolicyAudit,
|
||||
PolicyRun,
|
||||
PolicyActivate,
|
||||
PolicySimulate,
|
||||
FindingsRead,
|
||||
EffectiveWrite,
|
||||
GraphRead,
|
||||
VulnRead,
|
||||
ExportViewer,
|
||||
ExportOperator,
|
||||
ExportAdmin,
|
||||
GraphWrite,
|
||||
GraphExport,
|
||||
GraphSimulate
|
||||
GraphSimulate,
|
||||
OrchRead,
|
||||
OrchOperate,
|
||||
AuthorityTenantsRead
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Normalises a scope string (trim/convert to lower case).
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope raw value.</param>
|
||||
/// <returns>Normalised scope or <c>null</c> when the input is blank.</returns>
|
||||
public static string? Normalize(string? scope)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(scope))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return scope.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the provided scope is registered as a built-in StellaOps scope.
|
||||
/// </summary>
|
||||
public static bool IsKnown(string scope)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(scope);
|
||||
return KnownScopes.Contains(scope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full set of built-in scopes.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<string> All => KnownScopes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalises a scope string (trim/convert to lower case).
|
||||
/// </summary>
|
||||
/// <param name="scope">Scope raw value.</param>
|
||||
/// <returns>Normalised scope or <c>null</c> when the input is blank.</returns>
|
||||
public static string? Normalize(string? scope)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(scope))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return scope.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the provided scope is registered as a built-in StellaOps scope.
|
||||
/// </summary>
|
||||
public static bool IsKnown(string scope)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(scope);
|
||||
return KnownScopes.Contains(scope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full set of built-in scopes.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<string> All => KnownScopes;
|
||||
}
|
||||
|
||||
@@ -19,4 +19,9 @@ public static class StellaOpsServiceIdentities
|
||||
/// Service identity used by Vuln Explorer when issuing scoped permalink requests.
|
||||
/// </summary>
|
||||
public const string VulnExplorer = "vuln-explorer";
|
||||
|
||||
/// <summary>
|
||||
/// Service identity used by Signals components when managing reachability facts.
|
||||
/// </summary>
|
||||
public const string Signals = "signals";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace StellaOps.Auth.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Shared tenancy default values used across StellaOps services.
|
||||
/// </summary>
|
||||
public static class StellaOpsTenancyDefaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Sentinel value indicating the token is not scoped to a specific project.
|
||||
/// </summary>
|
||||
public const string AnyProject = "*";
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
@@ -12,12 +13,12 @@ public interface IStellaOpsTokenClient
|
||||
/// <summary>
|
||||
/// Requests an access token using the resource owner password credentials flow.
|
||||
/// </summary>
|
||||
Task<StellaOpsTokenResult> RequestPasswordTokenAsync(string username, string password, string? scope = null, CancellationToken cancellationToken = default);
|
||||
Task<StellaOpsTokenResult> RequestPasswordTokenAsync(string username, string password, string? scope = null, IReadOnlyDictionary<string, string>? additionalParameters = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Requests an access token using the client credentials flow.
|
||||
/// </summary>
|
||||
Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(string? scope = null, CancellationToken cancellationToken = default);
|
||||
Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(string? scope = null, IReadOnlyDictionary<string, string>? additionalParameters = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the cached JWKS document.
|
||||
|
||||
@@ -48,7 +48,12 @@ public sealed class StellaOpsTokenClient : IStellaOpsTokenClient
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Task<StellaOpsTokenResult> RequestPasswordTokenAsync(string username, string password, string? scope = null, CancellationToken cancellationToken = default)
|
||||
public Task<StellaOpsTokenResult> RequestPasswordTokenAsync(
|
||||
string username,
|
||||
string password,
|
||||
string? scope = null,
|
||||
IReadOnlyDictionary<string, string>? additionalParameters = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(username);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(password);
|
||||
@@ -70,10 +75,23 @@ public sealed class StellaOpsTokenClient : IStellaOpsTokenClient
|
||||
|
||||
AppendScope(parameters, scope, options);
|
||||
|
||||
if (additionalParameters is not null)
|
||||
{
|
||||
foreach (var (key, value) in additionalParameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
parameters[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return RequestTokenAsync(parameters, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(string? scope = null, CancellationToken cancellationToken = default)
|
||||
public Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(string? scope = null, IReadOnlyDictionary<string, string>? additionalParameters = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var options = optionsMonitor.CurrentValue;
|
||||
if (string.IsNullOrWhiteSpace(options.ClientId))
|
||||
@@ -94,6 +112,19 @@ public sealed class StellaOpsTokenClient : IStellaOpsTokenClient
|
||||
|
||||
AppendScope(parameters, scope, options);
|
||||
|
||||
if (additionalParameters is not null)
|
||||
{
|
||||
foreach (var (key, value) in additionalParameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key) || value is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
parameters[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return RequestTokenAsync(parameters, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ public class ServiceCollectionExtensionsTests
|
||||
Assert.Equal(new Uri("https://authority.example/"), new Uri(jwtOptions.Authority!));
|
||||
Assert.True(jwtOptions.TokenValidationParameters.ValidateAudience);
|
||||
Assert.Contains("api://concelier", jwtOptions.TokenValidationParameters.ValidAudiences);
|
||||
Assert.Equal(TimeSpan.FromSeconds(60), jwtOptions.TokenValidationParameters.ClockSkew);
|
||||
Assert.Equal(new[] { "concelier.jobs.trigger" }, resourceOptions.NormalizedScopes);
|
||||
}
|
||||
}
|
||||
Assert.Equal(TimeSpan.FromSeconds(60), jwtOptions.TokenValidationParameters.ClockSkew);
|
||||
Assert.Equal(new[] { "concelier.jobs.trigger" }, resourceOptions.NormalizedScopes);
|
||||
Assert.IsType<StellaOpsAuthorityConfigurationManager>(jwtOptions.ConfigurationManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public static class ServiceCollectionExtensions
|
||||
services.AddAuthorization();
|
||||
services.AddStellaOpsScopeHandler();
|
||||
services.TryAddSingleton<StellaOpsBypassEvaluator>();
|
||||
services.TryAddSingleton<TimeProvider>(_ => TimeProvider.System);
|
||||
services.AddHttpClient(StellaOpsAuthorityConfigurationManager.HttpClientName);
|
||||
services.AddSingleton<StellaOpsAuthorityConfigurationManager>();
|
||||
|
||||
var optionsBuilder = services.AddOptions<StellaOpsResourceServerOptions>();
|
||||
if (!string.IsNullOrWhiteSpace(configurationSection))
|
||||
@@ -60,7 +63,7 @@ public static class ServiceCollectionExtensions
|
||||
authenticationBuilder.AddJwtBearer(StellaOpsAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
services.AddOptions<JwtBearerOptions>(StellaOpsAuthenticationDefaults.AuthenticationScheme)
|
||||
.Configure<IOptionsMonitor<StellaOpsResourceServerOptions>>((jwt, monitor) =>
|
||||
.Configure<IServiceProvider, IOptionsMonitor<StellaOpsResourceServerOptions>>((jwt, provider, monitor) =>
|
||||
{
|
||||
var resourceOptions = monitor.CurrentValue;
|
||||
|
||||
@@ -81,6 +84,7 @@ public static class ServiceCollectionExtensions
|
||||
jwt.TokenValidationParameters.ClockSkew = resourceOptions.TokenClockSkew;
|
||||
jwt.TokenValidationParameters.NameClaimType = ClaimTypes.Name;
|
||||
jwt.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
|
||||
jwt.ConfigurationManager = provider.GetRequiredService<StellaOpsAuthorityConfigurationManager>();
|
||||
});
|
||||
|
||||
return services;
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace StellaOps.Auth.ServerIntegration;
|
||||
|
||||
/// <summary>
|
||||
/// Cached configuration manager for StellaOps Authority metadata and JWKS.
|
||||
/// </summary>
|
||||
internal sealed class StellaOpsAuthorityConfigurationManager : IConfigurationManager<OpenIdConnectConfiguration>
|
||||
{
|
||||
internal const string HttpClientName = "StellaOps.Auth.ServerIntegration.Metadata";
|
||||
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
private readonly IOptionsMonitor<StellaOpsResourceServerOptions> optionsMonitor;
|
||||
private readonly TimeProvider timeProvider;
|
||||
private readonly ILogger<StellaOpsAuthorityConfigurationManager> logger;
|
||||
private readonly SemaphoreSlim refreshLock = new(1, 1);
|
||||
|
||||
private OpenIdConnectConfiguration? cachedConfiguration;
|
||||
private DateTimeOffset cacheExpiresAt;
|
||||
|
||||
public StellaOpsAuthorityConfigurationManager(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IOptionsMonitor<StellaOpsResourceServerOptions> optionsMonitor,
|
||||
TimeProvider timeProvider,
|
||||
ILogger<StellaOpsAuthorityConfigurationManager> logger)
|
||||
{
|
||||
this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
this.optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor));
|
||||
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
public async Task<OpenIdConnectConfiguration> GetConfigurationAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var now = timeProvider.GetUtcNow();
|
||||
var current = Volatile.Read(ref cachedConfiguration);
|
||||
if (current is not null && now < cacheExpiresAt)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
await refreshLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (cachedConfiguration is not null && now < cacheExpiresAt)
|
||||
{
|
||||
return cachedConfiguration;
|
||||
}
|
||||
|
||||
var options = optionsMonitor.CurrentValue;
|
||||
var metadataAddress = ResolveMetadataAddress(options);
|
||||
var httpClient = httpClientFactory.CreateClient(HttpClientName);
|
||||
httpClient.Timeout = options.BackchannelTimeout;
|
||||
|
||||
var retriever = new HttpDocumentRetriever(httpClient)
|
||||
{
|
||||
RequireHttps = options.RequireHttpsMetadata
|
||||
};
|
||||
|
||||
logger.LogDebug("Fetching OpenID Connect configuration from {MetadataAddress}.", metadataAddress);
|
||||
|
||||
var configuration = await OpenIdConnectConfigurationRetriever.GetAsync(metadataAddress, retriever, cancellationToken).ConfigureAwait(false);
|
||||
configuration.Issuer ??= options.AuthorityUri.ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(configuration.JwksUri))
|
||||
{
|
||||
logger.LogDebug("Fetching JWKS from {JwksUri}.", configuration.JwksUri);
|
||||
var jwksDocument = await retriever.GetDocumentAsync(configuration.JwksUri, cancellationToken).ConfigureAwait(false);
|
||||
var jsonWebKeySet = new JsonWebKeySet(jwksDocument);
|
||||
configuration.SigningKeys.Clear();
|
||||
foreach (JsonWebKey key in jsonWebKeySet.Keys)
|
||||
{
|
||||
configuration.SigningKeys.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
cachedConfiguration = configuration;
|
||||
cacheExpiresAt = now + options.MetadataCacheLifetime;
|
||||
return configuration;
|
||||
}
|
||||
finally
|
||||
{
|
||||
refreshLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestRefresh()
|
||||
{
|
||||
Volatile.Write(ref cachedConfiguration, null);
|
||||
cacheExpiresAt = DateTimeOffset.MinValue;
|
||||
}
|
||||
|
||||
private static string ResolveMetadataAddress(StellaOpsResourceServerOptions options)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(options.MetadataAddress))
|
||||
{
|
||||
return options.MetadataAddress;
|
||||
}
|
||||
|
||||
var authority = options.AuthorityUri;
|
||||
if (!authority.AbsoluteUri.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
authority = new Uri(authority.AbsoluteUri + "/", UriKind.Absolute);
|
||||
}
|
||||
|
||||
return new Uri(authority, ".well-known/openid-configuration").AbsoluteUri;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,11 @@ public sealed class StellaOpsResourceServerOptions
|
||||
/// </summary>
|
||||
public TimeSpan TokenClockSkew { get; set; } = TimeSpan.FromSeconds(60);
|
||||
|
||||
/// <summary>
|
||||
/// Lifetime for cached discovery/JWKS metadata before forcing a refresh.
|
||||
/// </summary>
|
||||
public TimeSpan MetadataCacheLifetime { get; set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the canonical Authority URI (populated during validation).
|
||||
/// </summary>
|
||||
@@ -112,6 +117,11 @@ public sealed class StellaOpsResourceServerOptions
|
||||
throw new InvalidOperationException("Resource server token clock skew must be between 0 seconds and 5 minutes.");
|
||||
}
|
||||
|
||||
if (MetadataCacheLifetime <= TimeSpan.Zero || MetadataCacheLifetime > TimeSpan.FromHours(24))
|
||||
{
|
||||
throw new InvalidOperationException("Resource server metadata cache lifetime must be greater than zero and less than or equal to 24 hours.");
|
||||
}
|
||||
|
||||
AuthorityUri = authorityUri;
|
||||
|
||||
NormalizeList(audiences, toLower: false);
|
||||
|
||||
@@ -12,5 +12,6 @@ public static class AuthorityClientMetadataKeys
|
||||
public const string PostLogoutRedirectUris = "postLogoutRedirectUris";
|
||||
public const string SenderConstraint = "senderConstraint";
|
||||
public const string Tenant = "tenant";
|
||||
public const string Project = "project";
|
||||
public const string ServiceIdentity = "serviceIdentity";
|
||||
}
|
||||
|
||||
@@ -652,12 +652,18 @@ public sealed record AuthorityClientDescriptor
|
||||
AllowedAudiences = Normalize(allowedAudiences);
|
||||
RedirectUris = redirectUris is null ? Array.Empty<Uri>() : redirectUris.ToArray();
|
||||
PostLogoutRedirectUris = postLogoutRedirectUris is null ? Array.Empty<Uri>() : postLogoutRedirectUris.ToArray();
|
||||
Properties = properties is null
|
||||
var propertyBag = properties is null
|
||||
? new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
: new Dictionary<string, string?>(properties, StringComparer.OrdinalIgnoreCase);
|
||||
Tenant = Properties.TryGetValue(AuthorityClientMetadataKeys.Tenant, out var tenantValue)
|
||||
Tenant = propertyBag.TryGetValue(AuthorityClientMetadataKeys.Tenant, out var tenantValue)
|
||||
? AuthorityClientRegistration.NormalizeTenantValue(tenantValue)
|
||||
: null;
|
||||
var normalizedProject = propertyBag.TryGetValue(AuthorityClientMetadataKeys.Project, out var projectValue)
|
||||
? AuthorityClientRegistration.NormalizeProjectValue(projectValue)
|
||||
: null;
|
||||
Project = normalizedProject ?? StellaOpsTenancyDefaults.AnyProject;
|
||||
propertyBag[AuthorityClientMetadataKeys.Project] = Project;
|
||||
Properties = propertyBag;
|
||||
}
|
||||
|
||||
public string ClientId { get; }
|
||||
@@ -669,6 +675,7 @@ public sealed record AuthorityClientDescriptor
|
||||
public IReadOnlyCollection<Uri> RedirectUris { get; }
|
||||
public IReadOnlyCollection<Uri> PostLogoutRedirectUris { get; }
|
||||
public string? Tenant { get; }
|
||||
public string? Project { get; }
|
||||
public IReadOnlyDictionary<string, string?> Properties { get; }
|
||||
|
||||
private static IReadOnlyCollection<string> Normalize(IReadOnlyCollection<string>? values)
|
||||
@@ -781,6 +788,7 @@ public sealed record AuthorityClientRegistration
|
||||
IReadOnlyCollection<Uri>? redirectUris = null,
|
||||
IReadOnlyCollection<Uri>? postLogoutRedirectUris = null,
|
||||
string? tenant = null,
|
||||
string? project = null,
|
||||
IReadOnlyDictionary<string, string?>? properties = null,
|
||||
IReadOnlyCollection<AuthorityClientCertificateBindingRegistration>? certificateBindings = null)
|
||||
{
|
||||
@@ -796,9 +804,13 @@ public sealed record AuthorityClientRegistration
|
||||
RedirectUris = redirectUris is null ? Array.Empty<Uri>() : redirectUris.ToArray();
|
||||
PostLogoutRedirectUris = postLogoutRedirectUris is null ? Array.Empty<Uri>() : postLogoutRedirectUris.ToArray();
|
||||
Tenant = NormalizeTenantValue(tenant);
|
||||
Properties = properties is null
|
||||
var propertyBag = properties is null
|
||||
? new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
: new Dictionary<string, string?>(properties, StringComparer.OrdinalIgnoreCase);
|
||||
var normalizedProject = NormalizeProjectValue(project ?? (propertyBag.TryGetValue(AuthorityClientMetadataKeys.Project, out var projectValue) ? projectValue : null));
|
||||
Project = normalizedProject ?? StellaOpsTenancyDefaults.AnyProject;
|
||||
propertyBag[AuthorityClientMetadataKeys.Project] = Project;
|
||||
Properties = propertyBag;
|
||||
CertificateBindings = certificateBindings is null
|
||||
? Array.Empty<AuthorityClientCertificateBindingRegistration>()
|
||||
: certificateBindings.ToArray();
|
||||
@@ -814,11 +826,12 @@ public sealed record AuthorityClientRegistration
|
||||
public IReadOnlyCollection<Uri> RedirectUris { get; }
|
||||
public IReadOnlyCollection<Uri> PostLogoutRedirectUris { get; }
|
||||
public string? Tenant { get; }
|
||||
public string? Project { get; }
|
||||
public IReadOnlyDictionary<string, string?> Properties { get; }
|
||||
public IReadOnlyCollection<AuthorityClientCertificateBindingRegistration> CertificateBindings { get; }
|
||||
|
||||
public AuthorityClientRegistration WithClientSecret(string? clientSecret)
|
||||
=> new(ClientId, Confidential, DisplayName, clientSecret, AllowedGrantTypes, AllowedScopes, AllowedAudiences, RedirectUris, PostLogoutRedirectUris, Tenant, Properties, CertificateBindings);
|
||||
=> new(ClientId, Confidential, DisplayName, clientSecret, AllowedGrantTypes, AllowedScopes, AllowedAudiences, RedirectUris, PostLogoutRedirectUris, Tenant, Project, Properties, CertificateBindings);
|
||||
|
||||
private static IReadOnlyCollection<string> Normalize(IReadOnlyCollection<string>? values)
|
||||
=> values is null || values.Count == 0
|
||||
@@ -867,6 +880,16 @@ public sealed record AuthorityClientRegistration
|
||||
return value.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
internal static string? NormalizeProjectValue(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string ValidateRequired(string value, string paramName)
|
||||
=> string.IsNullOrWhiteSpace(value)
|
||||
? throw new ArgumentException("Value cannot be null or whitespace.", paramName)
|
||||
|
||||
@@ -5,7 +5,14 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<GenerateMSBuildEditorConfigFile>false</GenerateMSBuildEditorConfigFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EditorConfigFiles Remove="$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildEditorConfig.editorconfig" />
|
||||
</ItemGroup>
|
||||
<Target Name="EnsureGeneratedEditorConfig" BeforeTargets="ResolveEditorConfigFiles">
|
||||
<WriteLinesToFile File="$(IntermediateOutputPath)$(MSBuildProjectName).GeneratedMSBuildEditorConfig.editorconfig" Lines="" Overwrite="false" />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
|
||||
@@ -78,6 +78,10 @@ public sealed class AuthorityTokenDocument
|
||||
[BsonIgnoreIfNull]
|
||||
public string? Tenant { get; set; }
|
||||
|
||||
[BsonElement("project")]
|
||||
[BsonIgnoreIfNull]
|
||||
public string? Project { get; set; }
|
||||
|
||||
[BsonElement("devices")]
|
||||
[BsonIgnoreIfNull]
|
||||
public List<BsonDocument>? Devices { get; set; }
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using OpenIddict.Abstractions;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Authority.Console;
|
||||
using StellaOps.Authority.Tenants;
|
||||
using StellaOps.Cryptography.Audit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Authority.Tests.Console;
|
||||
|
||||
public sealed class ConsoleEndpointsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Tenants_ReturnsTenant_WhenHeaderMatchesClaim()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-31T12:00:00Z"));
|
||||
var sink = new RecordingAuthEventSink();
|
||||
await using var app = await CreateApplicationAsync(timeProvider, sink, new AuthorityTenantView("tenant-default", "Default", "active", "shared", Array.Empty<string>(), Array.Empty<string>()));
|
||||
|
||||
var accessor = app.Services.GetRequiredService<TestPrincipalAccessor>();
|
||||
accessor.Principal = CreatePrincipal(
|
||||
tenant: "tenant-default",
|
||||
scopes: new[] { StellaOpsScopes.UiRead, StellaOpsScopes.AuthorityTenantsRead },
|
||||
expiresAt: timeProvider.GetUtcNow().AddMinutes(5));
|
||||
|
||||
var client = app.CreateTestClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||
|
||||
var response = await client.GetAsync("/console/tenants");
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
using var json = JsonDocument.Parse(payload);
|
||||
var tenants = json.RootElement.GetProperty("tenants");
|
||||
Assert.Equal(1, tenants.GetArrayLength());
|
||||
Assert.Equal("tenant-default", tenants[0].GetProperty("id").GetString());
|
||||
|
||||
var audit = Assert.Single(sink.Events);
|
||||
Assert.Equal("authority.console.tenants.read", audit.EventType);
|
||||
Assert.Equal(AuthEventOutcome.Success, audit.Outcome);
|
||||
Assert.Contains("tenant.resolved", audit.Properties.Select(property => property.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Tenants_ReturnsBadRequest_WhenHeaderMissing()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-31T12:00:00Z"));
|
||||
var sink = new RecordingAuthEventSink();
|
||||
await using var app = await CreateApplicationAsync(timeProvider, sink, new AuthorityTenantView("tenant-default", "Default", "active", "shared", Array.Empty<string>(), Array.Empty<string>()));
|
||||
|
||||
var accessor = app.Services.GetRequiredService<TestPrincipalAccessor>();
|
||||
accessor.Principal = CreatePrincipal(
|
||||
tenant: "tenant-default",
|
||||
scopes: new[] { StellaOpsScopes.UiRead, StellaOpsScopes.AuthorityTenantsRead },
|
||||
expiresAt: timeProvider.GetUtcNow().AddMinutes(5));
|
||||
|
||||
var client = app.CreateTestClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
var response = await client.GetAsync("/console/tenants");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
Assert.Empty(sink.Events);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Tenants_ReturnsForbid_WhenHeaderDoesNotMatchClaim()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-31T12:00:00Z"));
|
||||
var sink = new RecordingAuthEventSink();
|
||||
await using var app = await CreateApplicationAsync(timeProvider, sink, new AuthorityTenantView("tenant-default", "Default", "active", "shared", Array.Empty<string>(), Array.Empty<string>()));
|
||||
|
||||
var accessor = app.Services.GetRequiredService<TestPrincipalAccessor>();
|
||||
accessor.Principal = CreatePrincipal(
|
||||
tenant: "tenant-default",
|
||||
scopes: new[] { StellaOpsScopes.UiRead, StellaOpsScopes.AuthorityTenantsRead },
|
||||
expiresAt: timeProvider.GetUtcNow().AddMinutes(5));
|
||||
|
||||
var client = app.CreateTestClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "other-tenant");
|
||||
|
||||
var response = await client.GetAsync("/console/tenants");
|
||||
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
|
||||
Assert.Empty(sink.Events);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Profile_ReturnsProfileMetadata()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-31T12:00:00Z"));
|
||||
var sink = new RecordingAuthEventSink();
|
||||
await using var app = await CreateApplicationAsync(timeProvider, sink, new AuthorityTenantView("tenant-default", "Default", "active", "shared", Array.Empty<string>(), Array.Empty<string>()));
|
||||
|
||||
var principal = CreatePrincipal(
|
||||
tenant: "tenant-default",
|
||||
scopes: new[] { StellaOpsScopes.UiRead, StellaOpsScopes.AuthorityTenantsRead },
|
||||
expiresAt: timeProvider.GetUtcNow().AddMinutes(5),
|
||||
issuedAt: timeProvider.GetUtcNow().AddMinutes(-1),
|
||||
authenticationTime: timeProvider.GetUtcNow().AddMinutes(-1),
|
||||
subject: "user-123",
|
||||
username: "console-user",
|
||||
displayName: "Console User");
|
||||
|
||||
var accessor = app.Services.GetRequiredService<TestPrincipalAccessor>();
|
||||
accessor.Principal = principal;
|
||||
|
||||
var client = app.CreateTestClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||
|
||||
var response = await client.GetAsync("/console/profile");
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
using var json = JsonDocument.Parse(payload);
|
||||
Assert.Equal("user-123", json.RootElement.GetProperty("subjectId").GetString());
|
||||
Assert.Equal("console-user", json.RootElement.GetProperty("username").GetString());
|
||||
Assert.Equal("tenant-default", json.RootElement.GetProperty("tenant").GetString());
|
||||
|
||||
var audit = Assert.Single(sink.Events);
|
||||
Assert.Equal("authority.console.profile.read", audit.EventType);
|
||||
Assert.Equal(AuthEventOutcome.Success, audit.Outcome);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TokenIntrospect_FlagsInactive_WhenExpired()
|
||||
{
|
||||
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-31T12:00:00Z"));
|
||||
var sink = new RecordingAuthEventSink();
|
||||
await using var app = await CreateApplicationAsync(timeProvider, sink, new AuthorityTenantView("tenant-default", "Default", "active", "shared", Array.Empty<string>(), Array.Empty<string>()));
|
||||
|
||||
var principal = CreatePrincipal(
|
||||
tenant: "tenant-default",
|
||||
scopes: new[] { StellaOpsScopes.UiRead, StellaOpsScopes.AuthorityTenantsRead },
|
||||
expiresAt: timeProvider.GetUtcNow().AddMinutes(-1),
|
||||
issuedAt: timeProvider.GetUtcNow().AddMinutes(-10),
|
||||
tokenId: "token-abc");
|
||||
|
||||
var accessor = app.Services.GetRequiredService<TestPrincipalAccessor>();
|
||||
accessor.Principal = principal;
|
||||
|
||||
var client = app.CreateTestClient();
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||
|
||||
var response = await client.PostAsync("/console/token/introspect", content: null);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync();
|
||||
using var json = JsonDocument.Parse(payload);
|
||||
Assert.False(json.RootElement.GetProperty("active").GetBoolean());
|
||||
Assert.Equal("token-abc", json.RootElement.GetProperty("tokenId").GetString());
|
||||
|
||||
var audit = Assert.Single(sink.Events);
|
||||
Assert.Equal("authority.console.token.introspect", audit.EventType);
|
||||
Assert.Equal(AuthEventOutcome.Success, audit.Outcome);
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal CreatePrincipal(
|
||||
string tenant,
|
||||
IReadOnlyCollection<string> scopes,
|
||||
DateTimeOffset expiresAt,
|
||||
DateTimeOffset? issuedAt = null,
|
||||
DateTimeOffset? authenticationTime = null,
|
||||
string? subject = null,
|
||||
string? username = null,
|
||||
string? displayName = null,
|
||||
string? tokenId = null)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(StellaOpsClaimTypes.Tenant, tenant),
|
||||
new(StellaOpsClaimTypes.Scope, string.Join(' ', scopes)),
|
||||
new("exp", expiresAt.ToUnixTimeSeconds().ToString()),
|
||||
new(OpenIddictConstants.Claims.Audience, "console")
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(subject))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.Subject, subject));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
claims.Add(new Claim(OpenIddictConstants.Claims.PreferredUsername, username));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(displayName))
|
||||
{
|
||||
claims.Add(new Claim(OpenIddictConstants.Claims.Name, displayName));
|
||||
}
|
||||
|
||||
if (issuedAt is not null)
|
||||
{
|
||||
claims.Add(new Claim("iat", issuedAt.Value.ToUnixTimeSeconds().ToString()));
|
||||
}
|
||||
|
||||
if (authenticationTime is not null)
|
||||
{
|
||||
claims.Add(new Claim("auth_time", authenticationTime.Value.ToUnixTimeSeconds().ToString()));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tokenId))
|
||||
{
|
||||
claims.Add(new Claim(StellaOpsClaimTypes.TokenId, tokenId));
|
||||
}
|
||||
|
||||
var identity = new ClaimsIdentity(claims, TestAuthenticationDefaults.AuthenticationScheme);
|
||||
return new ClaimsPrincipal(identity);
|
||||
}
|
||||
|
||||
private static async Task<WebApplication> CreateApplicationAsync(
|
||||
FakeTimeProvider timeProvider,
|
||||
RecordingAuthEventSink sink,
|
||||
params AuthorityTenantView[] tenants)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||
{
|
||||
EnvironmentName = Environments.Development
|
||||
});
|
||||
builder.WebHost.UseTestServer();
|
||||
|
||||
builder.Services.AddSingleton<TimeProvider>(timeProvider);
|
||||
builder.Services.AddSingleton<IAuthEventSink>(sink);
|
||||
builder.Services.AddSingleton<IAuthorityTenantCatalog>(new FakeTenantCatalog(tenants));
|
||||
builder.Services.AddSingleton<TestPrincipalAccessor>();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddSingleton<StellaOpsBypassEvaluator>();
|
||||
|
||||
var authBuilder = builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = TestAuthenticationDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = TestAuthenticationDefaults.AuthenticationScheme;
|
||||
});
|
||||
|
||||
authBuilder.AddScheme<AuthenticationSchemeOptions, TestAuthenticationHandler>(TestAuthenticationDefaults.AuthenticationScheme, static _ => { });
|
||||
authBuilder.AddScheme<AuthenticationSchemeOptions, TestAuthenticationHandler>(StellaOpsAuthenticationDefaults.AuthenticationScheme, static _ => { });
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddStellaOpsScopeHandler();
|
||||
|
||||
builder.Services.AddOptions<StellaOpsResourceServerOptions>()
|
||||
.Configure(options =>
|
||||
{
|
||||
options.Authority = "https://authority.integration.test";
|
||||
})
|
||||
.PostConfigure(static options => options.Validate());
|
||||
|
||||
var app = builder.Build();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapConsoleEndpoints();
|
||||
|
||||
await app.StartAsync().ConfigureAwait(false);
|
||||
return app;
|
||||
}
|
||||
|
||||
private sealed class FakeTenantCatalog : IAuthorityTenantCatalog
|
||||
{
|
||||
private readonly IReadOnlyList<AuthorityTenantView> tenants;
|
||||
|
||||
public FakeTenantCatalog(IEnumerable<AuthorityTenantView> tenants)
|
||||
{
|
||||
this.tenants = tenants.ToArray();
|
||||
}
|
||||
|
||||
public IReadOnlyList<AuthorityTenantView> GetTenants() => tenants;
|
||||
}
|
||||
|
||||
private sealed class RecordingAuthEventSink : IAuthEventSink
|
||||
{
|
||||
public List<AuthEventRecord> Events { get; } = new();
|
||||
|
||||
public ValueTask WriteAsync(AuthEventRecord record, CancellationToken cancellationToken)
|
||||
{
|
||||
Events.Add(record);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestPrincipalAccessor
|
||||
{
|
||||
public ClaimsPrincipal? Principal { get; set; }
|
||||
}
|
||||
|
||||
private sealed class TestAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
public TestAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
ISystemClock clock)
|
||||
: base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var accessor = Context.RequestServices.GetRequiredService<TestPrincipalAccessor>();
|
||||
if (accessor.Principal is null)
|
||||
{
|
||||
return Task.FromResult(AuthenticateResult.Fail("No principal configured."));
|
||||
}
|
||||
|
||||
var ticket = new AuthenticationTicket(accessor.Principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class HostTestClientExtensions
|
||||
{
|
||||
public static HttpClient CreateTestClient(this WebApplication app)
|
||||
{
|
||||
var server = app.Services.GetRequiredService<IServer>() as TestServer
|
||||
?? throw new InvalidOperationException("TestServer is not available. Ensure UseTestServer() is configured.");
|
||||
return server.CreateClient();
|
||||
}
|
||||
}
|
||||
internal static class TestAuthenticationDefaults
|
||||
{
|
||||
public const string AuthenticationScheme = "AuthorityConsoleTests";
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user