Add SBOM, symbols, traces, and VEX files for CVE-2022-21661 SQLi case
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created CycloneDX and SPDX SBOM files for both reachable and unreachable images. - Added symbols.json detailing function entry and sink points in the WordPress code. - Included runtime traces for function calls in both reachable and unreachable scenarios. - Developed OpenVEX files indicating vulnerability status and justification for both cases. - Updated README for evaluator harness to guide integration with scanner output.
This commit is contained in:
@@ -21,6 +21,8 @@ on:
|
|||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
- 'scripts/**'
|
- 'scripts/**'
|
||||||
- '.gitea/workflows/**'
|
- '.gitea/workflows/**'
|
||||||
|
schedule:
|
||||||
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
force_deploy:
|
force_deploy:
|
||||||
@@ -28,6 +30,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
type: boolean
|
type: boolean
|
||||||
|
excititor_batch:
|
||||||
|
description: 'Run Excititor batch-ingest validation suite'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
DOTNET_VERSION: '10.0.100-rc.1.25451.107'
|
||||||
@@ -48,6 +55,18 @@ jobs:
|
|||||||
tar -xzf /tmp/helm.tgz -C /tmp
|
tar -xzf /tmp/helm.tgz -C /tmp
|
||||||
sudo install -m 0755 /tmp/linux-amd64/helm /usr/local/bin/helm
|
sudo install -m 0755 /tmp/linux-amd64/helm /usr/local/bin/helm
|
||||||
|
|
||||||
|
- name: Validate Helm chart rendering
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
CHART_PATH="deploy/helm/stellaops"
|
||||||
|
helm lint "$CHART_PATH"
|
||||||
|
for values in values.yaml values-dev.yaml values-stage.yaml values-prod.yaml values-airgap.yaml values-mirror.yaml; do
|
||||||
|
release="stellaops-${values%.*}"
|
||||||
|
echo "::group::Helm template ${release} (${values})"
|
||||||
|
helm template "$release" "$CHART_PATH" -f "$CHART_PATH/$values" >/dev/null
|
||||||
|
echo "::endgroup::"
|
||||||
|
done
|
||||||
|
|
||||||
- name: Validate deployment profiles
|
- name: Validate deployment profiles
|
||||||
run: ./deploy/tools/validate-profiles.sh
|
run: ./deploy/tools/validate-profiles.sh
|
||||||
|
|
||||||
@@ -442,6 +461,15 @@ PY
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Run console endpoint tests
|
||||||
|
run: |
|
||||||
|
mkdir -p "$TEST_RESULTS_DIR"
|
||||||
|
dotnet test src/Authority/StellaOps.Authority/StellaOps.Authority.Tests/StellaOps.Authority.Tests.csproj \
|
||||||
|
--configuration $BUILD_CONFIGURATION \
|
||||||
|
--logger "trx;LogFileName=console-endpoints.trx" \
|
||||||
|
--results-directory "$TEST_RESULTS_DIR" \
|
||||||
|
--filter ConsoleEndpointsTests
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -451,6 +479,44 @@ PY
|
|||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
sealed-mode-ci:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: build-test
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: read
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT_NAME: sealedmode
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Login to registry
|
||||||
|
if: ${{ secrets.REGISTRY_USERNAME != '' && secrets.REGISTRY_PASSWORD != '' }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: registry.stella-ops.org
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Run sealed-mode CI harness
|
||||||
|
working-directory: ops/devops/sealed-mode-ci
|
||||||
|
env:
|
||||||
|
COMPOSE_PROJECT_NAME: sealedmode
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
./run-sealed-ci.sh
|
||||||
|
|
||||||
|
- name: Upload sealed-mode CI artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: sealed-mode-ci
|
||||||
|
path: ops/devops/sealed-mode-ci/artifacts/sealed-mode-ci
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
authority-container:
|
authority-container:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: build-test
|
needs: build-test
|
||||||
@@ -464,6 +530,41 @@ PY
|
|||||||
- name: Build Authority container image
|
- name: Build Authority container image
|
||||||
run: docker build -f ops/authority/Dockerfile -t stellaops-authority:ci .
|
run: docker build -f ops/authority/Dockerfile -t stellaops-authority:ci .
|
||||||
|
|
||||||
|
excititor-batch-validation:
|
||||||
|
needs: build-test
|
||||||
|
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.excititor_batch == 'true')
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
BATCH_RESULTS_DIR: ${{ github.workspace }}/artifacts/test-results/excititor-batch
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
||||||
|
include-prerelease: true
|
||||||
|
|
||||||
|
- name: Run Excititor batch ingest validation suite
|
||||||
|
env:
|
||||||
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p "$BATCH_RESULTS_DIR"
|
||||||
|
dotnet test src/Excititor/__Tests/StellaOps.Excititor.WebService.Tests/StellaOps.Excititor.WebService.Tests.csproj \
|
||||||
|
--configuration $BUILD_CONFIGURATION \
|
||||||
|
--filter "Category=BatchIngestValidation" \
|
||||||
|
--logger "trx;LogFileName=excititor-batch.trx" \
|
||||||
|
--results-directory "$BATCH_RESULTS_DIR"
|
||||||
|
|
||||||
|
- name: Upload Excititor batch ingest results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: excititor-batch-ingest-results
|
||||||
|
path: ${{ env.BATCH_RESULTS_DIR }}
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{{- $root := . -}}
|
{{- $root := . -}}
|
||||||
|
{{- $configMaps := default (dict) .Values.configMaps -}}
|
||||||
|
{{- $hasPolicyActivationConfig := hasKey $configMaps "policy-engine-activation" -}}
|
||||||
|
{{- $policyActivationConfigName := "" -}}
|
||||||
|
{{- if $hasPolicyActivationConfig -}}
|
||||||
|
{{- $policyActivationConfigName = include "stellaops.fullname" (dict "root" $root "name" "policy-engine-activation") -}}
|
||||||
|
{{- end -}}
|
||||||
|
{{- $policyActivationTargets := dict "policy-engine" true "policy-gateway" true -}}
|
||||||
{{- range $name, $svc := .Values.services }}
|
{{- range $name, $svc := .Values.services }}
|
||||||
{{- $configMounts := (default (list) $svc.configMounts) }}
|
{{- $configMounts := (default (list) $svc.configMounts) }}
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -43,9 +50,22 @@ spec:
|
|||||||
value: {{ $envValue | quote }}
|
value: {{ $envValue | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.envFrom }}
|
{{- $needsPolicyActivation := and $hasPolicyActivationConfig (hasKey $policyActivationTargets $name) }}
|
||||||
|
{{- $envFrom := default (list) $svc.envFrom }}
|
||||||
|
{{- if and $needsPolicyActivation (ne $policyActivationConfigName "") }}
|
||||||
|
{{- $hasActivationReference := false }}
|
||||||
|
{{- range $envFromEntry := $envFrom }}
|
||||||
|
{{- if and (hasKey $envFromEntry "configMapRef") (eq (index (index $envFromEntry "configMapRef") "name") $policyActivationConfigName) }}
|
||||||
|
{{- $hasActivationReference = true }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $hasActivationReference }}
|
||||||
|
{{- $envFrom = append $envFrom (dict "configMapRef" (dict "name" $policyActivationConfigName)) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $envFrom }}
|
||||||
envFrom:
|
envFrom:
|
||||||
{{ toYaml $svc.envFrom | nindent 12 }}
|
{{ toYaml $envFrom | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if $svc.ports }}
|
{{- if $svc.ports }}
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ configMaps:
|
|||||||
telemetry:
|
telemetry:
|
||||||
enableRequestLogging: true
|
enableRequestLogging: true
|
||||||
minimumLogLevel: Warning
|
minimumLogLevel: Warning
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
authority:
|
authority:
|
||||||
image: registry.stella-ops.org/stellaops/authority@sha256:5551a3269b7008cd5aceecf45df018c67459ed519557ccbe48b093b926a39bcc
|
image: registry.stella-ops.org/stellaops/authority@sha256:5551a3269b7008cd5aceecf45df018c67459ed519557ccbe48b093b926a39bcc
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ configMaps:
|
|||||||
telemetry:
|
telemetry:
|
||||||
enableRequestLogging: true
|
enableRequestLogging: true
|
||||||
minimumLogLevel: Debug
|
minimumLogLevel: Debug
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "false"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "false"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
services:
|
services:
|
||||||
authority:
|
authority:
|
||||||
image: registry.stella-ops.org/stellaops/authority@sha256:a8e8faec44a579aa5714e58be835f25575710430b1ad2ccd1282a018cd9ffcdd
|
image: registry.stella-ops.org/stellaops/authority@sha256:a8e8faec44a579aa5714e58be835f25575710430b1ad2ccd1282a018cd9ffcdd
|
||||||
|
|||||||
@@ -110,6 +110,13 @@ configMaps:
|
|||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
concelier:
|
concelier:
|
||||||
image: registry.stella-ops.org/stellaops/concelier@sha256:dafef3954eb4b837e2c424dd2d23e1e4d60fa83794840fac9cd3dea1d43bd085
|
image: registry.stella-ops.org/stellaops/concelier@sha256:dafef3954eb4b837e2c424dd2d23e1e4d60fa83794840fac9cd3dea1d43bd085
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ configMaps:
|
|||||||
telemetry:
|
telemetry:
|
||||||
enableRequestLogging: true
|
enableRequestLogging: true
|
||||||
minimumLogLevel: Information
|
minimumLogLevel: Information
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
services:
|
services:
|
||||||
authority:
|
authority:
|
||||||
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
|
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ configMaps:
|
|||||||
telemetry:
|
telemetry:
|
||||||
enableRequestLogging: true
|
enableRequestLogging: true
|
||||||
minimumLogLevel: Information
|
minimumLogLevel: Information
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "true"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
services:
|
services:
|
||||||
authority:
|
authority:
|
||||||
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
|
image: registry.stella-ops.org/stellaops/authority@sha256:b0348bad1d0b401cc3c71cb40ba034c8043b6c8874546f90d4783c9dbfcc0bf5
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ configMaps:
|
|||||||
issuerTrustCollection: issuer_trust_overrides
|
issuerTrustCollection: issuer_trust_overrides
|
||||||
auditCollection: issuer_audit
|
auditCollection: issuer_audit
|
||||||
|
|
||||||
|
policy-engine-activation:
|
||||||
|
data:
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__FORCETWOPERSONAPPROVAL: "false"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__DEFAULTREQUIRESTWOPERSONAPPROVAL: "false"
|
||||||
|
STELLAOPS_POLICY_ENGINE__ACTIVATION__EMITAUDITLOGS: "true"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
issuer-directory:
|
issuer-directory:
|
||||||
image: registry.stella-ops.org/stellaops/issuer-directory-web:2025.10.0-edge
|
image: registry.stella-ops.org/stellaops/issuer-directory-web:2025.10.0-edge
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ These registrations are provided as examples in `etc/authority.yaml.sample`. Clo
|
|||||||
|
|
||||||
- **Interactive only.** `policy:publish` and `policy:promote` are restricted to password/device-code flows (Console, CLI) and are rejected when requested via client credentials or app secrets. Tokens inherit the 5-minute fresh-auth window; resource servers reject stale tokens and emit `authority.policy_attestation_validated=false`.
|
- **Interactive only.** `policy:publish` and `policy:promote` are restricted to password/device-code flows (Console, CLI) and are rejected when requested via client credentials or app secrets. Tokens inherit the 5-minute fresh-auth window; resource servers reject stale tokens and emit `authority.policy_attestation_validated=false`.
|
||||||
- **Mandatory parameters.** Requests must include:
|
- **Mandatory parameters.** Requests must include:
|
||||||
- `policy_reason` (≤512 chars) — human-readable justification (e.g., “Promote tenant A baseline to production”).
|
- Authority enforces mTLS bindings on /token, /fresh-auth, and /introspect by comparing the presented TLS client certificate thumbprint against the stored claim. Requests missing a certificate or presenting a different certificate are rejected with , and the counter is incremented for operational alerts.
|
||||||
- `policy_ticket` (≤128 chars) — change request / CAB identifier (e.g., `CR-2025-1102`).
|
- `policy_ticket` (≤128 chars) — change request / CAB identifier (e.g., `CR-2025-1102`).
|
||||||
- `policy_digest` — lowercase hex digest (32–128 characters) of the policy bundle being published/promoted.
|
- `policy_digest` — lowercase hex digest (32–128 characters) of the policy bundle being published/promoted.
|
||||||
- **Audit surfaces.** On success, the metadata is copied into the access token (`stellaops:policy_reason`, `stellaops:policy_ticket`, `stellaops:policy_digest`, `stellaops:policy_operation`) and recorded in [`authority.password.grant`] audit events as `policy.*` properties.
|
- **Audit surfaces.** On success, the metadata is copied into the access token (`stellaops:policy_reason`, `stellaops:policy_ticket`, `stellaops:policy_digest`, `stellaops:policy_operation`) and recorded in [`authority.password.grant`] audit events as `policy.*` properties.
|
||||||
@@ -142,6 +142,14 @@ These registrations are provided as examples in `etc/authority.yaml.sample`. Clo
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
### Policy activation dual-control
|
||||||
|
|
||||||
|
- **Config knobs.** `PolicyEngine.activation.forceTwoPersonApproval` forces every activation to collect two distinct `policy:activate` approvals (first response = `202 pending_second_approval`). `PolicyEngine.activation.defaultRequiresTwoPersonApproval` sets the default when callers omit the flag.
|
||||||
|
- **Operator choice.** When force is disabled, Console/CLI can opt any revision into dual-control by setting `requiresTwoPersonApproval: true`; the service persists the requirement alongside the revision metadata.
|
||||||
|
- **Audit coverage.** With `PolicyEngine.activation.emitAuditLogs` (default `true`), every activation emits structured `policy.activation.*` logger scopes (pack id, revision, actor(s), tenant, approval count, comment) so SOC pipelines can diff the two-person trail.
|
||||||
|
- **Status codes.** First approval on a dual-control revision returns `202 pending_second_approval`; duplicates produce `400 duplicate_approval`; the second distinct actor returns the usual `200 activated`.
|
||||||
|
|
||||||
|
|
||||||
#### Least-privilege guidance for graph clients
|
#### Least-privilege guidance for graph clients
|
||||||
|
|
||||||
- **Service identities** – The Cartographer worker should request `graph:write` and `graph:read` only; grant `graph:simulate` exclusively to pipeline automation that invokes Policy Engine overlays on demand. Keep `graph:export` scoped to API gateway components responsible for streaming GraphML/JSONL artifacts. Authority enforces this by rejecting `graph:write` tokens that lack `properties.serviceIdentity: cartographer`.
|
- **Service identities** – The Cartographer worker should request `graph:write` and `graph:read` only; grant `graph:simulate` exclusively to pipeline automation that invokes Policy Engine overlays on demand. Keep `graph:export` scoped to API gateway components responsible for streaming GraphML/JSONL artifacts. Authority enforces this by rejecting `graph:write` tokens that lack `properties.serviceIdentity: cartographer`.
|
||||||
@@ -356,6 +364,7 @@ exceptions:
|
|||||||
| Bootstrap | `bootstrap.apiKey` | Shared secret required for `/internal/*`. | Only required when `bootstrap.enabled` is true. |
|
| Bootstrap | `bootstrap.apiKey` | Shared secret required for `/internal/*`. | Only required when `bootstrap.enabled` is true. |
|
||||||
|
|
||||||
### 7.1 Sender-constrained clients (DPoP & mTLS)
|
### 7.1 Sender-constrained clients (DPoP & mTLS)
|
||||||
|
> Rollout tracker: see [`docs/security/dpop-mtls-rollout.md`](security/dpop-mtls-rollout.md) for phase gates tied to `AUTH-DPOP-11-001` and `AUTH-MTLS-11-002`.
|
||||||
|
|
||||||
Authority now understands two flavours of sender-constrained OAuth clients:
|
Authority now understands two flavours of sender-constrained OAuth clients:
|
||||||
|
|
||||||
@@ -386,6 +395,7 @@ Authority now understands two flavours of sender-constrained OAuth clients:
|
|||||||
- Declare client `audiences` in bootstrap manifests or plug-in provisioning metadata; Authority now defaults the token `aud` claim and `resource` indicator from this list, which is also used to trigger nonce enforcement for audiences such as `signer` and `attestor`.
|
- Declare client `audiences` in bootstrap manifests or plug-in provisioning metadata; Authority now defaults the token `aud` claim and `resource` indicator from this list, which is also used to trigger nonce enforcement for audiences such as `signer` and `attestor`.
|
||||||
- **Mutual TLS clients** – client registrations may declare an mTLS binding (`senderConstraint: mtls`). When enabled via `security.senderConstraints.mtls`, Authority validates the presented client certificate against stored bindings (`certificateBindings[]`), optional chain verification, and timing windows. Successful requests embed `cnf.x5t#S256` into the access token (and introspection output) so resource servers can enforce the certificate thumbprint.
|
- **Mutual TLS clients** – client registrations may declare an mTLS binding (`senderConstraint: mtls`). When enabled via `security.senderConstraints.mtls`, Authority validates the presented client certificate against stored bindings (`certificateBindings[]`), optional chain verification, and timing windows. Successful requests embed `cnf.x5t#S256` into the access token (and introspection output) so resource servers can enforce the certificate thumbprint.
|
||||||
- `security.senderConstraints.mtls.enforceForAudiences` forces mTLS whenever the requested `aud`/`resource` (or the client's configured audiences) intersect the configured allow-list (default includes `signer`). Clients configured for different sender constraints are rejected early so operator policy remains consistent.
|
- `security.senderConstraints.mtls.enforceForAudiences` forces mTLS whenever the requested `aud`/`resource` (or the client's configured audiences) intersect the configured allow-list (default includes `signer`). Clients configured for different sender constraints are rejected early so operator policy remains consistent.
|
||||||
|
- Authority enforces mTLS bindings on `/token`, `/fresh-auth`, and `/introspect` by comparing the presented TLS client certificate thumbprint against the stored `authority_sender_certificate_hex` claim. Requests missing a certificate or presenting a different certificate are rejected with `invalid_token`, and the `authority_mtls_mismatch_total{reason=...}` counter is incremented for visibility.
|
||||||
- Certificate bindings now act as an allow-list: Authority verifies thumbprint, subject, issuer, serial number, and any declared SAN values against the presented certificate, with rotation grace windows applied to `notBefore/notAfter`. Operators can enforce subject regexes, SAN type allow-lists (`dns`, `uri`, `ip`), trusted certificate authorities, and rotation grace via `security.senderConstraints.mtls.*`.
|
- Certificate bindings now act as an allow-list: Authority verifies thumbprint, subject, issuer, serial number, and any declared SAN values against the presented certificate, with rotation grace windows applied to `notBefore/notAfter`. Operators can enforce subject regexes, SAN type allow-lists (`dns`, `uri`, `ip`), trusted certificate authorities, and rotation grace via `security.senderConstraints.mtls.*`.
|
||||||
|
|
||||||
Both modes persist additional metadata in `authority_tokens`: `senderConstraint` records the enforced policy, while `senderKeyThumbprint` stores the DPoP JWK thumbprint or mTLS certificate hash captured at issuance. Downstream services can rely on these fields (and the corresponding `cnf` claim) when auditing offline copies of the token store.
|
Both modes persist additional metadata in `authority_tokens`: `senderConstraint` records the enforced policy, while `senderKeyThumbprint` stores the DPoP JWK thumbprint or mTLS certificate hash captured at issuance. Downstream services can rely on these fields (and the corresponding `cnf` claim) when auditing offline copies of the token store.
|
||||||
|
|||||||
@@ -57,6 +57,61 @@ The script spins up MongoDB/Redis via Testcontainers and requires:
|
|||||||
* Docker ≥ 25
|
* Docker ≥ 25
|
||||||
* Node 20 (for Jest/Playwright)
|
* Node 20 (for Jest/Playwright)
|
||||||
|
|
||||||
|
#### Mongo2Go / OpenSSL shim
|
||||||
|
|
||||||
|
Multiple suites (Concelier connectors, Excititor worker/WebService, Scheduler)
|
||||||
|
fall back to [Mongo2Go](https://github.com/Mongo2Go/Mongo2Go) when a developer
|
||||||
|
does not have a local `mongod` listening on `127.0.0.1:27017`. Modern distros
|
||||||
|
ship OpenSSL 3 by default, so you **must** expose the legacy OpenSSL 1.1
|
||||||
|
libraries that the embedded `mongod` requires:
|
||||||
|
|
||||||
|
1. From the repo root, export the provided binaries before running any tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LD_LIBRARY_PATH="$(pwd)/tests/native/openssl-1.1/linux-x64:${LD_LIBRARY_PATH:-}"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. (Optional) If you only need the shim for a single command, prefix it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LD_LIBRARY_PATH="$(pwd)/tests/native/openssl-1.1/linux-x64" \
|
||||||
|
dotnet test src/Concelier/StellaOps.Concelier.sln --nologo
|
||||||
|
```
|
||||||
|
|
||||||
|
3. CI runners or dev containers should either copy
|
||||||
|
`tests/native/openssl-1.1/linux-x64/libcrypto.so.1.1` and `libssl.so.1.1`
|
||||||
|
into a directory that is already on the default library path, or export the
|
||||||
|
`LD_LIBRARY_PATH` value shown above before invoking `dotnet test`.
|
||||||
|
|
||||||
|
The shim lives under `tests/native/openssl-1.1/README.md` with upstream source
|
||||||
|
and licensing details. When the system already has OpenSSL 1.1 installed you
|
||||||
|
can skip this step.
|
||||||
|
|
||||||
|
#### Local Mongo helper
|
||||||
|
|
||||||
|
Some suites (Concelier WebService/Core, Exporter JSON) need a full
|
||||||
|
`mongod` instance when you want to debug outside of Mongo2Go (for example to
|
||||||
|
inspect data with `mongosh` or pin a specific server version). A thin wrapper
|
||||||
|
is available under `tools/mongodb/local-mongo.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# download (cached under .cache/mongodb-local) and start a local replica set
|
||||||
|
tools/mongodb/local-mongo.sh start
|
||||||
|
|
||||||
|
# reuse an existing data set
|
||||||
|
tools/mongodb/local-mongo.sh restart
|
||||||
|
|
||||||
|
# stop / clean
|
||||||
|
tools/mongodb/local-mongo.sh stop
|
||||||
|
tools/mongodb/local-mongo.sh clean
|
||||||
|
```
|
||||||
|
|
||||||
|
By default the script downloads MongoDB 6.0.16 for Ubuntu 22.04, binds to
|
||||||
|
`127.0.0.1:27017`, and initialises a single-node replica set called `rs0`. The
|
||||||
|
current URI is printed on start, e.g.
|
||||||
|
`mongodb://127.0.0.1:27017/?replicaSet=rs0`, and you can export it before
|
||||||
|
running `dotnet test` if a suite supports overriding its connection string.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Concelier OSV↔GHSA parity fixtures
|
### Concelier OSV↔GHSA parity fixtures
|
||||||
@@ -106,4 +161,3 @@ flowchart LR
|
|||||||
---
|
---
|
||||||
|
|
||||||
*Last updated {{ "now" | date: "%Y‑%m‑%d" }}*
|
*Last updated {{ "now" | date: "%Y‑%m‑%d" }}*
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
| DOCS-REPLAY-185-004 | TODO | Docs Guild, Platform Guild | REPLAY-CORE-185-001 | Expand `docs/replay/DEVS_GUIDE_REPLAY.md` with integration checklist and cross-links to sections 3 & 11 of `docs/replay/DETERMINISTIC_REPLAY.md`. | Guide updated with checklist; references validated; lint passes. |
|
| DOCS-REPLAY-185-004 | TODO | Docs Guild, Platform Guild | REPLAY-CORE-185-001 | Expand `docs/replay/DEVS_GUIDE_REPLAY.md` with integration checklist and cross-links to sections 3 & 11 of `docs/replay/DETERMINISTIC_REPLAY.md`. | Guide updated with checklist; references validated; lint passes. |
|
||||||
| DOCS-REPLAY-186-004 | TODO | Docs Guild, Scanner Guild | SCAN-REPLAY-186-001 | Publish `docs/replay/TEST_STRATEGY.md` detailing golden replay, feed drift, and tool upgrade verification steps; link from scanner architecture doc. | New doc merged; links verified; CI scenario notes documented. |
|
| DOCS-REPLAY-186-004 | TODO | Docs Guild, Scanner Guild | SCAN-REPLAY-186-001 | Publish `docs/replay/TEST_STRATEGY.md` detailing golden replay, feed drift, and tool upgrade verification steps; link from scanner architecture doc. | New doc merged; links verified; CI scenario notes documented. |
|
||||||
| RUNBOOK-REPLAY-187-004 | TODO | Docs Guild, Ops Guild | EVID-REPLAY-187-001, CLI-REPLAY-187-002 | Create `/docs/runbooks/replay_ops.md` covering retention enforcement, RootPack rotation, offline kit workflows, and verification drills referencing `docs/replay/DETERMINISTIC_REPLAY.md`. | Runbook merged; rehearsal notes captured; cross-links added. |
|
| RUNBOOK-REPLAY-187-004 | TODO | Docs Guild, Ops Guild | EVID-REPLAY-187-001, CLI-REPLAY-187-002 | Create `/docs/runbooks/replay_ops.md` covering retention enforcement, RootPack rotation, offline kit workflows, and verification drills referencing `docs/replay/DETERMINISTIC_REPLAY.md`. | Runbook merged; rehearsal notes captured; cross-links added. |
|
||||||
|
| DOCS-REACH-201-006 | TODO | Docs Guild | ZASTAVA-REACH-201-001, SCAN-REACH-201-002, SIGNALS-REACH-201-003 | Author reachability doc suite (`docs/signals/reachability.md`, `docs/signals/callgraph-formats.md`, `docs/signals/runtime-facts.md`, CLI/UI appendices) plus embed replay evidence guidance. | Docs merged with imposed rule text; cross-links to Scanner/Zastava/Replay guides validated. |
|
||||||
| DOCS-OBS-50-002 | TODO | Docs Guild, Security Guild | TELEMETRY-OBS-50-002 | Author `/docs/observability/telemetry-standards.md` detailing common fields, scrubbing policy, sampling defaults, and redaction override procedure. | Doc merged; imposed rule banner present; examples validated with telemetry fixtures; security review sign-off captured. |
|
| DOCS-OBS-50-002 | TODO | Docs Guild, Security Guild | TELEMETRY-OBS-50-002 | Author `/docs/observability/telemetry-standards.md` detailing common fields, scrubbing policy, sampling defaults, and redaction override procedure. | Doc merged; imposed rule banner present; examples validated with telemetry fixtures; security review sign-off captured. |
|
||||||
| DOCS-OBS-50-003 | TODO | Docs Guild, Observability Guild | TELEMETRY-OBS-50-001 | Create `/docs/observability/logging.md` covering structured log schema, dos/don'ts, tenant isolation, and copyable examples. | Doc merged with banner; sample logs redacted; lint passes; linked from coding standards. |
|
| DOCS-OBS-50-003 | TODO | Docs Guild, Observability Guild | TELEMETRY-OBS-50-001 | Create `/docs/observability/logging.md` covering structured log schema, dos/don'ts, tenant isolation, and copyable examples. | Doc merged with banner; sample logs redacted; lint passes; linked from coding standards. |
|
||||||
| DOCS-OBS-50-004 | TODO | Docs Guild, Observability Guild | TELEMETRY-OBS-50-002 | Draft `/docs/observability/tracing.md` explaining context propagation, async linking, CLI header usage, and sampling strategies. | Doc merged; imposed rule banner included; diagrams updated; references to CLI/Console features added. |
|
| DOCS-OBS-50-004 | TODO | Docs Guild, Observability Guild | TELEMETRY-OBS-50-002 | Draft `/docs/observability/tracing.md` explaining context propagation, async linking, CLI header usage, and sampling strategies. | Doc merged; imposed rule banner included; diagrams updated; references to CLI/Console features added. |
|
||||||
@@ -304,7 +305,8 @@
|
|||||||
> 2025-11-03: DOCS-AIAI-31-002 completed – architecture deep dive documents pipeline, deterministic tooling, caching, profiles, and deployment guidance.
|
> 2025-11-03: DOCS-AIAI-31-002 completed – architecture deep dive documents pipeline, deterministic tooling, caching, profiles, and deployment guidance.
|
||||||
| DOCS-AIAI-31-003 | DONE (2025-11-03) | Docs Guild, Advisory AI Guild | AIAI-31-006 | Write `/docs/advisory-ai/api.md` describing endpoints, schemas, errors, rate limits. | API doc aligned with OpenAPI; examples validated; checklist appended. |
|
| DOCS-AIAI-31-003 | DONE (2025-11-03) | Docs Guild, Advisory AI Guild | AIAI-31-006 | Write `/docs/advisory-ai/api.md` describing endpoints, schemas, errors, rate limits. | API doc aligned with OpenAPI; examples validated; checklist appended. |
|
||||||
> 2025-11-03: DOCS-AIAI-31-003 completed – `docs/advisory-ai/api.md` covers scopes, request/response schema, rate limits, error codes, observability, offline notes.
|
> 2025-11-03: DOCS-AIAI-31-003 completed – `docs/advisory-ai/api.md` covers scopes, request/response schema, rate limits, error codes, observability, offline notes.
|
||||||
| DOCS-AIAI-31-004 | BLOCKED (2025-11-03) | Docs Guild, Console Guild | CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001 | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. | Doc merged; images stored; checklist appended. |
|
| DOCS-AIAI-31-004 | DOING (2025-11-07) | Docs Guild, Console Guild | CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001 | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. | Doc merged; images stored; checklist appended. |
|
||||||
|
> 2025-11-07: Draft outline committed; waiting on final console endpoints for screenshots + API captures.
|
||||||
> 2025-11-03: BLOCKED – waiting for Console endpoints/widgets (CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001) to land before documenting UI flows.
|
> 2025-11-03: BLOCKED – waiting for Console endpoints/widgets (CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001) to land before documenting UI flows.
|
||||||
| DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Docs Guild, DevEx/CLI Guild | CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. | Doc merged; examples tested; checklist appended. |
|
| DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Docs Guild, DevEx/CLI Guild | CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. | Doc merged; examples tested; checklist appended. |
|
||||||
> 2025-11-03: BLOCKED – awaiting CLI implementation (`stella advise run`) and golden outputs (CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C).
|
> 2025-11-03: BLOCKED – awaiting CLI implementation (`stella advise run`) and golden outputs (CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C).
|
||||||
|
|||||||
35
docs/advisory-ai/console.md
Normal file
35
docs/advisory-ai/console.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Advisory AI Console Workflows
|
||||||
|
|
||||||
|
_Last updated: 2025-11-07_
|
||||||
|
|
||||||
|
This guide documents the forthcoming Advisory AI console experience so that console, docs, and QA guilds share a single reference while the new endpoints finish landing.
|
||||||
|
|
||||||
|
## 1. Entry points & navigation
|
||||||
|
- **Dashboard tile**: `Advisory AI` card on the console overview routes to `/console/vuln/advisory-ai` once CONSOLE-VULN-29-001 ships. The tile must include the current model build stamp and data freshness time.
|
||||||
|
- **Deep links**: Copy-as-ticket payloads link back into the console using `/console/vex/{statementId}` (CONSOLE-VEX-30-001). Provide fallbacks that open the Evidence modal with a toast if the workspace is still loading.
|
||||||
|
|
||||||
|
## 2. Evidence surfacing
|
||||||
|
| Workflow | Required API | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Findings overview | `GET /console/vuln/findings` | Must include policy verdict badge, VEX justification summary, and last-seen timestamps. |
|
||||||
|
| Evidence drawer | `GET /console/vex/statements/{id}` | Stream SSE chunk descriptions so long-form provenance renders progressively. |
|
||||||
|
| Copy as ticket | `POST /console/vuln/tickets` | Returns signed payload + attachment list for JIRA/ServiceNow templates. |
|
||||||
|
|
||||||
|
## 3. Accessibility & offline requirements
|
||||||
|
- Console screens must pass WCAG 2.2 AA contrast and provide focus order that matches the keyboard shortcuts planned for Advisory AI (see `docs/advisory-ai/overview.md`).
|
||||||
|
- All screenshots captured for this doc must come from sealed-mode bundles (no external fonts/CDNs). Store them under `docs/assets/advisory-ai/console/` with hashed filenames.
|
||||||
|
- Modal dialogs need `aria-describedby` attributes referencing the explanation text returned by the API; translation strings must live with existing locale packs.
|
||||||
|
|
||||||
|
## 4. Copy-as-ticket guidance
|
||||||
|
1. Operators select one or more VEX-backed findings.
|
||||||
|
2. Console renders the sanitized payload (JSON) plus context summary for the receiving system.
|
||||||
|
3. Users can download the payload or send it via webhook; both flows must log `console.ticket.export` events for audit.
|
||||||
|
|
||||||
|
## 5. Open items before publication
|
||||||
|
- [ ] Replace placeholder API responses with captures from the first merged build of CONSOLE-VULN-29-001 / CONSOLE-VEX-30-001.
|
||||||
|
- [ ] Capture at least two screenshots (list view + evidence drawer) once UI polish is complete.
|
||||||
|
- [ ] Verify copy-as-ticket instructions with Support to ensure the payload fields align with existing SOC runbooks.
|
||||||
|
|
||||||
|
> Tracking: DOCS-AIAI-31-004 (Docs Guild, Console Guild)
|
||||||
|
|
||||||
|
**Reference**: API contracts and sample payloads live in `docs/api/console/workspaces.md` (see `/console/vuln/*` and `/console/vex/*` sections) plus the JSON fixtures under `docs/api/console/samples/`.
|
||||||
5
docs/api/console/samples/vex-statement-sse.ndjson
Normal file
5
docs/api/console/samples/vex-statement-sse.ndjson
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{"event":"statement.created","data":{"statementId":"vex:tenant-default:jwt-auth:5d1a","advisoryId":"CVE-2024-12345","product":"registry.local/ops/auth:2025.10.0","state":"under_investigation","justification":"exploit_observed","sequence":4178,"updatedAt":"2025-11-07T23:10:09Z"}}
|
||||||
|
{"event":"statement.updated","data":{"statementId":"vex:tenant-default:jwt-auth:5d1a","advisoryId":"CVE-2024-12345","product":"registry.local/ops/auth:2025.10.0","state":"fixed","justification":"solution_available","sequence":4182,"updatedAt":"2025-11-08T11:44:32Z"}}
|
||||||
|
{"event":"statement.conflict","data":{"statementId":"vex:tenant-default:jwt-auth:5d1a","advisoryId":"CVE-2024-12345","product":"registry.local/ops/auth:2025.10.0","conflictSummary":"Excititor statement GHSA-1111 differs on status","sequence":4183,"updatedAt":"2025-11-08T11:44:59Z"}}
|
||||||
|
{"event":"statement.updated","data":{"statementId":"vex:tenant-default:jwt-auth:5d1a","advisoryId":"CVE-2024-12345","product":"registry.local/ops/auth:2025.10.0","state":"fixed","justification":"solution_available","sequence":4184,"updatedAt":"2025-11-08T11:45:04Z"}}
|
||||||
|
{"event":"statement.deleted","data":{"statementId":"vex:tenant-default:legacy:1a2b","advisoryId":"CVE-2023-9999","product":"registry.local/ops/legacy:2024.01.0","sequence":4185,"updatedAt":"2025-11-08T12:01:01Z"}}
|
||||||
84
docs/api/console/samples/vuln-findings-sample.json
Normal file
84
docs/api/console/samples/vuln-findings-sample.json
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"findingId": "tenant-default:advisory-ai:sha256:5d1a",
|
||||||
|
"coordinates": {
|
||||||
|
"advisoryId": "CVE-2024-12345",
|
||||||
|
"package": "pkg:npm/jsonwebtoken@9.0.2",
|
||||||
|
"component": "jwt-auth-service",
|
||||||
|
"image": "registry.local/ops/auth:2025.10.0"
|
||||||
|
},
|
||||||
|
"summary": "jsonwebtoken <10.0.0 allows algorithm downgrade.",
|
||||||
|
"severity": "high",
|
||||||
|
"cvss": 8.1,
|
||||||
|
"kev": true,
|
||||||
|
"policyBadge": "fail",
|
||||||
|
"vex": {
|
||||||
|
"statementId": "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
"state": "under_investigation",
|
||||||
|
"justification": "Advisory AI flagged reachable path via Scheduler run 42."
|
||||||
|
},
|
||||||
|
"reachability": {
|
||||||
|
"status": "reachable",
|
||||||
|
"lastObserved": "2025-11-07T23:11:04Z",
|
||||||
|
"signalsVersion": "signals-2025.310.1"
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"sbomDigest": "sha256:6c81f2bbd8bd7336f197f3f68fba2f76d7287dd1a5e2a0f0e9f14f23f3c2f917",
|
||||||
|
"policyRunId": "policy-run::2025-11-07::ca9f",
|
||||||
|
"attestationId": "dsse://authority/attest/84a2"
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"firstSeen": "2025-10-31T04:22:18Z",
|
||||||
|
"lastSeen": "2025-11-07T23:16:51Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"findingId": "tenant-default:advisory-ai:sha256:9bf4",
|
||||||
|
"coordinates": {
|
||||||
|
"advisoryId": "GHSA-xxxx-yyyy-zzzz",
|
||||||
|
"package": "pkg:docker/library/nginx@1.25.2",
|
||||||
|
"component": "ingress-gateway",
|
||||||
|
"image": "registry.local/ops/ingress:2025.09.1"
|
||||||
|
},
|
||||||
|
"summary": "Heap overflow in nginx HTTP/3 parsing.",
|
||||||
|
"severity": "critical",
|
||||||
|
"cvss": 9.8,
|
||||||
|
"kev": false,
|
||||||
|
"policyBadge": "warn",
|
||||||
|
"vex": {
|
||||||
|
"statementId": "vex:tenant-default:ingress:9bf4",
|
||||||
|
"state": "not_affected",
|
||||||
|
"justification": "component_not_present"
|
||||||
|
},
|
||||||
|
"reachability": {
|
||||||
|
"status": "unknown",
|
||||||
|
"signalsVersion": "signals-2025.309.0"
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"sbomDigest": "sha256:99f1e2a7aa0f7c970dcb6674244f0bfb5f37148e3ee09fd4f925d3358dea2239",
|
||||||
|
"policyRunId": "policy-run::2025-11-06::b210",
|
||||||
|
"attestationId": "dsse://authority/attest/1d34"
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"firstSeen": "2025-10-29T18:03:11Z",
|
||||||
|
"lastSeen": "2025-11-07T10:45:03Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"facets": {
|
||||||
|
"severity": [
|
||||||
|
{ "value": "critical", "count": 1 },
|
||||||
|
{ "value": "high", "count": 1 }
|
||||||
|
],
|
||||||
|
"policyBadge": [
|
||||||
|
{ "value": "fail", "count": 1 },
|
||||||
|
{ "value": "warn", "count": 1 }
|
||||||
|
],
|
||||||
|
"reachability": [
|
||||||
|
{ "value": "reachable", "count": 1 },
|
||||||
|
{ "value": "unknown", "count": 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nextPageToken": "eyJjdXJzb3IiOiJmZjg0NiJ9"
|
||||||
|
}
|
||||||
311
docs/api/console/workspaces.md
Normal file
311
docs/api/console/workspaces.md
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
# Console Workspaces API
|
||||||
|
|
||||||
|
_Tracking: CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, DOCS-AIAI-31-004_
|
||||||
|
|
||||||
|
## 1. Goals & Scope
|
||||||
|
|
||||||
|
The console workspaces provide read-only aggregates for Advisory AI operators:
|
||||||
|
|
||||||
|
- `/console/vuln/*` surfaces tenant-scoped findings annotated with policy verdicts, VEX justifications, Scheduler reachability signals, and Advisory AI rationale.
|
||||||
|
- `/console/vex/*` streams the underlying VEX statements, conflicts, and justification summaries (with SSE support for live updates).
|
||||||
|
|
||||||
|
All endpoints MUST:
|
||||||
|
|
||||||
|
1. Remain deterministic offline (stable sort keys, ISO-8601 UTC timestamps, hashed assets).
|
||||||
|
2. Operate with Authority-issued DPoP or mTLS client credentials that include `console:read` and either `vuln:read` or `vex:read`.
|
||||||
|
3. Respect tenant isolation – every request carries `X-StellaOps-Tenant`.
|
||||||
|
|
||||||
|
## 2. Shared Request/Response Conventions
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| Headers | `Authorization: DPoP <token>`, `DPoP: <proof>`, `X-StellaOps-Tenant: <tenantId>`, `Accept: application/json` (or `text/event-stream` for SSE). |
|
||||||
|
| Pagination | Cursor-based via `pageToken`; defaults to 50 items, max 200. Cursors are opaque, base64url, signed. |
|
||||||
|
| Sorting | Findings sorted by `(severity desc, exploitScore desc, findingId asc)`. Statements sorted by `(lastUpdated desc, statementId asc)`. |
|
||||||
|
| Dates | RFC 3339 / ISO-8601 UTC (e.g., `2025-11-08T12:02:11Z`). |
|
||||||
|
| Determinism | All arrays must be pre-sorted; no server-generated uuids in responses. |
|
||||||
|
|
||||||
|
## 3. Vulnerability Workspace (`/console/vuln/*`)
|
||||||
|
|
||||||
|
### 3.1 `GET /console/vuln/findings`
|
||||||
|
|
||||||
|
Query parameters:
|
||||||
|
|
||||||
|
| Parameter | Type | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `pageToken` | string | Optional cursor from previous response. |
|
||||||
|
| `pageSize` | int | 1-200, default 50. |
|
||||||
|
| `severity` | string[] | Accepts `critical`, `high`, `medium`, `low`, `info`. |
|
||||||
|
| `product` | string[] | SBOM `purl` or image digest anchors. |
|
||||||
|
| `policyBadge` | string[] | `pass`, `warn`, `fail`, `waived`. |
|
||||||
|
| `vexState` | string[] | `not_affected`, `fixed`, `under_investigation`, etc. |
|
||||||
|
| `reachability` | string[] | `reachable`, `unreachable`, `unknown`. |
|
||||||
|
| `search` | string | Substring match on CVE/GHSA/KEV ID (case-insensitive). |
|
||||||
|
|
||||||
|
Response body:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"findingId": "tenant-default:advisory-ai:sha256:5d1a",
|
||||||
|
"coordinates": {
|
||||||
|
"advisoryId": "CVE-2024-12345",
|
||||||
|
"package": "pkg:npm/jsonwebtoken@9.0.2",
|
||||||
|
"component": "jwt-auth-service",
|
||||||
|
"image": "registry.local/ops/auth:2025.10.0"
|
||||||
|
},
|
||||||
|
"summary": "jsonwebtoken <10.0.0 allows algorithm downgrade.",
|
||||||
|
"severity": "high",
|
||||||
|
"cvss": 8.1,
|
||||||
|
"kev": true,
|
||||||
|
"policyBadge": "fail",
|
||||||
|
"vex": {
|
||||||
|
"statementId": "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
"state": "under_investigation",
|
||||||
|
"justification": "Advisory AI flagged reachable path via Scheduler run 42."
|
||||||
|
},
|
||||||
|
"reachability": {
|
||||||
|
"status": "reachable",
|
||||||
|
"lastObserved": "2025-11-07T23:11:04Z",
|
||||||
|
"signalsVersion": "signals-2025.310.1"
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"sbomDigest": "sha256:6c81…",
|
||||||
|
"policyRunId": "policy-run::2025-11-07::ca9f",
|
||||||
|
"attestationId": "dsse://authority/attest/84a2"
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"firstSeen": "2025-10-31T04:22:18Z",
|
||||||
|
"lastSeen": "2025-11-07T23:16:51Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"facets": {
|
||||||
|
"severity": [
|
||||||
|
{ "value": "critical", "count": 2 },
|
||||||
|
{ "value": "high", "count": 7 }
|
||||||
|
],
|
||||||
|
"policyBadge": [
|
||||||
|
{ "value": "fail", "count": 6 },
|
||||||
|
{ "value": "warn", "count": 3 },
|
||||||
|
{ "value": "waived", "count": 1 }
|
||||||
|
],
|
||||||
|
"reachability": [
|
||||||
|
{ "value": "reachable", "count": 5 },
|
||||||
|
{ "value": "unreachable", "count": 2 },
|
||||||
|
{ "value": "unknown", "count": 1 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nextPageToken": "eyJjdXJzb3IiOiJmZjg0NiJ9"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 `GET /console/vuln/facets`
|
||||||
|
Returns the full facet catalog (counts by severity, product, policy badge, VEX state, reachability, KEV flag). Designed for sidebar filters without paging; identical parameter surface as `/findings`.
|
||||||
|
|
||||||
|
### 3.3 `GET /console/vuln/{findingId}`
|
||||||
|
|
||||||
|
Returns the full finding document, including evidence timeline, policy overlays, and export-ready metadata:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"findingId": "tenant-default:advisory-ai:sha256:5d1a",
|
||||||
|
"details": {
|
||||||
|
"description": "jsonwebtoken <10.0.0 allows algorithm downgrade.",
|
||||||
|
"references": [
|
||||||
|
"https://nvd.nist.gov/vuln/detail/CVE-2024-12345",
|
||||||
|
"https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-45mw-4jw3-g2wg"
|
||||||
|
],
|
||||||
|
"exploitAvailability": "known_exploit"
|
||||||
|
},
|
||||||
|
"policyBadges": [
|
||||||
|
{
|
||||||
|
"policyId": "policy://tenant-default/runtime-hardening",
|
||||||
|
"verdict": "fail",
|
||||||
|
"explainUrl": "https://console.local/policy/runs/policy-run::2025-11-07::ca9f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"vex": {
|
||||||
|
"statementId": "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
"state": "under_investigation",
|
||||||
|
"justification": "Runtime telemetry confirmed exploitation path.",
|
||||||
|
"impactStatement": "Token exchange service remains exposed until patch 2025.11.2.",
|
||||||
|
"remediations": [
|
||||||
|
{
|
||||||
|
"type": "patch",
|
||||||
|
"description": "Upgrade jwt-auth-service to 2025.11.2.",
|
||||||
|
"deadline": "2025-11-12T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reachability": {
|
||||||
|
"status": "reachable",
|
||||||
|
"callPathSamples": [
|
||||||
|
"api-gateway -> jwt-auth-service -> jsonwebtoken.verify"
|
||||||
|
],
|
||||||
|
"lastUpdated": "2025-11-07T23:11:04Z"
|
||||||
|
},
|
||||||
|
"evidence": {
|
||||||
|
"sbom": {
|
||||||
|
"digest": "sha256:6c81…",
|
||||||
|
"componentPath": [
|
||||||
|
"/src/jwt-auth/package.json",
|
||||||
|
"/src/jwt-auth/node_modules/jsonwebtoken"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"attestations": [
|
||||||
|
{
|
||||||
|
"type": "scan-report",
|
||||||
|
"attestationId": "dsse://authority/attest/84a2",
|
||||||
|
"signer": "attestor@stella-ops.org",
|
||||||
|
"bundleDigest": "sha256:e2bb…"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"firstSeen": "2025-10-31T04:22:18Z",
|
||||||
|
"lastSeen": "2025-11-07T23:16:51Z",
|
||||||
|
"vexLastUpdated": "2025-11-07T23:10:09Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 `POST /console/vuln/tickets`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
POST /console/vuln/tickets
|
||||||
|
{
|
||||||
|
"tenant": "tenant-default",
|
||||||
|
"selection": [
|
||||||
|
"tenant-default:advisory-ai:sha256:5d1a",
|
||||||
|
"tenant-default:advisory-ai:sha256:9bf4"
|
||||||
|
],
|
||||||
|
"targetSystem": "servicenow",
|
||||||
|
"metadata": {
|
||||||
|
"assignmentGroup": "runtime-security",
|
||||||
|
"priority": "P1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"ticketId": "console-ticket::tenant-default::2025-11-08::00018",
|
||||||
|
"payload": {
|
||||||
|
"version": "2025-11-01",
|
||||||
|
"tenant": "tenant-default",
|
||||||
|
"findings": [
|
||||||
|
{ "findingId": "tenant-default:advisory-ai:sha256:5d1a", "severity": "high" },
|
||||||
|
{ "findingId": "tenant-default:advisory-ai:sha256:9bf4", "severity": "critical" }
|
||||||
|
],
|
||||||
|
"policyBadge": "fail",
|
||||||
|
"vexSummary": "2 reachable findings pending patch.",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"type": "json",
|
||||||
|
"name": "console-ticket-20251108.json",
|
||||||
|
"digest": "sha256:1fdd…",
|
||||||
|
"contentType": "application/json",
|
||||||
|
"expiresAt": "2025-11-15T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"auditEventId": "console.ticket.export::2025-11-08::00018"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Requests emit `console.ticket.export` audit events (tenant, user, selection counts, target system).
|
||||||
|
|
||||||
|
## 4. VEX Workspace (`/console/vex/*`)
|
||||||
|
|
||||||
|
### 4.1 `GET /console/vex/statements`
|
||||||
|
|
||||||
|
Parameters mirror `/console/vuln/findings` plus:
|
||||||
|
|
||||||
|
| Parameter | Type | Notes |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `advisoryId` | string[] | CVE/GHSA/OVAL identifiers. |
|
||||||
|
| `justification` | string[] | `exploit_observed`, `component_not_present`, etc. |
|
||||||
|
| `statementType` | string[] | `vex`, `openvex`, `custom`, `advisory_ai`. |
|
||||||
|
| `prefer` | string | `prefer=stream` enables chunked streaming (NDJSON). |
|
||||||
|
|
||||||
|
Response (paged JSON):
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"statementId": "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
"advisoryId": "CVE-2024-12345",
|
||||||
|
"product": "registry.local/ops/auth:2025.10.0",
|
||||||
|
"status": "under_investigation",
|
||||||
|
"justification": "exploit_observed",
|
||||||
|
"lastUpdated": "2025-11-07T23:10:09Z",
|
||||||
|
"source": {
|
||||||
|
"type": "advisory_ai",
|
||||||
|
"modelBuild": "aiai-console-2025-10-28",
|
||||||
|
"confidence": 0.74
|
||||||
|
},
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "finding",
|
||||||
|
"href": "/console/vuln/findings/tenant-default:advisory-ai:sha256:5d1a"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nextPageToken": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `Accept: text/event-stream`, the endpoint emits events (see §4.3) instead of paged JSON.
|
||||||
|
|
||||||
|
### 4.2 `GET /console/vex/statements/{statementId}`
|
||||||
|
|
||||||
|
Returns the canonical statement plus provenance extracts. SSE clients can call this endpoint when they need full bodies after receiving a summary event.
|
||||||
|
|
||||||
|
### 4.3 `GET /console/vex/events` (SSE)
|
||||||
|
|
||||||
|
Streams live updates for VEX statements affecting the tenant:
|
||||||
|
|
||||||
|
- Event types: `statement.created`, `statement.updated`, `statement.deleted`, `statement.conflict`.
|
||||||
|
- Fields: `id`, `advisoryId`, `product`, `vexState`, `severityHint`, `policyBadge`, `conflictSummary`, `sequence`.
|
||||||
|
- Replay: Clients include `Last-Event-ID`; server resumes from sequence.
|
||||||
|
- Heartbeats every 15 seconds (`event: keepalive`, `data: {}`).
|
||||||
|
|
||||||
|
Example event payload:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
event: statement.updated
|
||||||
|
data: {
|
||||||
|
"statementId": "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
"advisoryId": "CVE-2024-12345",
|
||||||
|
"product": "registry.local/ops/auth:2025.10.0",
|
||||||
|
"state": "fixed",
|
||||||
|
"justification": "solution_available",
|
||||||
|
"sequence": 4182,
|
||||||
|
"updatedAt": "2025-11-08T11:44:32Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Signals & Scheduler Integration
|
||||||
|
|
||||||
|
- Reachability data is materialized by Scheduler delta jobs (`SCHED-CONSOLE-23-001`). `/console/vuln/findings` should cache the most recent job ID and expose `signalsVersion`.
|
||||||
|
- VEX justification fields reference Excititor statement IDs; ensure the gateway checks Excititor availability and degrades gracefully (returns `state: unavailable` plus telemetry).
|
||||||
|
- Scheduler must publish `console.vuln.refresh` events whenever advisory/VEX deltas warrant workspace refresh; console SSE endpoint may piggyback on the same Redis/NATS channel.
|
||||||
|
|
||||||
|
## 6. Determinism & Offline Notes
|
||||||
|
|
||||||
|
1. All responses are compressible JSON; no CDN fonts/assets referenced.
|
||||||
|
2. SSE endpoints must tolerate sealed mode by operating on loopback addresses only.
|
||||||
|
3. `authority-sealed-ci.json` (see DEVOPS-AIRGAP-57-002) is the evidence Authority consumes before enabling these APIs for sealed tenants; console responses echo `sealed: true/false` flags for UI badges.
|
||||||
|
|
||||||
|
## 7. Sample Payloads for Docs
|
||||||
|
|
||||||
|
- `docs/api/console/samples/vuln-findings-sample.json` – exported via `scripts/generate-console-samples.ts` (placeholder script to be added when backend lands).
|
||||||
|
- `docs/api/console/samples/vex-statement-sse.ndjson` – contains 5 chronological SSE events for screenshot reproduction.
|
||||||
|
|
||||||
|
> Until backend implementations ship, use the examples above to unblock DOCS-AIAI-31-004; replace them with live captures once the gateway endpoints are available in staging.
|
||||||
@@ -18,14 +18,35 @@
|
|||||||
- Build linksets from conflicting advisory observations (e.g., differing severity or status flags).
|
- Build linksets from conflicting advisory observations (e.g., differing severity or status flags).
|
||||||
- Confirm conflict markers propagate to `AdvisoryLinkset` outputs and associated metrics/log records.
|
- Confirm conflict markers propagate to `AdvisoryLinkset` outputs and associated metrics/log records.
|
||||||
- Capture deterministic ordering of conflict explanations for evidence exports.
|
- Capture deterministic ordering of conflict explanations for evidence exports.
|
||||||
|
- Coverage landed via `AdvisoryObservationFactoryTests.Create_PreservesRawReferencesForConflictAudits` (raw linkset + attribute parity) and `AdvisoryEventLogTests.AppendAsync_SortsConflictStatementIds` (canonical conflict JSON + stable hashes).
|
||||||
|
|
||||||
3. **Evidence/export parity**
|
3. **Evidence/export parity**
|
||||||
- Re-run observation/linkset pipelines against identical fixtures and assert resulting evidence manifests hash-identically.
|
- Re-run observation/linkset pipelines against identical fixtures and assert resulting evidence manifests hash-identically.
|
||||||
- Track monotonic `supersedes` chains and ensure canonical link records include `PRIMARY` schemes.
|
- Track monotonic `supersedes` chains and ensure canonical link records include `PRIMARY` schemes.
|
||||||
|
- `JsonExportSnapshotBuilderTests.WriteAsync_DifferentInputOrderProducesSameDigest` now proves export bundles remain byte-identical regardless of advisory enumeration order; digest sampling extends `ProducesIdenticalBytesAcrossRuns`.
|
||||||
|
|
||||||
|
## Mongo2Go/OpenSSL toolchain
|
||||||
|
|
||||||
|
Concelier solution tests (and most connector suites) depend on Mongo2Go’s embedded `mongod`, which is linked against OpenSSL 1.1. The repo already ships the required libraries in `tests/native/openssl-1.1/linux-x64/{libcrypto.so.1.1,libssl.so.1.1}`; use them instead of installing global packages so offline runners stay deterministic.
|
||||||
|
|
||||||
|
1. Add the shim to your shell before executing any Mongo-backed suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LD_LIBRARY_PATH="$(git rev-parse --show-toplevel)/tests/native/openssl-1.1/linux-x64:${LD_LIBRARY_PATH:-}"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. For single commands you can prefix the invocation (handy for CI copy/paste):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LD_LIBRARY_PATH="$(pwd)/tests/native/openssl-1.1/linux-x64" \
|
||||||
|
dotnet test src/Concelier/StellaOps.Concelier.sln --nologo
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The shim’s provenance and troubleshooting notes live in `tests/native/openssl-1.1/README.md`; reference it when mirroring the toolchain into air-gapped runners.
|
||||||
|
|
||||||
## Migration Steps
|
## Migration Steps
|
||||||
- [ ] Retire `StellaOps.Concelier.Merge.Tests` determinism suites once observation/linkset equivalents land.
|
- [x] Retire `StellaOps.Concelier.Merge.Tests` determinism suites once observation/linkset equivalents land.
|
||||||
- [ ] Introduce new regression fixtures under `StellaOps.Concelier.Core.Tests` (shared via `StellaOps.Concelier.Testing`).
|
- [x] Introduce new regression fixtures under `StellaOps.Concelier.Core.Tests` (shared via `StellaOps.Concelier.Testing`).
|
||||||
- [ ] Wire test helpers to Mongo in-memory harness for end-to-end parity runs.
|
- [ ] Wire test helpers to Mongo in-memory harness for end-to-end parity runs.
|
||||||
- [ ] Update documentation (`docs/migration/no-merge.md`) with validation checklist once new tests are green.
|
- [ ] Update documentation (`docs/migration/no-merge.md`) with validation checklist once new tests are green.
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ Follow the sprint files below in order. Update task status in both `SPRINTS` and
|
|||||||
> 2025-11-03: MERGE-LNM-21-001 marked DONE – published `docs/migration/no-merge.md` with rollout, backfill, validation, and rollback guidance for the LNM cutover.
|
> 2025-11-03: MERGE-LNM-21-001 marked DONE – published `docs/migration/no-merge.md` with rollout, backfill, validation, and rollback guidance for the LNM cutover.
|
||||||
> 2025-11-04: GRAPH-INDEX-28-011 marked DONE (Graph Indexer Guild) – SBOM ingest DI wiring now emits graph snapshots by default, snapshot root configurable via `STELLAOPS_GRAPH_SNAPSHOT_DIR`, and Graph Indexer tests exercised with Mongo URI guidance.
|
> 2025-11-04: GRAPH-INDEX-28-011 marked DONE (Graph Indexer Guild) – SBOM ingest DI wiring now emits graph snapshots by default, snapshot root configurable via `STELLAOPS_GRAPH_SNAPSHOT_DIR`, and Graph Indexer tests exercised with Mongo URI guidance.
|
||||||
> 2025-11-06: MERGE-LNM-21-002 remains DOING (BE-Merge) – default-off merge DI + job gating landed, but Concelier WebService ingest/mirror tests are failing; guard and migration fixes pending before completion.
|
> 2025-11-06: MERGE-LNM-21-002 remains DOING (BE-Merge) – default-off merge DI + job gating landed, but Concelier WebService ingest/mirror tests are failing; guard and migration fixes pending before completion.
|
||||||
|
> 2025-11-07: MERGE-LNM-21-002 marked DONE (BE-Merge) – Link-Not-Merge telemetry gaps closed by introducing `StellaOps.Ingestion.Telemetry`, guard metrics/tests updated, and Concelier Exporter JSON + solution smoke suites re-run to cover the new filename normalization.
|
||||||
> 2025-11-06: TASKRUN-43-001 marked DONE (Task Runner Guild) – approvals resume API now requeues packs, plan snapshots persisted, and filesystem artifact uploader stores manifests/files for offline review.
|
> 2025-11-06: TASKRUN-43-001 marked DONE (Task Runner Guild) – approvals resume API now requeues packs, plan snapshots persisted, and filesystem artifact uploader stores manifests/files for offline review.
|
||||||
> 2025-11-06: CLI-POLICY-23-005 marked DONE (DevEx/CLI Guild) – policy activate CLI verifies scheduling/approval flow, Spectre console fallbacks emit warnings offline, and full CLI suite passes against local feeds.
|
> 2025-11-06: CLI-POLICY-23-005 marked DONE (DevEx/CLI Guild) – policy activate CLI verifies scheduling/approval flow, Spectre console fallbacks emit warnings offline, and full CLI suite passes against local feeds.
|
||||||
> 2025-11-07: DOCS-AIAI-31-007 marked DONE (Docs Guild, Security Guild) – published `/docs/security/assistant-guardrails.md` covering redaction rules, blocked phrases, telemetry, and alert wiring.
|
> 2025-11-07: DOCS-AIAI-31-007 marked DONE (Docs Guild, Security Guild) – published `/docs/security/assistant-guardrails.md` covering redaction rules, blocked phrases, telemetry, and alert wiring.
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ ATTEST-VERIFY-74-001 | DONE | Emit telemetry (spans/metrics) tagged by subject,
|
|||||||
ATTEST-VERIFY-74-002 | DONE (2025-11-01) | Document verification report schema and explainability in `/docs/modules/attestor/workflows.md`. Dependencies: ATTEST-VERIFY-73-001. | Verification Guild, Docs Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
|
ATTEST-VERIFY-74-002 | DONE (2025-11-01) | Document verification report schema and explainability in `/docs/modules/attestor/workflows.md`. Dependencies: ATTEST-VERIFY-73-001. | Verification Guild, Docs Guild (src/Attestor/StellaOps.Attestor.Verify/TASKS.md)
|
||||||
ATTESTOR-72-001 | DONE | Scaffold service (REST API skeleton, storage interfaces, KMS integration stubs) and DSSE validation pipeline. Dependencies: ATTEST-ENVELOPE-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
ATTESTOR-72-001 | DONE | Scaffold service (REST API skeleton, storage interfaces, KMS integration stubs) and DSSE validation pipeline. Dependencies: ATTEST-ENVELOPE-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
||||||
ATTESTOR-72-002 | DONE | Implement attestation store (DB tables, object storage integration), CRUD, and indexing strategies. Dependencies: ATTESTOR-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
ATTESTOR-72-002 | DONE | Implement attestation store (DB tables, object storage integration), CRUD, and indexing strategies. Dependencies: ATTESTOR-72-001. | Attestor Service Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
||||||
ATTESTOR-72-003 | BLOCKED | Validate attestation store TTL against production-like Mongo/Redis stack; capture logs and remediation plan. Dependencies: ATTESTOR-72-002. | Attestor Service Guild, QA Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
ATTESTOR-72-003 | DONE (2025-11-03) | Validate attestation store TTL against production-like Mongo/Redis stack; capture logs and remediation plan. Dependencies: ATTESTOR-72-002. | Attestor Service Guild, QA Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
||||||
|
> 2025-11-03: TTL soak tests captured in `docs/modules/attestor/ttl-validation.md`; Mongo/Redis evidence archived for replay.
|
||||||
ATTESTOR-73-001 | DONE (2025-11-01) | Implement signing endpoint with Ed25519/ECDSA support, KMS integration, and audit logging. Dependencies: ATTESTOR-72-002, KMS-72-001. | Attestor Service Guild, KMS Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
ATTESTOR-73-001 | DONE (2025-11-01) | Implement signing endpoint with Ed25519/ECDSA support, KMS integration, and audit logging. Dependencies: ATTESTOR-72-002, KMS-72-001. | Attestor Service Guild, KMS Guild (src/Attestor/StellaOps.Attestor/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -48,8 +49,11 @@ AUTH-AIRGAP-56-001 | DONE (2025-11-04) | Provision new scopes (`airgap:seal`, `a
|
|||||||
> 2025-11-04: Verified discovery metadata now advertises the airgap scope trio, `etc/authority.yaml.sample` + offline kit docs ship the new roles, and Authority tests enforce tenant gating for `airgap:*` scopes (`dotnet test` executed).
|
> 2025-11-04: Verified discovery metadata now advertises the airgap scope trio, `etc/authority.yaml.sample` + offline kit docs ship the new roles, and Authority tests enforce tenant gating for `airgap:*` scopes (`dotnet test` executed).
|
||||||
AUTH-AIRGAP-56-002 | DONE (2025-11-04) | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. Dependencies: AUTH-AIRGAP-56-001, AIRGAP-IMP-58-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-AIRGAP-56-002 | DONE (2025-11-04) | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. Dependencies: AUTH-AIRGAP-56-001, AIRGAP-IMP-58-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
> 2025-11-04: `/authority/audit/airgap` minimal APIs persist tenant-scoped records with paging, RBAC checks for `airgap:import`/`airgap:status:read` pass, and Authority integration suite (187 tests) exercised the audit flow.
|
> 2025-11-04: `/authority/audit/airgap` minimal APIs persist tenant-scoped records with paging, RBAC checks for `airgap:import`/`airgap:status:read` pass, and Authority integration suite (187 tests) exercised the audit flow.
|
||||||
AUTH-AIRGAP-57-001 | BLOCKED (2025-11-01) | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. Dependencies: AUTH-AIRGAP-56-001, DEVOPS-AIRGAP-57-002. | Authority Core & Security Guild, DevOps Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-AIRGAP-57-001 | DOING (2025-11-08) | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. Dependencies: AUTH-AIRGAP-56-001, DEVOPS-AIRGAP-57-002. | Authority Core & Security Guild, DevOps Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
> 2025-11-01: AUTH-AIRGAP-57-001 blocked pending definition of sealed-confirmation evidence and configuration shape before gating (Authority Core & Security Guild, DevOps Guild).
|
> 2025-11-01: AUTH-AIRGAP-57-001 blocked pending definition of sealed-confirmation evidence and configuration shape before gating (Authority Core & Security Guild, DevOps Guild).
|
||||||
|
> 2025-11-08: Flipped to DOING; partnering with DevOps on artifacts so Authority gating tests can consume sealed confirmations once published (target 2025-11-10).
|
||||||
|
> 2025-11-07: Still waiting on DEVOPS-AIRGAP-57-002 sealed-mode CI suite (`ops/devops/sealed-mode-ci/*`) to publish artefacts so Authority can wire the gating tests.
|
||||||
|
> 2025-11-08: DevOps sealed-mode CI now uploads `artifacts/sealed-mode-ci/<commit>/authority-sealed-ci.json`; Authority to hook the gating middleware/tests up to that feed next.
|
||||||
AUTH-NOTIFY-38-001 | DONE (2025-11-01) | Define `Notify.Viewer`, `Notify.Operator`, `Notify.Admin` scopes/roles, update discovery metadata, offline defaults, and issuer templates. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-NOTIFY-38-001 | DONE (2025-11-01) | Define `Notify.Viewer`, `Notify.Operator`, `Notify.Admin` scopes/roles, update discovery metadata, offline defaults, and issuer templates. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
> 2025-11-01: AUTH-NOTIFY-38-001 completed—Notify scope catalog, discovery metadata, docs, configuration samples, and service tests updated for new roles.
|
> 2025-11-01: AUTH-NOTIFY-38-001 completed—Notify scope catalog, discovery metadata, docs, configuration samples, and service tests updated for new roles.
|
||||||
AUTH-NOTIFY-40-001 | DONE (2025-11-02) | Implement signed ack token key rotation, webhook allowlists, admin-only escalation settings, and audit logging of ack actions. Dependencies: AUTH-NOTIFY-38-001, WEB-NOTIFY-40-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-NOTIFY-40-001 | DONE (2025-11-02) | Implement signed ack token key rotation, webhook allowlists, admin-only escalation settings, and audit logging of ack actions. Dependencies: AUTH-NOTIFY-38-001, WEB-NOTIFY-40-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
@@ -73,6 +77,7 @@ AUTH-PACKS-41-001 | DONE (2025-11-04) | Define CLI SSO profiles and pack scopes
|
|||||||
> 2025-11-04: Verified discovery metadata, OpenAPI, `etc/authority.yaml.sample`, and offline kit docs reflect the packs scope set; Authority suite re-run (`dotnet test`) to confirm tenant gating and policy checks.
|
> 2025-11-04: Verified discovery metadata, OpenAPI, `etc/authority.yaml.sample`, and offline kit docs reflect the packs scope set; Authority suite re-run (`dotnet test`) to confirm tenant gating and policy checks.
|
||||||
> 2025-11-02: Shared OpenSSL 1.1 shim now feeds Mongo2Go for Authority & Signals tests, keeping pack scope regressions and other Mongo flows working on OpenSSL 3 hosts.
|
> 2025-11-02: Shared OpenSSL 1.1 shim now feeds Mongo2Go for Authority & Signals tests, keeping pack scope regressions and other Mongo flows working on OpenSSL 3 hosts.
|
||||||
AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. Dependencies: AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. Dependencies: AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
> 2025-11-07: AUTH-PACKS-41-001 + TASKRUN-42-001 are DONE; remaining blocker is ORCH-SVC-42-101 (still TODO) for log streaming/approvals APIs. Not deleted—waiting on Orchestrator to publish contracts.
|
||||||
|
|
||||||
|
|
||||||
[Identity & Signing] 100.B) Authority.II
|
[Identity & Signing] 100.B) Authority.II
|
||||||
@@ -80,8 +85,17 @@ Depends on: Sprint 100.B - Authority.I
|
|||||||
Summary: Identity & Signing focus on Authority (phase II).
|
Summary: Identity & Signing focus on Authority (phase II).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
AUTH-POLICY-23-002 | BLOCKED (2025-10-29) | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. Dependencies: AUTH-POLICY-23-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-POLICY-23-001 | DONE (2025-10-27) | Introduce fine-grained policy scopes (`policy:read`, `policy:author`, `policy:review`, `policy:simulate`, `findings:read`) for CLI/service accounts; update discovery metadata, issuer templates, and offline defaults. Dependencies: AUTH-AOC-19-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
AUTH-POLICY-23-003 | BLOCKED (2025-10-29) | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. Dependencies: AUTH-POLICY-23-001. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-POLICY-23-002 | DONE (2025-11-08) | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. Dependencies: AUTH-POLICY-23-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
> 2025-11-08: Added Policy Engine activation options (force/default/audit toggles), enforced pending-second-approval responses, and emitted `policy.activation.*` telemetry across auditor logs.
|
||||||
|
AUTH-POLICY-23-003 | DONE (2025-11-08) | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. Dependencies: AUTH-POLICY-23-001. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
> 2025-11-08: Documented dual-control activation steps, new `PolicyEngine.activation.*` knobs, sample YAML defaults, and console/operator guidance for audit visibility.
|
||||||
|
> 2025-11-07: Scope migration (AUTH-POLICY-23-001) shipped; activation guardrail and documentation updates now waiting on pairing.
|
||||||
|
AUTH-DPOP-11-001 | DOING (2025-11-07) | Enforce DPoP sender constraints for `/token` flows (nonce policies, JKT persistence, structured telemetry) so downstream services can trust `cnf` metadata. Dependencies: AUTH-AOC-19-002. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
AUTH-MTLS-11-002 | DOING (2025-11-07) | Deliver mTLS-bound token issuance/validation (cert thumbprint storage, JWKS rotation hooks) required for high-assurance tenants and plugin mitigations. Dependencies: AUTH-DPOP-11-001. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
> 2025-11-07: Authority + DevOps stand-up aligned on a 2025-11-10 delivery target for AUTH-DPOP-11-001 / AUTH-MTLS-11-002 and DEVOPS-AIRGAP-57-002 so plugin security/air-gap gating can flip to DOING immediately after.
|
||||||
|
> 2025-11-08: Taking ownership to wire certificate thumbprint persistence + audit logging; blocking issues from AUTH-DPOP-11-001 now resolved, so mTLS enforcement can proceed.
|
||||||
|
> 2025-11-08: `/token`/`/introspect` now enforce TLS certificate matches for mTLS-bound tokens and emit `authority_mtls_mismatch_total` telemetry when rejections occur.
|
||||||
AUTH-POLICY-27-002 | DONE (2025-11-02) | Provide attestation signing service bindings (OIDC token exchange, cosign integration) and enforce publish/promote scope checks, fresh-auth requirements, and audit logging. Dependencies: AUTH-POLICY-27-001, REGISTRY-API-27-007. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-POLICY-27-002 | DONE (2025-11-02) | Provide attestation signing service bindings (OIDC token exchange, cosign integration) and enforce publish/promote scope checks, fresh-auth requirements, and audit logging. Dependencies: AUTH-POLICY-27-001, REGISTRY-API-27-007. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
> 2025-11-02: Added interactive-only `policy:publish`/`policy:promote` scopes with metadata requirements (`policy_reason`, `policy_ticket`, `policy_digest`), fresh-auth validation, audit enrichment, and updated config/docs for operators.
|
> 2025-11-02: Added interactive-only `policy:publish`/`policy:promote` scopes with metadata requirements (`policy_reason`, `policy_ticket`, `policy_digest`), fresh-auth validation, audit enrichment, and updated config/docs for operators.
|
||||||
AUTH-POLICY-27-003 | DONE (2025-11-04) | Update Authority configuration/docs for Policy Studio roles, signing policies, approval workflows, and CLI integration; include compliance checklist. Dependencies: AUTH-POLICY-27-001, AUTH-POLICY-27-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
AUTH-POLICY-27-003 | DONE (2025-11-04) | Update Authority configuration/docs for Policy Studio roles, signing policies, approval workflows, and CLI integration; include compliance checklist. Dependencies: AUTH-POLICY-27-001, AUTH-POLICY-27-002. | Authority Core & Docs Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
@@ -100,9 +114,11 @@ AUTH-VULN-29-003 | DONE (2025-11-04) | Update security docs/config samples for V
|
|||||||
PLG4-6.CAPABILITIES | BLOCKED (2025-10-12) | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | BE-Auth Plugin, Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
PLG4-6.CAPABILITIES | BLOCKED (2025-10-12) | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | BE-Auth Plugin, Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
PLG6.DIAGRAM | TODO | Export final sequence/component diagrams for the developer guide and add offline-friendly assets under `docs/assets/authority`. | Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
PLG6.DIAGRAM | TODO | Export final sequence/component diagrams for the developer guide and add offline-friendly assets under `docs/assets/authority`. | Docs Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
PLG7.RFC | REVIEW | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | BE-Auth Plugin, Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
PLG7.RFC | REVIEW | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | BE-Auth Plugin, Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
SEC2.PLG | BLOCKED (2025-10-21) | Emit audit events from password verification outcomes and persist via `IAuthorityLoginAttemptStore`. <br>⛔ Waiting on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 to stabilise Authority auth surfaces before final verification + publish. | Security Guild, Storage Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
SEC2.PLG | BLOCKED (2025-10-21) | Emit audit events from password verification outcomes and persist via `IAuthorityLoginAttemptStore`. <br>⛔ Waiting on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 to stabilise Authority auth surfaces (PLUGIN-DI-08-001 closed 2025-10-21; re-run once sender constraints land). | Security Guild, Storage Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
SEC3.PLG | BLOCKED (2025-10-21) | Ensure lockout responses and rate-limit metadata flow through plugin logs/events (include retry-after). <br>⛔ Pending AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 so limiter telemetry contract matches final authority surface. | Security Guild, BE-Auth Plugin (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
SEC3.PLG | BLOCKED (2025-10-21) | Ensure lockout responses and rate-limit metadata flow through plugin logs/events (include retry-after). <br>⛔ Pending AUTH-DPOP-11-001 / AUTH-MTLS-11-002; PLUGIN-DI-08-001 already merged, so limiter telemetry just awaits final Authority surface. | Security Guild, BE-Auth Plugin (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
SEC5.PLG | BLOCKED (2025-10-21) | Address plugin-specific mitigations (bootstrap user handling, password policy docs) in threat model backlog. <br>⛔ Final documentation depends on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 outcomes. | Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
SEC5.PLG | BLOCKED (2025-10-21) | Address plugin-specific mitigations (bootstrap user handling, password policy docs) in threat model backlog. <br>⛔ Final documentation now hinges on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 (PLUGIN-DI-08-001 landed 2025-10-21). | Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
|
> 2025-11-07: Upstream AUTH-DPOP-11-001 / AUTH-MTLS-11-002 now DOING; revisit plugin backlog once sender-constraint hardening lands.
|
||||||
|
> 2025-11-08: Dependency audit confirmed — AUTH-DPOP-11-001 / AUTH-MTLS-11-002 staffed with 2025-11-10 delivery; no missing SEC2/SEC3/SEC5 subtasks, so these remain BLOCKED only until sender constraints merge.
|
||||||
PLG7.IMPL-001 | DONE (2025-11-03) | Scaffold `StellaOps.Authority.Plugin.Ldap` + tests, bind configuration (client certificate, trust-store, insecure toggle) with validation and docs samples. | BE-Auth Plugin (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
PLG7.IMPL-001 | DONE (2025-11-03) | Scaffold `StellaOps.Authority.Plugin.Ldap` + tests, bind configuration (client certificate, trust-store, insecure toggle) with validation and docs samples. | BE-Auth Plugin (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
> 2025-11-03: Initial `StellaOps.Authority.Plugin.Ldap` project/tests scaffolded with configuration options + registrar; sample manifest (`etc/authority.plugins/ldap.yaml`) updated to new schema (client certificate, trust store, insecure toggle).
|
> 2025-11-03: Initial `StellaOps.Authority.Plugin.Ldap` project/tests scaffolded with configuration options + registrar; sample manifest (`etc/authority.plugins/ldap.yaml`) updated to new schema (client certificate, trust store, insecure toggle).
|
||||||
PLG7.IMPL-002 | DONE (2025-11-04) | Implement LDAP credential store with TLS/mutual TLS enforcement, deterministic retry/backoff, and structured logging/metrics. | BE-Auth Plugin, Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
PLG7.IMPL-002 | DONE (2025-11-04) | Implement LDAP credential store with TLS/mutual TLS enforcement, deterministic retry/backoff, and structured logging/metrics. | BE-Auth Plugin, Security Guild (src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard/TASKS.md)
|
||||||
@@ -131,7 +147,14 @@ KMS-73-001 | TODO | Add cloud KMS driver (e.g., AWS KMS, GCP KMS) with signing a
|
|||||||
KMS-73-002 | TODO | Implement PKCS#11/HSM driver plus FIDO2 signing support for high assurance workflows. Dependencies: KMS-73-001. | KMS Guild (src/__Libraries/StellaOps.Cryptography.Kms/TASKS.md)
|
KMS-73-002 | TODO | Implement PKCS#11/HSM driver plus FIDO2 signing support for high assurance workflows. Dependencies: KMS-73-001. | KMS Guild (src/__Libraries/StellaOps.Cryptography.Kms/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
|
[Identity & Signing] 100.E) Deployment
|
||||||
|
Depends on: Helm base chart scaffolding (HELM-45-001)
|
||||||
|
Summary: Wire deployment assets so Policy Engine activation guardrails stay deterministic across clusters/offline kits.
|
||||||
|
Task ID | State | Task description | Owners (Source)
|
||||||
|
--- | --- | --- | ---
|
||||||
|
HELM-45-004 | DONE (2025-11-08) | Mount the new `policy-engine-activation` ConfigMap into the Policy Engine (and Policy Gateway) pods, ensure runtime config loads activation overrides from env/file, and refresh Helm/Compose samples for offline parity. | Deployment Guild, Policy Guild (ops/deployment/TASKS.md)
|
||||||
|
|
||||||
|
> 2025-11-08: Helm template now injects the activation ConfigMap for policy-engine/gateway pods, Policy Engine host loads `/config/policy-engine/activation.yaml`, Policy Engine/Gateway tests are green, and CI now runs `helm lint`/`helm template` over every `values*.yaml`.
|
||||||
|
|
||||||
|
|
||||||
If all tasks are done - read next sprint section - SPRINT_110_ingestion_evidence.md
|
If all tasks are done - read next sprint section - SPRINT_110_ingestion_evidence.md
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ AIAI-31-004C | DONE (2025-11-06) | Deliver CLI `stella advise run` command, rend
|
|||||||
DOCS-AIAI-31-002 | DONE (2025-11-03) | Author `/docs/advisory-ai/architecture.md` detailing RAG pipeline, deterministic tooling, caching, model profiles. Dependencies: AIAI-31-004. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-002 | DONE (2025-11-03) | Author `/docs/advisory-ai/architecture.md` detailing RAG pipeline, deterministic tooling, caching, model profiles. Dependencies: AIAI-31-004. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-001 | DONE (2025-11-03) | Publish `/docs/advisory-ai/overview.md` covering capabilities, guardrails, RBAC personas, and offline posture. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-001 | DONE (2025-11-03) | Publish `/docs/advisory-ai/overview.md` covering capabilities, guardrails, RBAC personas, and offline posture. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-003 | DONE (2025-11-03) | Write `/docs/advisory-ai/api.md` covering endpoints, schemas, errors, rate limits, and imposed-rule banner. Dependencies: DOCS-AIAI-31-002. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-003 | DONE (2025-11-03) | Write `/docs/advisory-ai/api.md` covering endpoints, schemas, errors, rate limits, and imposed-rule banner. Dependencies: DOCS-AIAI-31-002. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-004 | BLOCKED (2025-11-03) | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. Dependencies: CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001. | Docs Guild, Console Guild (docs/TASKS.md)
|
DOCS-AIAI-31-004 | DOING (2025-11-07) | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. Dependencies: CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001. | Docs Guild, Console Guild (docs/TASKS.md)
|
||||||
|
> 2025-11-07: Draft doc committed (`docs/advisory-ai/console.md`) with workflow outline; screenshots will be added once CONSOLE-VULN-29-001 / CONSOLE-VEX-30-001 ship.
|
||||||
|
> 2025-11-08: Console endpoints are staffed (CONSOLE-VULN-29-001 / CONSOLE-VEX-30-001 DOING); still waiting on EXCITITOR-CONSOLE-23-001 feeds before capturing screenshots/tests.
|
||||||
DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. Dependencies: CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C. | Docs Guild, DevEx/CLI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. Dependencies: CLI-VULN-29-001, CLI-VEX-30-001, AIAI-31-004C. | Docs Guild, DevEx/CLI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-006 | BLOCKED (2025-11-03) | Update `/docs/policy/assistant-parameters.md` covering temperature, token limits, ranking weights, TTLs. Dependencies: POLICY-ENGINE-31-001. | Docs Guild, Policy Guild (docs/TASKS.md)
|
DOCS-AIAI-31-006 | BLOCKED (2025-11-03) | Update `/docs/policy/assistant-parameters.md` covering temperature, token limits, ranking weights, TTLs. Dependencies: POLICY-ENGINE-31-001. | Docs Guild, Policy Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-007 | DONE (2025-11-07) | Write `/docs/security/assistant-guardrails.md` detailing redaction, injection defense, logging. Dependencies: AIAI-31-005. | Docs Guild, Security Guild (docs/TASKS.md)
|
DOCS-AIAI-31-007 | DONE (2025-11-07) | Write `/docs/security/assistant-guardrails.md` detailing redaction, injection defense, logging. Dependencies: AIAI-31-005. | Docs Guild, Security Guild (docs/TASKS.md)
|
||||||
@@ -127,7 +129,7 @@ CONCELIER-OAS-61-001 `Spec coverage` | TODO | Update Concelier OAS with advisory
|
|||||||
CONCELIER-OAS-61-002 `Examples library` | TODO | Provide rich examples for advisories, linksets, conflict annotations used by SDK + docs. Dependencies: CONCELIER-OAS-61-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OAS-61-002 `Examples library` | TODO | Provide rich examples for advisories, linksets, conflict annotations used by SDK + docs. Dependencies: CONCELIER-OAS-61-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OAS-62-001 `SDK smoke tests` | TODO | Add SDK tests covering advisory search, pagination, and conflict handling; ensure source metadata surfaced. Dependencies: CONCELIER-OAS-61-002. | Concelier Core Guild, SDK Generator Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OAS-62-001 `SDK smoke tests` | TODO | Add SDK tests covering advisory search, pagination, and conflict handling; ensure source metadata surfaced. Dependencies: CONCELIER-OAS-61-002. | Concelier Core Guild, SDK Generator Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OAS-63-001 `Deprecation headers` | TODO | Implement deprecation header support and timeline events for retiring endpoints. Dependencies: CONCELIER-OAS-62-001. | Concelier Core Guild, API Governance Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OAS-63-001 `Deprecation headers` | TODO | Implement deprecation header support and timeline events for retiring endpoints. Dependencies: CONCELIER-OAS-62-001. | Concelier Core Guild, API Governance Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OBS-50-001 `Telemetry adoption` | TODO | Replace ad-hoc logging with telemetry core across ingestion/linking pipelines; ensure spans/logs include tenant, source vendor, upstream id, content hash, and trace IDs. | Concelier Core Guild, Observability Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OBS-50-001 `Telemetry adoption` | DONE (2025-11-07) | Replace ad-hoc logging with telemetry core across ingestion/linking pipelines; ensure spans/logs include tenant, source vendor, upstream id, content hash, and trace IDs. | Concelier Core Guild, Observability Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OBS-51-001 `Metrics & SLOs` | TODO | Emit metrics for ingest latency (cold/warm), queue depth, aoc violation rate, and publish SLO burn-rate alerts (ingest P95 <30s cold / <5s warm). Ship dashboards + alert configs. Dependencies: CONCELIER-OBS-50-001. | Concelier Core Guild, DevOps Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OBS-51-001 `Metrics & SLOs` | TODO | Emit metrics for ingest latency (cold/warm), queue depth, aoc violation rate, and publish SLO burn-rate alerts (ingest P95 <30s cold / <5s warm). Ship dashboards + alert configs. Dependencies: CONCELIER-OBS-50-001. | Concelier Core Guild, DevOps Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OBS-52-001 `Timeline events` | TODO | Emit `timeline_event` records for advisory ingest/normalization/linkset creation with provenance, trace IDs, conflict summaries, and evidence placeholders. Dependencies: CONCELIER-OBS-51-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OBS-52-001 `Timeline events` | TODO | Emit `timeline_event` records for advisory ingest/normalization/linkset creation with provenance, trace IDs, conflict summaries, and evidence placeholders. Dependencies: CONCELIER-OBS-51-001. | Concelier Core Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
CONCELIER-OBS-53-001 `Evidence snapshots` | TODO | Produce advisory evaluation bundle payloads (raw doc, linkset, normalization diff) for evidence locker; ensure Merkle manifests seeded with content hashes. Dependencies: CONCELIER-OBS-52-001. | Concelier Core Guild, Evidence Locker Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
CONCELIER-OBS-53-001 `Evidence snapshots` | TODO | Produce advisory evaluation bundle payloads (raw doc, linkset, normalization diff) for evidence locker; ensure Merkle manifests seeded with content hashes. Dependencies: CONCELIER-OBS-52-001. | Concelier Core Guild, Evidence Locker Guild (src/Concelier/__Libraries/StellaOps.Concelier.Core/TASKS.md)
|
||||||
@@ -172,14 +174,17 @@ CONCELIER-WEB-AIRGAP-56-001 `Mirror import APIs` | TODO | Extend ingestion endpo
|
|||||||
CONCELIER-WEB-AIRGAP-56-002 `Airgap status surfaces` | TODO | Add staleness metadata and bundle provenance to advisory APIs (`/advisories/observations`, `/advisories/linksets`). Dependencies: CONCELIER-WEB-AIRGAP-56-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AIRGAP-56-002 `Airgap status surfaces` | TODO | Add staleness metadata and bundle provenance to advisory APIs (`/advisories/observations`, `/advisories/linksets`). Dependencies: CONCELIER-WEB-AIRGAP-56-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-AIRGAP-57-001 `Error remediation` | TODO | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` responses with user guidance. Dependencies: CONCELIER-WEB-AIRGAP-56-002. | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AIRGAP-57-001 `Error remediation` | TODO | Map sealed-mode violations to `AIRGAP_EGRESS_BLOCKED` responses with user guidance. Dependencies: CONCELIER-WEB-AIRGAP-56-002. | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-AIRGAP-58-001 `Import timeline emission` | TODO | Emit timeline events for bundle ingestion operations with bundle ID, scope, and actor metadata. Dependencies: CONCELIER-WEB-AIRGAP-57-001. | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AIRGAP-58-001 `Import timeline emission` | TODO | Emit timeline events for bundle ingestion operations with bundle ID, scope, and actor metadata. Dependencies: CONCELIER-WEB-AIRGAP-57-001. | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-AOC-19-002 `AOC observability` | TODO | Emit `ingestion_write_total`, `aoc_violation_total`, latency histograms, and tracing spans (`ingest.fetch/transform/write`, `aoc.guard`). Wire structured logging to include tenant, source vendor, upstream id, and content hash. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AOC-19-002 `AOC observability` | DONE (2025-11-07) | Emit `ingestion_write_total`, `aoc_violation_total`, latency histograms, and tracing spans (`ingest.fetch/transform/write`, `aoc.guard`). Wire structured logging to include tenant, source vendor, upstream id, and content hash. | Concelier WebService Guild, Observability Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-AOC-19-003 `Schema/guard unit tests` | TODO | Add unit tests covering schema validation failures, forbidden field rejections (`ERR_AOC_001/002/006/007`), idempotent upserts, and supersedes chains using deterministic fixtures. Dependencies: CONCELIER-WEB-AOC-19-002. | QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AOC-19-003 `Schema/guard unit tests` | TODO | Add unit tests covering schema validation failures, forbidden field rejections (`ERR_AOC_001/002/006/007`), idempotent upserts, and supersedes chains using deterministic fixtures. Dependencies: CONCELIER-WEB-AOC-19-002. | QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-AOC-19-004 `End-to-end ingest verification` | TODO | Create integration tests ingesting large advisory batches (cold/warm) validating linkset enrichment, metrics emission, and reproducible outputs. Capture load-test scripts + doc notes for Offline Kit dry runs. Dependencies: CONCELIER-WEB-AOC-19-003. | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-AOC-19-004 `End-to-end ingest verification` | TODO | Create integration tests ingesting large advisory batches (cold/warm) validating linkset enrichment, metrics emission, and reproducible outputs. Capture load-test scripts + doc notes for Offline Kit dry runs. Dependencies: CONCELIER-WEB-AOC-19-003. | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
|
CONCELIER-WEB-AOC-19-005 `Chunk evidence regression` | TODO (2025-11-08) | Fix `/advisories/{key}/chunks` fixture seeding so AdvisoryChunksEndpoint tests stop returning 404/not-found when raw documents are pre-populated; ensure the Mongo migration no longer emits “Unable to locate advisory_raw documents” during WebService test boot. Dependencies: CONCELIER-WEB-AOC-19-002. | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
|
CONCELIER-WEB-AOC-19-006 `Allowlist ingest auth parity` | TODO (2025-11-08) | Align WebService auth defaults with the test tokens so the allowlisted tenant can create an advisory before forbidden tenants are rejected in `AdvisoryIngestEndpoint_RejectsTenantOutsideAllowlist`. Dependencies: CONCELIER-WEB-AOC-19-002. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
|
CONCELIER-WEB-AOC-19-007 `AOC verify violation codes` | TODO (2025-11-08) | Update AOC verify logic/fixtures so guard failures produce the expected `ERR_AOC_001` payload (current regression returns `ERR_AOC_004`) while keeping mapper/guard parity exercised by the new tests. Dependencies: CONCELIER-WEB-AOC-19-002. | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OAS-61-001 `/.well-known/openapi` | DONE (2025-11-02) | Implement discovery endpoint emitting Concelier spec with version metadata and ETag. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OAS-61-001 `/.well-known/openapi` | DONE (2025-11-02) | Implement discovery endpoint emitting Concelier spec with version metadata and ETag. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OAS-61-002 `Error envelope migration` | TODO | Ensure all API responses use standardized error envelope; update controllers/tests. Dependencies: CONCELIER-WEB-OAS-61-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OAS-61-002 `Error envelope migration` | TODO | Ensure all API responses use standardized error envelope; update controllers/tests. Dependencies: CONCELIER-WEB-OAS-61-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OAS-62-001 `Examples expansion` | TODO | Add curated examples for advisory observations/linksets/conflicts; integrate into dev portal. Dependencies: CONCELIER-WEB-OAS-61-002. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OAS-62-001 `Examples expansion` | TODO | Add curated examples for advisory observations/linksets/conflicts; integrate into dev portal. Dependencies: CONCELIER-WEB-OAS-61-002. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OAS-63-001 `Deprecation headers` | TODO | Add Sunset/Deprecation headers for retiring endpoints and update documentation/notifications. Dependencies: CONCELIER-WEB-OAS-62-001. | Concelier WebService Guild, API Governance Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OAS-63-001 `Deprecation headers` | TODO | Add Sunset/Deprecation headers for retiring endpoints and update documentation/notifications. Dependencies: CONCELIER-WEB-OAS-62-001. | Concelier WebService Guild, API Governance Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OBS-50-001 `Telemetry adoption` | TODO | Adopt telemetry core in web service host, ensure ingest + read endpoints emit trace/log fields (`tenant_id`, `route`, `decision_effect`), and add correlation IDs to responses. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OBS-50-001 `Telemetry adoption` | DONE (2025-11-07) | Adopt telemetry core in web service host, ensure ingest + read endpoints emit trace/log fields (`tenant_id`, `route`, `decision_effect`), and add correlation IDs to responses. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OBS-51-001 `Observability APIs` | TODO | Surface ingest health metrics, queue depth, and SLO status via `/obs/concelier/health` endpoint for Console widgets, with caching and tenant partitioning. Dependencies: CONCELIER-WEB-OBS-50-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OBS-51-001 `Observability APIs` | TODO | Surface ingest health metrics, queue depth, and SLO status via `/obs/concelier/health` endpoint for Console widgets, with caching and tenant partitioning. Dependencies: CONCELIER-WEB-OBS-50-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
CONCELIER-WEB-OBS-52-001 `Timeline streaming` | TODO | Provide SSE stream `/obs/concelier/timeline` bridging to Timeline Indexer with paging tokens, guardrails, and audit logging. Dependencies: CONCELIER-WEB-OBS-51-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OBS-52-001 `Timeline streaming` | TODO | Provide SSE stream `/obs/concelier/timeline` bridging to Timeline Indexer with paging tokens, guardrails, and audit logging. Dependencies: CONCELIER-WEB-OBS-51-001. | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
|
|
||||||
@@ -194,7 +199,7 @@ CONCELIER-WEB-OBS-54-001 `Attestation exposure` | TODO | Provide `/attestations/
|
|||||||
CONCELIER-WEB-OBS-55-001 `Incident mode toggles` | TODO | Implement incident mode toggle endpoints, propagate to orchestrator/locker, and document cooldown/backoff semantics. Dependencies: CONCELIER-WEB-OBS-54-001. | Concelier WebService Guild, DevOps Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
CONCELIER-WEB-OBS-55-001 `Incident mode toggles` | TODO | Implement incident mode toggle endpoints, propagate to orchestrator/locker, and document cooldown/backoff semantics. Dependencies: CONCELIER-WEB-OBS-54-001. | Concelier WebService Guild, DevOps Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
FEEDCONN-CCCS-02-009 Version range provenance (Oct 2025) | BE-Conn-CCCS | **TODO (due 2025-10-21)** – Map CCCS advisories into the new `advisory_observations.affected.versions[]` structure, preserving each upstream range with provenance anchors (`cccs:{serial}:{index}`) and normalized comparison keys. Update mapper tests/fixtures for the Link-Not-Merge schema and verify linkset builders consume the ranges without relying on legacy merge counters.<br>2025-10-29: `docs/dev/normalized-rule-recipes.md` now documents helper snippets for building observation version entries—use them instead of merge-specific builders and refresh fixtures with `UPDATE_CCCS_FIXTURES=1`. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Cccs/TASKS.md)
|
FEEDCONN-CCCS-02-009 Version range provenance (Oct 2025) | BE-Conn-CCCS | **TODO (due 2025-10-21)** – Map CCCS advisories into the new `advisory_observations.affected.versions[]` structure, preserving each upstream range with provenance anchors (`cccs:{serial}:{index}`) and normalized comparison keys. Update mapper tests/fixtures for the Link-Not-Merge schema and verify linkset builders consume the ranges without relying on legacy merge counters.<br>2025-10-29: `docs/dev/normalized-rule-recipes.md` now documents helper snippets for building observation version entries—use them instead of merge-specific builders and refresh fixtures with `UPDATE_CCCS_FIXTURES=1`. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Cccs/TASKS.md)
|
||||||
FEEDCONN-CERTBUND-02-010 Version range provenance | BE-Conn-CERTBUND | **TODO (due 2025-10-22)** – Translate `product.Versions` phrases (e.g., `2023.1 bis 2024.2`, `alle`) into comparison helpers for `advisory_observations.affected.versions[]`, capturing provenance (`certbund:{advisoryId}:{vendor}`) and localisation notes. Update mapper/tests for the Link-Not-Merge schema and refresh documentation accordingly. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.CertBund/TASKS.md)
|
FEEDCONN-CERTBUND-02-010 Version range provenance | BE-Conn-CERTBUND | **TODO (due 2025-10-22)** – Translate `product.Versions` phrases (e.g., `2023.1 bis 2024.2`, `alle`) into comparison helpers for `advisory_observations.affected.versions[]`, capturing provenance (`certbund:{advisoryId}:{vendor}`) and localisation notes. Update mapper/tests for the Link-Not-Merge schema and refresh documentation accordingly. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.CertBund/TASKS.md)
|
||||||
FEEDCONN-CISCO-02-009 SemVer range provenance | BE-Conn-Cisco | **TODO (due 2025-10-21)** – Emit Cisco SemVer ranges into `advisory_observations.affected.versions[]` with provenance identifiers (`cisco:{productId}`) and deterministic comparison keys. Update mapper/tests for the Link-Not-Merge schema and replace legacy merge counter checks with observation/linkset validation. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Vndr.Cisco/TASKS.md)
|
FEEDCONN-CISCO-02-009 SemVer range provenance | BE-Conn-Cisco | **DOING (2025-11-08)** – Emitting Cisco SemVer ranges into `advisory_observations.affected.versions[]` with provenance identifiers (`cisco:{productId}`) and deterministic comparison keys. Updating mapper/tests for the Link-Not-Merge schema and replacing legacy merge counter checks with observation/linkset validation. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Vndr.Cisco/TASKS.md)
|
||||||
FEEDCONN-ICSCISA-02-012 Version range provenance | BE-Conn-ICS-CISA | **DONE (2025-11-03)** – Promote existing firmware/semver data into `advisory_observations.affected.versions[]` entries with deterministic comparison keys and provenance identifiers (`ics-cisa:{advisoryId}:{product}`). Add regression coverage for mixed firmware strings and raise a Models ticket only when observation schema needs a new comparison helper.<br>2025-10-29: Follow `docs/dev/normalized-rule-recipes.md` §2 to build observation version entries and log failures without invoking the retired merge helpers.<br>2025-11-03: Completed – connector now normalizes semver ranges with provenance notes, RSS fallback content clears the AOC guard, and end-to-end Fetch/Parse/Map integration tests pass. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Ics.Cisa/TASKS.md)
|
FEEDCONN-ICSCISA-02-012 Version range provenance | BE-Conn-ICS-CISA | **DONE (2025-11-03)** – Promote existing firmware/semver data into `advisory_observations.affected.versions[]` entries with deterministic comparison keys and provenance identifiers (`ics-cisa:{advisoryId}:{product}`). Add regression coverage for mixed firmware strings and raise a Models ticket only when observation schema needs a new comparison helper.<br>2025-10-29: Follow `docs/dev/normalized-rule-recipes.md` §2 to build observation version entries and log failures without invoking the retired merge helpers.<br>2025-11-03: Completed – connector now normalizes semver ranges with provenance notes, RSS fallback content clears the AOC guard, and end-to-end Fetch/Parse/Map integration tests pass. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Ics.Cisa/TASKS.md)
|
||||||
FEEDCONN-KISA-02-008 Firmware range provenance | BE-Conn-KISA, Models | **DONE (2025-11-04)** – Define comparison helpers for Hangul-labelled firmware ranges (`XFU 1.0.1.0084 ~ 2.0.1.0034`) and map them into `advisory_observations.affected.versions[]` with provenance tags. Coordinate with Models only if a new comparison scheme is required, then update localisation notes and fixtures for the Link-Not-Merge schema.<br>2025-11-03: Analysis in progress – auditing existing mapper output/fixtures ahead of implementing firmware range normalization and provenance wiring.<br>2025-11-03: SemVer normalization helper wired through `KisaMapper` with provenance slugs + vendor extensions; integration tests updated and green, follow-up capture for additional Hangul exclusivity markers queued before completion.<br>2025-11-03: Extended connector tests to cover single-ended (`이상`, `초과`, `이하`, `미만`) and non-numeric phrases, verifying normalized rule types (`gt`, `gte`, `lt`, `lte`) and fallback behaviour; broader corpus review remains before transitioning to DONE.<br>2025-11-03: Captured the top 10 `detailDos.do?IDX=` pages into `seed-data/kisa/html/` via `scripts/kisa_capture_html.py`; JSON endpoint (`rssDetailData.do?IDX=…`) now returns error pages, so connector updates must parse the embedded HTML or secure authenticated API access before closing.<br>2025-11-04: Fetch + parse pipeline now consumes the HTML detail pages end to end (metadata persisted, DOM parser extracts vendor/product ranges); fixtures/tests operate on the HTML snapshots to guard normalized SemVer + vendor extension expectations and severity extraction. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Kisa/TASKS.md)
|
FEEDCONN-KISA-02-008 Firmware range provenance | BE-Conn-KISA, Models | **DONE (2025-11-04)** – Define comparison helpers for Hangul-labelled firmware ranges (`XFU 1.0.1.0084 ~ 2.0.1.0034`) and map them into `advisory_observations.affected.versions[]` with provenance tags. Coordinate with Models only if a new comparison scheme is required, then update localisation notes and fixtures for the Link-Not-Merge schema.<br>2025-11-03: Analysis in progress – auditing existing mapper output/fixtures ahead of implementing firmware range normalization and provenance wiring.<br>2025-11-03: SemVer normalization helper wired through `KisaMapper` with provenance slugs + vendor extensions; integration tests updated and green, follow-up capture for additional Hangul exclusivity markers queued before completion.<br>2025-11-03: Extended connector tests to cover single-ended (`이상`, `초과`, `이하`, `미만`) and non-numeric phrases, verifying normalized rule types (`gt`, `gte`, `lt`, `lte`) and fallback behaviour; broader corpus review remains before transitioning to DONE.<br>2025-11-03: Captured the top 10 `detailDos.do?IDX=` pages into `seed-data/kisa/html/` via `scripts/kisa_capture_html.py`; JSON endpoint (`rssDetailData.do?IDX=…`) now returns error pages, so connector updates must parse the embedded HTML or secure authenticated API access before closing.<br>2025-11-04: Fetch + parse pipeline now consumes the HTML detail pages end to end (metadata persisted, DOM parser extracts vendor/product ranges); fixtures/tests operate on the HTML snapshots to guard normalized SemVer + vendor extension expectations and severity extraction. | CONCELIER-LNM-21-001 (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Kisa/TASKS.md)
|
||||||
FEEDCONN-SHARED-STATE-003 Source state seeding helper | Tools Guild, BE-Conn-MSRC | **DONE (2025-11-04)** – Delivered `SourceStateSeeder` CLI + processor APIs, Mongo fixtures, and MSRC runbook updates. Seeds raw docs + cursor state deterministically; tests cover happy/path/idempotent flows (`dotnet test src/Concelier/__Tests/StellaOps.Concelier.Connector.Common.Tests/...` – note: requires `libcrypto.so.1.1` when running Mongo2Go locally). | Tools (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Common/TASKS.md)
|
FEEDCONN-SHARED-STATE-003 Source state seeding helper | Tools Guild, BE-Conn-MSRC | **DONE (2025-11-04)** – Delivered `SourceStateSeeder` CLI + processor APIs, Mongo fixtures, and MSRC runbook updates. Seeds raw docs + cursor state deterministically; tests cover happy/path/idempotent flows (`dotnet test src/Concelier/__Tests/StellaOps.Concelier.Connector.Common.Tests/...` – note: requires `libcrypto.so.1.1` when running Mongo2Go locally). | Tools (src/Concelier/__Libraries/StellaOps.Concelier.Connector.Common/TASKS.md)
|
||||||
@@ -212,7 +217,7 @@ Summary: Ingestion & Evidence focus on Concelier (phase VII).
|
|||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
MERGE-LNM-21-002 | DONE (2025-11-07) | Refactor or retire `AdvisoryMergeService` and related pipelines, ensuring callers transition to observation/linkset APIs; add compile-time analyzer preventing merge service usage.<br>2025-11-03: Began dependency audit and call-site inventory ahead of deprecation plan; cataloging service registrations/tests referencing merge APIs.<br>2025-11-05 14:42Z: Drafted `concelier:features:noMergeEnabled` gating, merge job allowlist handling, and deprecation/telemetry changes prior to analyzer rollout.<br>2025-11-06 16:10Z: Landed analyzer project (`CONCELIER0002`), wired into Concelier WebService/tests, and updated docs to direct suppressions through explicit migration notes.<br>2025-11-07 03:25Z: Default-on toggle + job gating surfaced ingestion test brittleness; guard/migration diagnostics capture requests missing `upstream.contentHash`.<br>2025-11-07 19:45Z: Set `ConcelierOptions.Features.NoMergeEnabled` default to `true`, added regression coverage (`Features_NoMergeEnabled_DefaultsToTrue`), and rechecked ingest helpers to carry canonical links. Remote .NET 10 CLI run remains queued for validation. | BE-Merge (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
MERGE-LNM-21-002 | DONE (2025-11-07) | Refactor or retire `AdvisoryMergeService` and related pipelines, ensuring callers transition to observation/linkset APIs; add compile-time analyzer preventing merge service usage.<br>2025-11-03: Began dependency audit and call-site inventory ahead of deprecation plan; cataloging service registrations/tests referencing merge APIs.<br>2025-11-05 14:42Z: Drafted `concelier:features:noMergeEnabled` gating, merge job allowlist handling, and deprecation/telemetry changes prior to analyzer rollout.<br>2025-11-06 16:10Z: Landed analyzer project (`CONCELIER0002`), wired into Concelier WebService/tests, and updated docs to direct suppressions through explicit migration notes.<br>2025-11-07 03:25Z: Default-on toggle + job gating surfaced ingestion test brittleness; guard/migration diagnostics capture requests missing `upstream.contentHash`.<br>2025-11-07 19:45Z: Set `ConcelierOptions.Features.NoMergeEnabled` default to `true`, added regression coverage (`Features_NoMergeEnabled_DefaultsToTrue`), and rechecked ingest helpers to carry canonical links. Remote .NET 10 CLI run remains queued for validation. | BE-Merge (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
||||||
MERGE-LNM-21-003 Determinism/test updates | DOING (2025-11-07) | QA Guild, BE-Merge | Replace merge determinism suites with observation/linkset regression tests verifying no data mutation and conflicts remain visible. Dependencies: MERGE-LNM-21-002.<br>2025-11-07: Drafting test migration plan (`docs/dev/lnm-determinism-tests.md`) to map legacy merge fixtures onto observation/linkset pipelines; identifying coverage gaps (conflict surfacing, raw vs canonical parity, hash stability).<br>2025-11-07 20:05Z: Landed `AdvisoryObservationFactoryTests.Create_IsDeterministicAcrossRuns` to cover canonical JSON stability and pruned the old merge determinism integration test. | MERGE-LNM-21-002 (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
MERGE-LNM-21-003 Determinism/test updates | DONE (2025-11-07) | QA Guild, BE-Merge | Replaced the retired merge determinism harness with observation/linkset/export regressions. `AdvisoryObservationFactoryTests` now assert raw reference parity + conflict notes, `AdvisoryEventLogTests` sort/uniquify conflict statement IDs, and `JsonExportSnapshotBuilderTests` guard digest parity across reordered input. `docs/dev/lnm-determinism-tests.md` checklist updated with the new coverage. | MERGE-LNM-21-002 (src/Concelier/__Libraries/StellaOps.Concelier.Merge/TASKS.md)
|
||||||
WEB-AOC-19-001 (dependency) | DONE (2025-11-07) | Shared guard primitives now enforce the top-level allowlist (`_id`, tenant, source, upstream, content, identifiers, linkset, supersedes, created/ingested timestamps, attributes) and emit the reusable `AocError` payload consumed by HTTP/CLI tooling. Extend `AocGuardOptions.AllowedTopLevelFields` when staging new schema fields to avoid false-positive `ERR_AOC_007` violations. | BE-Base Platform Guild (docs/aoc/guard-library.md, src/Web/StellaOps.Web/TASKS.md)
|
WEB-AOC-19-001 (dependency) | DONE (2025-11-07) | Shared guard primitives now enforce the top-level allowlist (`_id`, tenant, source, upstream, content, identifiers, linkset, supersedes, created/ingested timestamps, attributes) and emit the reusable `AocError` payload consumed by HTTP/CLI tooling. Extend `AocGuardOptions.AllowedTopLevelFields` when staging new schema fields to avoid false-positive `ERR_AOC_007` violations. | BE-Base Platform Guild (docs/aoc/guard-library.md, src/Web/StellaOps.Web/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -278,7 +283,7 @@ EXCITITOR-OAS-61-001 `Spec coverage` | TODO | Update VEX OAS to include observat
|
|||||||
EXCITITOR-OAS-61-002 `Example catalog` | TODO | Provide examples for VEX justifications, statuses, conflicts; ensure SDK docs reference them. Dependencies: EXCITITOR-OAS-61-001. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-OAS-61-002 `Example catalog` | TODO | Provide examples for VEX justifications, statuses, conflicts; ensure SDK docs reference them. Dependencies: EXCITITOR-OAS-61-001. | Excititor Core Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-OAS-62-001 `SDK smoke tests` | TODO | Add SDK scenarios for VEX observation queries and conflict handling to language smoke suites. Dependencies: EXCITITOR-OAS-61-002. | Excititor Core Guild, SDK Generator Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-OAS-62-001 `SDK smoke tests` | TODO | Add SDK scenarios for VEX observation queries and conflict handling to language smoke suites. Dependencies: EXCITITOR-OAS-61-002. | Excititor Core Guild, SDK Generator Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-OAS-63-001 `Deprecation headers` | TODO | Add deprecation metadata and notifications for legacy VEX routes. Dependencies: EXCITITOR-OAS-62-001. | Excititor Core Guild, API Governance Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-OAS-63-001 `Deprecation headers` | TODO | Add deprecation metadata and notifications for legacy VEX routes. Dependencies: EXCITITOR-OAS-62-001. | Excititor Core Guild, API Governance Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-OBS-50-001 `Telemetry adoption` | TODO | Integrate telemetry core across VEX ingestion/linking, ensuring spans/logs capture tenant, product scope, upstream id, justification hash, and trace IDs. | Excititor Core Guild, Observability Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-OBS-50-001 `Telemetry adoption` | DONE (2025-11-07) | Integrate telemetry core across VEX ingestion/linking, ensuring spans/logs capture tenant, product scope, upstream id, justification hash, and trace IDs. | Excititor Core Guild, Observability Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
EXCITITOR-OBS-51-001 `Metrics & SLOs` | TODO | Publish metrics for VEX ingest latency, scope resolution success, conflict rate, signature verification failures. Define SLOs (link latency P95 <30s) and configure burn-rate alerts. Dependencies: EXCITITOR-OBS-50-001. | Excititor Core Guild, DevOps Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
EXCITITOR-OBS-51-001 `Metrics & SLOs` | TODO | Publish metrics for VEX ingest latency, scope resolution success, conflict rate, signature verification failures. Define SLOs (link latency P95 <30s) and configure burn-rate alerts. Dependencies: EXCITITOR-OBS-50-001. | Excititor Core Guild, DevOps Guild (src/Excititor/__Libraries/StellaOps.Excititor.Core/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -332,16 +337,16 @@ Summary: Ingestion & Evidence focus on Excititor (phase VI).
|
|||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
EXCITITOR-WEB-AIRGAP-58-001 | TODO | Emit timeline events for VEX bundle imports with bundle ID, scope, and actor metadata. Dependencies: EXCITITOR-WEB-AIRGAP-57-001. | Excititor WebService Guild, AirGap Importer Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-AIRGAP-58-001 | TODO | Emit timeline events for VEX bundle imports with bundle ID, scope, and actor metadata. Dependencies: EXCITITOR-WEB-AIRGAP-57-001. | Excititor WebService Guild, AirGap Importer Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-AOC-19-001 `Raw VEX ingestion APIs` | TODO | Implement `POST /ingest/vex`, `GET /vex/raw*`, and `POST /aoc/verify` endpoints. Enforce Authority scopes, tenant injection, and guard pipeline to ensure only immutable VEX facts are persisted. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-AOC-19-001 `Raw VEX ingestion APIs` | DONE (2025-11-08) | Implement `POST /ingest/vex`, `GET /vex/raw*`, and `POST /aoc/verify` endpoints. Enforce Authority scopes, tenant injection, and guard pipeline to ensure only immutable VEX facts are persisted. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-AOC-19-002 `AOC observability + metrics` | TODO | Export metrics (`ingestion_write_total`, `aoc_violation_total`, signature verification counters) and tracing spans matching Conseiller naming. Ensure structured logging includes tenant, source vendor, upstream id, and content hash. Dependencies: EXCITITOR-WEB-AOC-19-001. | Excititor WebService Guild, Observability Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-AOC-19-002 `AOC observability + metrics` | DONE (2025-11-08) | Export metrics (`ingestion_write_total`, `aoc_violation_total`, signature verification counters) and tracing spans matching Conseiller naming. Ensure structured logging includes tenant, source vendor, upstream id, and content hash. Dependencies: EXCITITOR-WEB-AOC-19-001. | Excititor WebService Guild, Observability Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-AOC-19-003 `Guard + schema test harness` | TODO | Add unit/integration tests for schema validation, forbidden field rejection (`ERR_AOC_001/006/007`), and supersedes behavior using CycloneDX-VEX & CSAF fixtures with deterministic expectations. Dependencies: EXCITITOR-WEB-AOC-19-002. | QA Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-AOC-19-003 `Guard + schema test harness` | DONE (2025-11-08) | Add unit/integration tests for schema validation, forbidden field rejection (`ERR_AOC_001/006/007`), and supersedes behavior using CycloneDX-VEX & CSAF fixtures with deterministic expectations. Dependencies: EXCITITOR-WEB-AOC-19-002. | QA Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-AOC-19-004 `Batch ingest validation` | TODO | Build large fixture ingest covering mixed VEX statuses, verifying raw storage parity, metrics, and CLI `aoc verify` compatibility. Document load test/runbook updates. Dependencies: EXCITITOR-WEB-AOC-19-003. | Excititor WebService Guild, QA Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-AOC-19-004 `Batch ingest validation` | DONE (2025-11-08) | Build large fixture ingest covering mixed VEX statuses, verifying raw storage parity, metrics, and CLI `aoc verify` compatibility. Document load test/runbook updates. Dependencies: EXCITITOR-WEB-AOC-19-003. | Excititor WebService Guild, QA Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OAS-61-001 | TODO | Implement `/.well-known/openapi` discovery endpoint with spec version metadata. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OAS-61-001 | TODO | Implement `/.well-known/openapi` discovery endpoint with spec version metadata. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OAS-61-002 | TODO | Standardize error envelope responses and update controller/unit tests. Dependencies: EXCITITOR-WEB-OAS-61-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OAS-61-002 | TODO | Standardize error envelope responses and update controller/unit tests. Dependencies: EXCITITOR-WEB-OAS-61-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OAS-62-001 | TODO | Add curated examples for VEX observation/linkset endpoints and ensure portal displays them. Dependencies: EXCITITOR-WEB-OAS-61-002. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OAS-62-001 | TODO | Add curated examples for VEX observation/linkset endpoints and ensure portal displays them. Dependencies: EXCITITOR-WEB-OAS-61-002. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OAS-63-001 | TODO | Emit deprecation headers and update docs for retiring VEX APIs. Dependencies: EXCITITOR-WEB-OAS-62-001. | Excititor WebService Guild, API Governance Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OAS-63-001 | TODO | Emit deprecation headers and update docs for retiring VEX APIs. Dependencies: EXCITITOR-WEB-OAS-62-001. | Excititor WebService Guild, API Governance Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OBS-50-001 `Telemetry adoption` | TODO | Adopt telemetry core for VEX APIs, ensure responses include trace IDs & correlation headers, and update structured logging for read endpoints. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OBS-50-001 `Telemetry adoption` | DONE (2025-11-07) | Adopt telemetry core for VEX APIs, ensure responses include trace IDs & correlation headers, and update structured logging for read endpoints. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OBS-51-001 `Observability health endpoints` | TODO | Implement `/obs/excititor/health` summarizing ingest/link SLOs, signature failure counts, and conflict trends for Console dashboards. Dependencies: EXCITITOR-WEB-OBS-50-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OBS-51-001 `Observability health endpoints` | DONE (2025-11-08) | Implement `/obs/excititor/health` summarizing ingest/link SLOs, signature failure counts, and conflict trends for Console dashboards. Dependencies: EXCITITOR-WEB-OBS-50-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OBS-52-001 `Timeline streaming` | TODO | Provide SSE bridge for VEX timeline events with tenant filters, pagination, and guardrails. Dependencies: EXCITITOR-WEB-OBS-51-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OBS-52-001 `Timeline streaming` | TODO | Provide SSE bridge for VEX timeline events with tenant filters, pagination, and guardrails. Dependencies: EXCITITOR-WEB-OBS-51-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OBS-53-001 `Evidence APIs` | TODO | Expose `/evidence/vex/*` endpoints that fetch locker bundles, enforce scopes, and surface verification metadata. Dependencies: EXCITITOR-WEB-OBS-52-001. | Excititor WebService Guild, Evidence Locker Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OBS-53-001 `Evidence APIs` | TODO | Expose `/evidence/vex/*` endpoints that fetch locker bundles, enforce scopes, and surface verification metadata. Dependencies: EXCITITOR-WEB-OBS-52-001. | Excititor WebService Guild, Evidence Locker Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
EXCITITOR-WEB-OBS-54-001 `Attestation APIs` | TODO | Add `/attestations/vex/*` endpoints returning DSSE verification state, builder identity, and chain-of-custody links. Dependencies: EXCITITOR-WEB-OBS-53-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
EXCITITOR-WEB-OBS-54-001 `Attestation APIs` | TODO | Add `/attestations/vex/*` endpoints returning DSSE verification state, builder identity, and chain-of-custody links. Dependencies: EXCITITOR-WEB-OBS-53-001. | Excititor WebService Guild (src/Excititor/StellaOps.Excititor.WebService/TASKS.md)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ LEDGER-29-001 | DONE (2025-11-03) | Design ledger & projection schemas (tables/i
|
|||||||
LEDGER-29-002 | DONE (2025-11-03) | Implement ledger write API (`POST /vuln/ledger/events`) with validation, idempotency, hash chaining, and Merkle root computation job.<br>2025-11-03: Web service + domain scaffolding landed with canonical hashing helpers, in-memory repository, Merkle scheduler stub, request/response contracts, and unit tests covering hashing & conflict flows. Dependencies: LEDGER-29-001. | Findings Ledger Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-002 | DONE (2025-11-03) | Implement ledger write API (`POST /vuln/ledger/events`) with validation, idempotency, hash chaining, and Merkle root computation job.<br>2025-11-03: Web service + domain scaffolding landed with canonical hashing helpers, in-memory repository, Merkle scheduler stub, request/response contracts, and unit tests covering hashing & conflict flows. Dependencies: LEDGER-29-001. | Findings Ledger Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-003 | DONE (2025-11-03) | Build projector worker that derives `findings_projection` rows from ledger events + policy determinations; ensure idempotent replay keyed by `(tenant,finding_id,policy_version)`. <br>2025-11-03: Postgres projection services landed with replay checkpoints, fixtures, and unit coverage (LEDGER-29-003). Dependencies: LEDGER-29-002. | Findings Ledger Guild, Scheduler Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-003 | DONE (2025-11-03) | Build projector worker that derives `findings_projection` rows from ledger events + policy determinations; ensure idempotent replay keyed by `(tenant,finding_id,policy_version)`. <br>2025-11-03: Postgres projection services landed with replay checkpoints, fixtures, and unit coverage (LEDGER-29-003). Dependencies: LEDGER-29-002. | Findings Ledger Guild, Scheduler Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-004 | DONE (2025-11-04) | Integrate Policy Engine batch evaluation (baseline + simulate) with projector; cache rationale references.<br>2025-11-04: Ledger service now calls `/api/policy/eval/batch` with resilient HttpClient, shared cache, and inline fallback; documentation/config samples updated; ledger tests executed (`dotnet test src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj --no-restore`). Dependencies: LEDGER-29-003. | Findings Ledger Guild, Policy Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-004 | DONE (2025-11-04) | Integrate Policy Engine batch evaluation (baseline + simulate) with projector; cache rationale references.<br>2025-11-04: Ledger service now calls `/api/policy/eval/batch` with resilient HttpClient, shared cache, and inline fallback; documentation/config samples updated; ledger tests executed (`dotnet test src/Findings/__Tests/StellaOps.Findings.Ledger.Tests/StellaOps.Findings.Ledger.Tests.csproj --no-restore`). Dependencies: LEDGER-29-003. | Findings Ledger Guild, Policy Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-005 | TODO | Implement workflow mutation handlers (assign, comment, accept-risk, target-fix, verify-fix, reopen) producing ledger events with validation and attachments metadata. Dependencies: LEDGER-29-004. | Findings Ledger Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-005 | DONE | Implement workflow mutation handlers (assign, comment, accept-risk, target-fix, verify-fix, reopen) producing ledger events with validation and attachments metadata. Dependencies: LEDGER-29-004. | Findings Ledger Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-006 | TODO | Integrate attachment encryption (KMS envelope), signed URL issuance, CSRF protection hooks for Console. Dependencies: LEDGER-29-005. | Findings Ledger Guild, Security Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-006 | DONE | Integrate attachment encryption (KMS envelope), signed URL issuance, CSRF protection hooks for Console. Dependencies: LEDGER-29-005. | Findings Ledger Guild, Security Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-007 | TODO | Instrument metrics (`ledger_write_latency`, `projection_lag_seconds`, `ledger_events_total`), structured logs, and Merkle anchoring alerts; publish dashboards. Dependencies: LEDGER-29-006. | Findings Ledger Guild, Observability Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-007 | TODO | Instrument metrics (`ledger_write_latency`, `projection_lag_seconds`, `ledger_events_total`), structured logs, and Merkle anchoring alerts; publish dashboards. Dependencies: LEDGER-29-006. | Findings Ledger Guild, Observability Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-008 | TODO | Develop unit/property/integration tests, replay/restore tooling, determinism harness, and load tests at 5M findings/tenant. Dependencies: LEDGER-29-007. | Findings Ledger Guild, QA Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-008 | TODO | Develop unit/property/integration tests, replay/restore tooling, determinism harness, and load tests at 5M findings/tenant. Dependencies: LEDGER-29-007. | Findings Ledger Guild, QA Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
LEDGER-29-009 | TODO | Provide deployment manifests (Helm/Compose), backup/restore guidance, Merkle anchor externalization (optional), and offline kit instructions. Dependencies: LEDGER-29-008. | Findings Ledger Guild, DevOps Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
LEDGER-29-009 | TODO | Provide deployment manifests (Helm/Compose), backup/restore guidance, Merkle anchor externalization (optional), and offline kit instructions. Dependencies: LEDGER-29-008. | Findings Ledger Guild, DevOps Guild (src/Findings/StellaOps.Findings.Ledger/TASKS.md)
|
||||||
@@ -106,8 +106,8 @@ POLICY-ENGINE-20-008 | TODO | Add unit/property/golden/perf suites covering poli
|
|||||||
POLICY-ENGINE-20-009 | TODO | Define Mongo schemas/indexes for `policies`, `policy_runs`, and `effective_finding_*`; implement migrations and tenant enforcement. Dependencies: POLICY-ENGINE-20-008. | Policy Guild, Storage Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-20-009 | TODO | Define Mongo schemas/indexes for `policies`, `policy_runs`, and `effective_finding_*`; implement migrations and tenant enforcement. Dependencies: POLICY-ENGINE-20-008. | Policy Guild, Storage Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-27-001 | TODO | Extend compile outputs to include rule coverage metadata, symbol table, inline documentation, and rule index for editor autocomplete; persist deterministic hashes. Dependencies: POLICY-ENGINE-20-009. | Policy Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-27-001 | TODO | Extend compile outputs to include rule coverage metadata, symbol table, inline documentation, and rule index for editor autocomplete; persist deterministic hashes. Dependencies: POLICY-ENGINE-20-009. | Policy Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-27-002 | TODO | Enhance simulate endpoints to emit rule firing counts, heatmap aggregates, sampled explain traces with deterministic ordering, and delta summaries for quick/batch sims. Dependencies: POLICY-ENGINE-27-001. | Policy Guild, Observability Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-27-002 | TODO | Enhance simulate endpoints to emit rule firing counts, heatmap aggregates, sampled explain traces with deterministic ordering, and delta summaries for quick/batch sims. Dependencies: POLICY-ENGINE-27-001. | Policy Guild, Observability Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-27-003 | TODO | Implement complexity/time limit enforcement with compiler scoring, configurable thresholds, and structured diagnostics (`ERR_POL_COMPLEXITY`). Dependencies: POLICY-ENGINE-27-002. | Policy Guild, Security Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-27-003 | DONE | Implement complexity/time limit enforcement with compiler scoring, configurable thresholds, and structured diagnostics (`ERR_POL_COMPLEXITY`). Dependencies: POLICY-ENGINE-27-002. | Policy Guild, Security Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-27-004 | TODO | Update golden/property tests to cover new coverage metrics, symbol tables, explain traces, and complexity limits; provide fixtures for Registry/Console integration. Dependencies: POLICY-ENGINE-27-003. | Policy Guild, QA Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-27-004 | DONE | Update golden/property tests to cover new coverage metrics, symbol tables, explain traces, and complexity limits; provide fixtures for Registry/Console integration. Dependencies: POLICY-ENGINE-27-003. | Policy Guild, QA Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-29-001 | TODO | Implement batch evaluation endpoint (`POST /policy/eval/batch`) returning determinations + rationale chain for sets of `(artifact,purl,version,advisory)` tuples; support pagination and cost budgets. Dependencies: POLICY-ENGINE-27-004. | Policy Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-29-001 | TODO | Implement batch evaluation endpoint (`POST /policy/eval/batch`) returning determinations + rationale chain for sets of `(artifact,purl,version,advisory)` tuples; support pagination and cost budgets. Dependencies: POLICY-ENGINE-27-004. | Policy Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
POLICY-ENGINE-29-002 | TODO | Provide streaming simulation API comparing two policy versions, returning per-finding deltas without writes; align determinism with Vuln Explorer simulation. Dependencies: POLICY-ENGINE-29-001. | Policy Guild, Findings Ledger Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
POLICY-ENGINE-29-002 | TODO | Provide streaming simulation API comparing two policy versions, returning per-finding deltas without writes; align determinism with Vuln Explorer simulation. Dependencies: POLICY-ENGINE-29-001. | Policy Guild, Findings Ledger Guild (src/Policy/StellaOps.Policy.Engine/TASKS.md)
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ Notes:
|
|||||||
- 2025-10-29: JSON parsers for Java/Node.js/Python/Go implemented; artifacts stored on filesystem with SHA-256 and callgraphs upserted into Mongo.
|
- 2025-10-29: JSON parsers for Java/Node.js/Python/Go implemented; artifacts stored on filesystem with SHA-256 and callgraphs upserted into Mongo.
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
|
SIGNALS-24-001 | DOING (2025-11-07) | Stand up Signals API skeleton with RBAC, sealed-mode config, DPoP/mTLS enforcement, and `/facts` scaffolding so downstream ingestion work can begin. Dependencies: AUTH-SIG-26-001. | Signals Guild, Authority Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
||||||
|
SIGNALS-24-002 | DOING (2025-11-07) | Implement callgraph ingestion/normalization (Java/Node/Python/Go) with CAS persistence and retrieval APIs to feed reachability scoring. Dependencies: SIGNALS-24-001. | Signals Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
||||||
SIGNALS-24-003 | BLOCKED (2025-10-27) | Implement runtime facts ingestion endpoint and normalizer (process, sockets, container metadata) populating `context_facts` with AOC provenance.<br>2025-10-27: Depends on `SIGNALS-24-001` for base API host and authentication plumbing. | Signals Guild, Runtime Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
SIGNALS-24-003 | BLOCKED (2025-10-27) | Implement runtime facts ingestion endpoint and normalizer (process, sockets, container metadata) populating `context_facts` with AOC provenance.<br>2025-10-27: Depends on `SIGNALS-24-001` for base API host and authentication plumbing. | Signals Guild, Runtime Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
||||||
|
> 2025-11-07: Waiting on SIGNALS-24-001 / SIGNALS-24-002 DOING work to land before flipping this to DOING.
|
||||||
|
> 2025-11-07: Upstream SIGNALS-24-001 / SIGNALS-24-002 now DOING; this flips to DOING once host + callgraph ingestion merge.
|
||||||
|
> 2025-11-08: Targeting 2025-11-09 merge for SIGNALS-24-001/002; schema + AOC contract drafted so SIGNALS-24-003 can move to DOING immediately after those PRs land (dependencies confirmed, none missing).
|
||||||
SIGNALS-24-004 | BLOCKED (2025-10-27) | Deliver reachability scoring engine producing states/scores and writing to `reachability_facts`; expose configuration for weights. Dependencies: SIGNALS-24-003.<br>2025-10-27: Upstream ingestion pipelines (`SIGNALS-24-002/003`) blocked; scoring engine cannot proceed. | Signals Guild, Data Science (src/Signals/StellaOps.Signals/TASKS.md)
|
SIGNALS-24-004 | BLOCKED (2025-10-27) | Deliver reachability scoring engine producing states/scores and writing to `reachability_facts`; expose configuration for weights. Dependencies: SIGNALS-24-003.<br>2025-10-27: Upstream ingestion pipelines (`SIGNALS-24-002/003`) blocked; scoring engine cannot proceed. | Signals Guild, Data Science (src/Signals/StellaOps.Signals/TASKS.md)
|
||||||
SIGNALS-24-005 | BLOCKED (2025-10-27) | Implement Redis caches (`reachability_cache:*`), invalidation on new facts, and publish `signals.fact.updated` events. Dependencies: SIGNALS-24-004.<br>2025-10-27: Awaiting scoring engine and ingestion layers before wiring cache/events. | Signals Guild, Platform Events Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
SIGNALS-24-005 | BLOCKED (2025-10-27) | Implement Redis caches (`reachability_cache:*`), invalidation on new facts, and publish `signals.fact.updated` events. Dependencies: SIGNALS-24-004.<br>2025-10-27: Awaiting scoring engine and ingestion layers before wiring cache/events. | Signals Guild, Platform Events Guild (src/Signals/StellaOps.Signals/TASKS.md)
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ Task ID | State | Task description | Owners (Source)
|
|||||||
ORCH-SVC-38-101 | TODO | Standardize event envelope (policy/export/job lifecycle) with idempotency keys, ensure export/job failure events published to notifier bus with provenance metadata. Dependencies: ORCH-SVC-37-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
ORCH-SVC-38-101 | TODO | Standardize event envelope (policy/export/job lifecycle) with idempotency keys, ensure export/job failure events published to notifier bus with provenance metadata. Dependencies: ORCH-SVC-37-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
||||||
ORCH-SVC-41-101 | TODO | Register `pack-run` job type, persist run metadata, integrate logs/artifacts collection, and expose API for Task Runner scheduling. Dependencies: ORCH-SVC-38-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
ORCH-SVC-41-101 | TODO | Register `pack-run` job type, persist run metadata, integrate logs/artifacts collection, and expose API for Task Runner scheduling. Dependencies: ORCH-SVC-38-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
||||||
ORCH-SVC-42-101 | TODO | Stream pack run logs via SSE/WS, add manifest endpoints, enforce quotas, and emit pack run events to Notifications Studio. Dependencies: ORCH-SVC-41-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
ORCH-SVC-42-101 | TODO | Stream pack run logs via SSE/WS, add manifest endpoints, enforce quotas, and emit pack run events to Notifications Studio. Dependencies: ORCH-SVC-41-101. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
||||||
|
> 2025-11-07: Still NOT STARTED—Authority pack RBAC (AUTH-PACKS-43-001) remains BLOCKED pending these approvals/log-stream APIs. Not missing; needs staffing.
|
||||||
ORCH-TEN-48-001 | TODO | Include `tenant_id`/`project_id` in job specs, set DB session context before processing, enforce context on all queries, and reject jobs missing tenant metadata. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
ORCH-TEN-48-001 | TODO | Include `tenant_id`/`project_id` in job specs, set DB session context before processing, enforce context on all queries, and reject jobs missing tenant metadata. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator/TASKS.md)
|
||||||
WORKER-GO-32-001 | TODO | Bootstrap Go SDK project with configuration binding, auth headers, job claim/acknowledge client, and smoke sample. | Worker SDK Guild (src/Orchestrator/StellaOps.Orchestrator.WorkerSdk.Go/TASKS.md)
|
WORKER-GO-32-001 | TODO | Bootstrap Go SDK project with configuration binding, auth headers, job claim/acknowledge client, and smoke sample. | Worker SDK Guild (src/Orchestrator/StellaOps.Orchestrator.WorkerSdk.Go/TASKS.md)
|
||||||
WORKER-GO-32-002 | TODO | Add heartbeat/progress helpers, structured logging hooks, Prometheus metrics, and jittered retry defaults. Dependencies: WORKER-GO-32-001. | Worker SDK Guild (src/Orchestrator/StellaOps.Orchestrator.WorkerSdk.Go/TASKS.md)
|
WORKER-GO-32-002 | TODO | Add heartbeat/progress helpers, structured logging hooks, Prometheus metrics, and jittered retry defaults. Dependencies: WORKER-GO-32-001. | Worker SDK Guild (src/Orchestrator/StellaOps.Orchestrator.WorkerSdk.Go/TASKS.md)
|
||||||
@@ -91,6 +92,7 @@ SCHED-SURFACE-01 | TODO | Evaluate Surface.FS pointers when planning delta scans
|
|||||||
SCHED-VULN-29-001 | TODO | Expose resolver job APIs (`POST /vuln/resolver/jobs`, `GET /vuln/resolver/jobs/{id}`) to trigger candidate recomputation per artifact/policy change with RBAC and rate limits. | Scheduler WebService Guild, Findings Ledger Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-VULN-29-001 | TODO | Expose resolver job APIs (`POST /vuln/resolver/jobs`, `GET /vuln/resolver/jobs/{id}`) to trigger candidate recomputation per artifact/policy change with RBAC and rate limits. | Scheduler WebService Guild, Findings Ledger Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
SCHED-VULN-29-002 | TODO | Provide projector lag metrics endpoint and webhook notifications for backlog breaches consumed by DevOps dashboards. Dependencies: SCHED-VULN-29-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-VULN-29-002 | TODO | Provide projector lag metrics endpoint and webhook notifications for backlog breaches consumed by DevOps dashboards. Dependencies: SCHED-VULN-29-001. | Scheduler WebService Guild, Observability Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
SCHED-WEB-20-002 | BLOCKED (waiting on SCHED-WORKER-20-301) | Provide simulation trigger endpoint returning diff preview metadata and job state for UI/CLI consumption. | Scheduler WebService Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-WEB-20-002 | BLOCKED (waiting on SCHED-WORKER-20-301) | Provide simulation trigger endpoint returning diff preview metadata and job state for UI/CLI consumption. | Scheduler WebService Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
|
> 2025-11-07: Worker counterpart (SCHED-WORKER-20-301) now DOING; revisit once API scaffolding lands.
|
||||||
SCHED-WEB-21-004 | DONE (2025-11-04) | Persist graph job lifecycle to Mongo storage and publish `scheduler.graph.job.completed@1` events + outbound webhook to Cartographer. Dependencies: SCHED-WEB-20-002. | Scheduler WebService Guild, Scheduler Storage Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
SCHED-WEB-21-004 | DONE (2025-11-04) | Persist graph job lifecycle to Mongo storage and publish `scheduler.graph.job.completed@1` events + outbound webhook to Cartographer. Dependencies: SCHED-WEB-20-002. | Scheduler WebService Guild, Scheduler Storage Guild (src/Scheduler/StellaOps.Scheduler.WebService/TASKS.md)
|
||||||
> 2025-11-04: Graph job completions now persist to Mongo with optimistic guards, emit Redis/webhook notifications once per transition, and refresh result URI metadata idempotently (tests cover service + Mongo store paths).
|
> 2025-11-04: Graph job completions now persist to Mongo with optimistic guards, emit Redis/webhook notifications once per transition, and refresh result URI metadata idempotently (tests cover service + Mongo store paths).
|
||||||
SCHED-WORKER-21-203 | TODO | Export metrics (`graph_build_seconds`, `graph_jobs_inflight`, `overlay_lag_seconds`) and structured logs with tenant/graph identifiers. | Scheduler Worker Guild, Observability Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.Worker/TASKS.md)
|
SCHED-WORKER-21-203 | TODO | Export metrics (`graph_build_seconds`, `graph_jobs_inflight`, `overlay_lag_seconds`) and structured logs with tenant/graph identifiers. | Scheduler Worker Guild, Observability Guild (src/Scheduler/__Libraries/StellaOps.Scheduler.Worker/TASKS.md)
|
||||||
|
|||||||
@@ -227,6 +227,14 @@ WEB-AOC-19-001 `Shared AOC guard primitives` | DONE (2025-11-07) | Provide `AOCF
|
|||||||
WEB-AOC-19-002 `Provenance & signature helpers` | TODO | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-AOC-19-002 `Provenance & signature helpers` | TODO | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
WEB-AOC-19-003 `Analyzer + test fixtures` | TODO | Author Roslyn analyzer preventing ingestion modules from writing forbidden keys without guard, and provide shared test fixtures for guard validation used by Concelier/Excititor service tests. Dependencies: WEB-AOC-19-002. | QA Guild, BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-AOC-19-003 `Analyzer + test fixtures` | TODO | Author Roslyn analyzer preventing ingestion modules from writing forbidden keys without guard, and provide shared test fixtures for guard validation used by Concelier/Excititor service tests. Dependencies: WEB-AOC-19-002. | QA Guild, BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
WEB-CONSOLE-23-001 `Global posture endpoints` | TODO | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-CONSOLE-23-001 `Global posture endpoints` | TODO | Provide consolidated `/console/dashboard` and `/console/filters` APIs returning tenant-scoped aggregates (findings by severity, VEX override counts, advisory deltas, run health, policy change log). Enforce AOC labelling, deterministic ordering, and cursor-based pagination for drill-down hints. | BE-Base Platform Guild, Product Analytics Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
|
CONSOLE-VULN-29-001 `Vulnerability workspace` | DOING (2025-11-08) | Build `/console/vuln/*` APIs and filters surfacing tenant-scoped findings with policy/VEX badges so Docs/UI teams can document workflows. Dependencies: WEB-CONSOLE-23-001, CONCELIER-GRAPH-21-001. | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
|
> 2025-11-07: API scaffolding kicked off; `docs/advisory-ai/console.md` consuming placeholder responses until this lands. Scheduler/Signals hooks queued once filters stabilized.
|
||||||
|
> 2025-11-08: Driving filter + reachability badge wiring plus `/console/vuln/search` DTOs to keep DOCS-AIAI-31-004 on real payloads; aligning Signals/Scheduler dependencies now that upstream tickets exist.
|
||||||
|
> 2025-11-08: Published HTTP contract + sample payloads in `docs/api/console/workspaces.md` and `docs/api/console/samples/vuln-findings-sample.json` so Docs can stage screenshots while backend wires up.
|
||||||
|
CONSOLE-VEX-30-001 `VEX evidence workspace` | DOING (2025-11-08) | Provide `/console/vex/*` APIs streaming VEX statements, justification summaries, and advisory links with SSE refresh hooks. Dependencies: WEB-CONSOLE-23-001, EXCITITOR-CONSOLE-23-001. | Console Guild, BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
|
> 2025-11-07: Endpoint contract draft in progress to unblock DOCS-AIAI-31-004 screenshot capture once responses are wired.
|
||||||
|
> 2025-11-08: Building SSE controller + `/console/vex/events` payloads and syncing Scheduler Signals tasks so DOCS-AIAI-31-004 can embed live data.
|
||||||
|
> 2025-11-08: SSE schema + NDJSON sample captured in `docs/api/console/workspaces.md` and `docs/api/console/samples/vex-statement-sse.ndjson`; waiting on Scheduler topic hook-up.
|
||||||
WEB-CONSOLE-23-002 `Live status & SSE proxy` | TODO | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-CONSOLE-23-002 `Live status & SSE proxy` | TODO | Expose `/console/status` polling endpoint and `/console/runs/{id}/stream` SSE/WebSocket proxy with heartbeat/backoff, queue lag metrics, and auth scope enforcement. Surface request IDs + retry headers. Dependencies: WEB-CONSOLE-23-001. | BE-Base Platform Guild, Scheduler Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
WEB-CONSOLE-23-003 `Evidence export orchestrator` | TODO | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-CONSOLE-23-003 `Evidence export orchestrator` | TODO | Add `/console/exports` POST/GET routes coordinating evidence bundle creation, streaming CSV/JSON exports, checksum manifest retrieval, and signed attestation references. Ensure requests honor tenant + policy scopes and expose job tracking metadata. Dependencies: WEB-CONSOLE-23-002. | BE-Base Platform Guild, Policy Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
WEB-CONSOLE-23-004 `Global search router` | TODO | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
WEB-CONSOLE-23-004 `Global search router` | TODO | Implement `/console/search` endpoint accepting CVE/GHSA/PURL/SBOM identifiers, performing fan-out queries with caching, ranking, and deterministic tie-breaking. Return typed results for Console navigation; respect result caps and latency SLOs. Dependencies: WEB-CONSOLE-23-003. | BE-Base Platform Guild (src/Web/StellaOps.Web/TASKS.md)
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ DEVOPS-AIRGAP-56-001 | TODO | Ship deny-all egress policies for Kubernetes (Netw
|
|||||||
DEVOPS-AIRGAP-56-002 | TODO | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. Dependencies: DEVOPS-AIRGAP-56-001. | DevOps Guild, AirGap Importer Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-56-002 | TODO | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. Dependencies: DEVOPS-AIRGAP-56-001. | DevOps Guild, AirGap Importer Guild (ops/devops/TASKS.md)
|
||||||
DEVOPS-AIRGAP-56-003 | TODO | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-56-003 | TODO | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops/TASKS.md)
|
||||||
DEVOPS-AIRGAP-57-001 | TODO | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-57-001 | TODO | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops/TASKS.md)
|
||||||
DEVOPS-AIRGAP-57-002 | TODO | Configure sealed-mode CI tests that run services with sealed flag and ensure no egress occurs (iptables + mock DNS). Dependencies: DEVOPS-AIRGAP-57-001. | DevOps Guild, Authority Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-57-002 | DOING (2025-11-08) | Configure sealed-mode CI tests that run services with sealed flag and ensure no egress occurs (iptables + mock DNS). Dependencies: DEVOPS-AIRGAP-57-001. | DevOps Guild, Authority Guild (ops/devops/TASKS.md)
|
||||||
|
> 2025-11-07: Harness scaffolded at `ops/devops/sealed-mode-ci/*` (README + runner script); integrate into CI to unblock AUTH-AIRGAP-57-001.
|
||||||
|
> 2025-11-08: `sealed-mode-compose.yml`, `run-sealed-ci.sh`, and `egress_probe.py` committed plus a `sealed-mode-ci` workflow stage that uploads `artifacts/sealed-mode-ci/<commit>/authority-sealed-ci.json`; Authority can now read the sealed evidence feed.
|
||||||
DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops/TASKS.md)
|
||||||
DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops/TASKS.md)
|
DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops/TASKS.md)
|
||||||
DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops/TASKS.md)
|
DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops/TASKS.md)
|
||||||
@@ -235,4 +237,24 @@ PROV-OBS-54-001 | TODO | Deliver verification library that validates DSSE signat
|
|||||||
PROV-OBS-54-002 | TODO | Generate .NET global tool for local verification + embed command helpers for CLI `stella forensic verify`. Provide deterministic packaging and offline kit instructions. Dependencies: PROV-OBS-54-001. | Provenance Guild, DevEx/CLI Guild (src/Provenance/StellaOps.Provenance.Attestation/TASKS.md)
|
PROV-OBS-54-002 | TODO | Generate .NET global tool for local verification + embed command helpers for CLI `stella forensic verify`. Provide deterministic packaging and offline kit instructions. Dependencies: PROV-OBS-54-001. | Provenance Guild, DevEx/CLI Guild (src/Provenance/StellaOps.Provenance.Attestation/TASKS.md)
|
||||||
|
|
||||||
|
|
||||||
|
[Ops & Offline] 190.K) Sovereign Crypto Enablement
|
||||||
|
|
||||||
|
Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 140.A - Graph, Sprint 150.A - Orchestrator, Sprint 160.A - EvidenceLocker, Sprint 170.A - Notifier, Sprint 180.A - Cli
|
||||||
|
|
||||||
|
Summary: Deliver RootPack_RU-ready sovereign crypto providers (CryptoPro + PKCS#11), configuration knobs, deterministic tests, and repo-wide crypto routing audit.
|
||||||
|
|
||||||
|
Task ID | State | Task description | Owners (Source)
|
||||||
|
--- | --- | --- | ---
|
||||||
|
SEC-CRYPTO-90-001 | DONE (2025-11-07) | Produce RootPack_RU sovereign crypto implementation plan, identify provider strategy (CryptoPro + PKCS#11), and slot work into Sprint 190 with task breakdown. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-002 | DONE (2025-11-07) | Extend signature/catalog constants and configuration schema to recognize `GOST12-256/512`, regional crypto profiles, and provider preference ordering. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-003 | DONE (2025-11-07) | Implement `StellaOps.Cryptography.Plugin.CryptoPro` provider (sign/verify/JWK export) using CryptoPro CSP/GostCryptography with deterministic logging + tests. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-004 | DONE (2025-11-07) | Implement `StellaOps.Cryptography.Plugin.Pkcs11Gost` provider (Rutoken/JaCarta) via Pkcs11Interop, configurable slot/pin/module management, and disposal safeguards. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-005 | DONE (2025-11-08) | Add configuration-driven provider selection (`crypto.regionalProfiles`), CLI/diagnostic verb to list providers/keys, and deterministic telemetry for usage. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-006 | DONE (2025-11-08) | Build deterministic test harness (Streebog + signature vectors), manual runbooks for hardware validation, and capture RootPack audit metadata. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-007 | DONE (2025-11-08) | Package RootPack_RU artifacts (plugin binaries, config templates, trust anchors) and document deployment/install steps + compliance evidence. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
SEC-CRYPTO-90-008 | DONE (2025-11-08) | Audit repository for any cryptography usage bypassing `StellaOps.Cryptography` and file remediation tasks to route through providers. | Security Guild (src/__Libraries/StellaOps.Cryptography/TASKS.md)
|
||||||
|
AUTH-CRYPTO-90-001 | DOING (2025-11-08) | Migrate Authority signing/key-loading paths (provider registry + crypto hash) so regional bundles can select sovereign providers per docs/security/crypto-routing-audit-2025-11-07.md. | Authority Core & Security Guild (src/Authority/StellaOps.Authority/TASKS.md)
|
||||||
|
SCANNER-CRYPTO-90-001 | DONE (2025-11-08) | Route remaining Scanner Worker hashing/digest consumers (Surface pointers, manifest publishers, CAS helpers, Sbomer plugins) through ICryptoHash/provider registry.<br>2025-11-08: EntryTrace execution, Surface manifest writer, Local CAS client, and Sbomer descriptor generator now accept ICryptoHash; tests updated with CryptoHashFactory/TestCryptoHash helpers. | Scanner Worker Guild & Security Guild (src/Scanner/StellaOps.Scanner.Worker/TASKS.md)
|
||||||
|
CONCELIER-CRYPTO-90-001 | DOING (2025-11-08) | Route OpenAPI discovery hashing plus Concelier mirror/RU connectors through `ICryptoHash`/provider registry so sovereign bundles can swap CryptoPro/PKCS#11 keys without code changes. | Concelier WebService Guild & Security Guild (src/Concelier/StellaOps.Concelier.WebService/TASKS.md)
|
||||||
|
|
||||||
If all tasks are done - read next sprint section - SPRINT_200_documentation_process.md
|
If all tasks are done - read next sprint section - SPRINT_200_documentation_process.md
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A
|
|||||||
Summary: Documentation & Process focus on Docs Tasks (phase Md.I).
|
Summary: Documentation & Process focus on Docs Tasks (phase Md.I).
|
||||||
Task ID | State | Task description | Owners (Source)
|
Task ID | State | Task description | Owners (Source)
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
DOCS-AIAI-31-001 | TODO | Publish `/docs/advisory-ai/overview.md` covering capabilities, guardrails, RBAC. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-001 | DONE (2025-11-03) | Publish `/docs/advisory-ai/overview.md` covering capabilities, guardrails, RBAC. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-002 | TODO | Author `/docs/advisory-ai/architecture.md` detailing RAG pipeline, deterministics, caching, model options. Dependencies: DOCS-AIAI-31-001. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-002 | DONE (2025-11-03) | Author `/docs/advisory-ai/architecture.md` detailing RAG pipeline, deterministics, caching, model options. Dependencies: DOCS-AIAI-31-001. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-003 | TODO | Write `/docs/advisory-ai/api.md` describing endpoints, schemas, errors, rate limits. Dependencies: DOCS-AIAI-31-002. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-003 | DONE (2025-11-03) | Write `/docs/advisory-ai/api.md` describing endpoints, schemas, errors, rate limits. Dependencies: DOCS-AIAI-31-002. | Docs Guild, Advisory AI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-004 | TODO | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. Dependencies: DOCS-AIAI-31-003. | Docs Guild, Console Guild (docs/TASKS.md)
|
DOCS-AIAI-31-004 | DOING (2025-11-07) | Create `/docs/advisory-ai/console.md` with screenshots, a11y notes, copy-as-ticket instructions. Dependencies: DOCS-AIAI-31-003, CONSOLE-VULN-29-001, CONSOLE-VEX-30-001, EXCITITOR-CONSOLE-23-001. | Docs Guild, Console Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-005 | TODO | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. Dependencies: DOCS-AIAI-31-004. | Docs Guild, DevEx/CLI Guild (docs/TASKS.md)
|
DOCS-AIAI-31-005 | BLOCKED (2025-11-03) | Publish `/docs/advisory-ai/cli.md` covering commands, exit codes, scripting patterns. Dependencies: DOCS-AIAI-31-004, CLI-VULN-29-001, CLI-VEX-30-001. | Docs Guild, DevEx/CLI Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-006 | TODO | Update `/docs/policy/assistant-parameters.md` covering temperature, token limits, ranking weights, TTLs. Dependencies: DOCS-AIAI-31-005. | Docs Guild, Policy Guild (docs/TASKS.md)
|
DOCS-AIAI-31-006 | BLOCKED (2025-11-03) | Update `/docs/policy/assistant-parameters.md` covering temperature, token limits, ranking weights, TTLs. Dependencies: DOCS-AIAI-31-005, POLICY-ENGINE-31-001. | Docs Guild, Policy Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-007 | TODO | Write `/docs/security/assistant-guardrails.md` detailing redaction, injection defense, logging. Dependencies: DOCS-AIAI-31-006. | Docs Guild, Security Guild (docs/TASKS.md)
|
DOCS-AIAI-31-007 | DONE (2025-11-07) | Write `/docs/security/assistant-guardrails.md` detailing redaction, injection defense, logging. Dependencies: DOCS-AIAI-31-006. | Docs Guild, Security Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-008 | TODO | Publish `/docs/sbom/remediation-heuristics.md` (feasibility scoring, blast radius). Dependencies: DOCS-AIAI-31-007. | Docs Guild, SBOM Service Guild (docs/TASKS.md)
|
DOCS-AIAI-31-008 | BLOCKED (2025-11-03) | Publish `/docs/sbom/remediation-heuristics.md` (feasibility scoring, blast radius). Dependencies: DOCS-AIAI-31-007, SBOM-AIAI-31-001. | Docs Guild, SBOM Service Guild (docs/TASKS.md)
|
||||||
DOCS-AIAI-31-009 | TODO | Create `/docs/runbooks/assistant-ops.md` for warmup, cache priming, model outages, scaling. Dependencies: DOCS-AIAI-31-008. | Docs Guild, DevOps Guild (docs/TASKS.md)
|
DOCS-AIAI-31-009 | BLOCKED (2025-11-03) | Create `/docs/runbooks/assistant-ops.md` for warmup, cache priming, model outages, scaling. Dependencies: DOCS-AIAI-31-008, DEVOPS-AIAI-31-001. | Docs Guild, DevOps Guild (docs/TASKS.md)
|
||||||
DOCS-AIRGAP-56-001 | TODO | Publish `/docs/airgap/overview.md` outlining modes, lifecycle, responsibilities, and imposed rule banner. | Docs Guild, AirGap Controller Guild (docs/TASKS.md)
|
DOCS-AIRGAP-56-001 | TODO | Publish `/docs/airgap/overview.md` outlining modes, lifecycle, responsibilities, and imposed rule banner. | Docs Guild, AirGap Controller Guild (docs/TASKS.md)
|
||||||
DOCS-AIRGAP-56-002 | TODO | Author `/docs/airgap/sealing-and-egress.md` covering network policies, EgressPolicy facade usage, and verification steps. Dependencies: DOCS-AIRGAP-56-001. | Docs Guild, DevOps Guild (docs/TASKS.md)
|
DOCS-AIRGAP-56-002 | TODO | Author `/docs/airgap/sealing-and-egress.md` covering network policies, EgressPolicy facade usage, and verification steps. Dependencies: DOCS-AIRGAP-56-001. | Docs Guild, DevOps Guild (docs/TASKS.md)
|
||||||
DOCS-AIRGAP-56-003 | TODO | Create `/docs/airgap/mirror-bundles.md` describing bundle format, DSSE/TUF/Merkle validation, creation/import workflows. Dependencies: DOCS-AIRGAP-56-002. | Docs Guild, Exporter Guild (docs/TASKS.md)
|
DOCS-AIRGAP-56-003 | TODO | Create `/docs/airgap/mirror-bundles.md` describing bundle format, DSSE/TUF/Merkle validation, creation/import workflows. Dependencies: DOCS-AIRGAP-56-002. | Docs Guild, Exporter Guild (docs/TASKS.md)
|
||||||
|
|||||||
17
docs/implplan/SPRINT_201_reachability_explainability.md
Normal file
17
docs/implplan/SPRINT_201_reachability_explainability.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Sprint 201 - Reachability Explainability & Replay Evidence
|
||||||
|
|
||||||
|
[Reachability Delivery] 201.A) Runtime facts + static callgraph union
|
||||||
|
Depends on: Sprint 140 Runtime Signals, Sprint 185 Replay Core, Sprint 186 Scanner Record Mode, Sprint 187 Evidence & CLI Replay
|
||||||
|
Summary: Close the explainability gaps by wiring Zastava runtime sampling, Scanner language lifters, Signals scoring, Replay manifests, docs, and test harnesses around the reachbench fixture packs.
|
||||||
|
|
||||||
|
Task ID | State | Task description | Owners (Source)
|
||||||
|
--- | --- | --- | ---
|
||||||
|
ZASTAVA-REACH-201-001 | TODO | Implement runtime symbol sampling in `StellaOps.Zastava.Observer` (EntryTrace-aware shell AST + build-id capture) and stream ND-JSON batches to Signals `/runtime-facts`, including CAS pointers for traces. Update runbook + config references. | Zastava Observer Guild (`src/Zastava/StellaOps.Zastava.Observer/TASKS.md`)
|
||||||
|
SCAN-REACH-201-002 | DOING (2025-11-08) | Ship language-aware static lifters (JVM, .NET/Roslyn+IL, Go SSA, Node/Deno TS AST, Rust MIR, Swift SIL, shell/binary analyzers) in Scanner Worker; emit canonical SymbolIDs, CAS-stored graphs, and attach reachability tags to SBOM components. | Scanner Worker Guild (`src/Scanner/StellaOps.Scanner.Worker/TASKS.md`)
|
||||||
|
SIGNALS-REACH-201-003 | DOING (2025-11-08) | Extend Signals ingestion to accept the new multi-language graphs + runtime facts, normalize into `reachability_graphs` CAS layout, and expose retrieval APIs for Policy/CLI. | Signals Guild (`src/Signals/StellaOps.Signals/TASKS.md`)
|
||||||
|
SIGNALS-REACH-201-004 | DOING (2025-11-08) | Build the reachability scoring engine (state/score/confidence), wire Redis caches + `signals.fact.updated` events, and integrate reachability weights defined in `docs/11_DATA_SCHEMAS.md`. | Signals Guild · Policy Guild (`src/Signals/StellaOps.Signals/TASKS.md`, `src/Policy/StellaOps.Policy.Engine/TASKS.md`)
|
||||||
|
REPLAY-REACH-201-005 | DOING (2025-11-08) | Update `StellaOps.Replay.Core` manifest schema + bundle writer so replay packs capture reachability graphs, runtime traces, analyzer versions, and evidence hashes; document new CAS namespace. | BE-Base Platform Guild (`src/__Libraries/StellaOps.Replay.Core/TASKS.md`)
|
||||||
|
DOCS-REACH-201-006 | TODO | Author the reachability doc set (`docs/signals/reachability.md`, `callgraph-formats.md`, `runtime-facts.md`, CLI/UI appendices) plus update Zastava + Replay guides with the new evidence and operators’ workflow. | Docs Guild (`docs/TASKS.md`)
|
||||||
|
QA-REACH-201-007 | TODO | Integrate `reachbench-2025-expanded` fixture pack under `tests/reachability/`, add evaluator harness tests that validate reachable vs unreachable cases, and wire CI guidance for deterministic runs. | QA Guild (`tests/README.md`)
|
||||||
|
|
||||||
|
> 2025-11-07: reachbench starter + expanded packs staged under repo root; consuming guilds must relocate fixtures into `tests/reachability/fixtures/` as part of QA-REACH-201-007 before enabling CI.
|
||||||
@@ -75,9 +75,10 @@ At startup, services **self‑advertise** their semver & channel; the UI surface
|
|||||||
### 2.4 Gates & tests
|
### 2.4 Gates & tests
|
||||||
|
|
||||||
* **Static**: linters, codegen checks, protobuf API freeze (backward‑compat tests).
|
* **Static**: linters, codegen checks, protobuf API freeze (backward‑compat tests).
|
||||||
* **Unit/integration**: per‑component, plus **end‑to‑end** flows (scan→vex→policy→sign→attest).
|
* **Unit/integration**: per-component, plus **end-to-end** flows (scan→vex→policy→sign→attest).
|
||||||
* **Perf SLOs**: hot paths (SBOM compose, diff, export) measured against budgets.
|
* **Perf SLOs**: hot paths (SBOM compose, diff, export) measured against budgets.
|
||||||
* **Security**: dependency audit vs Concelier export; container hardening tests; minimal caps.
|
* **Security**: dependency audit vs Concelier export; container hardening tests; minimal caps.
|
||||||
|
* **Deployment assets**: `Build Test Deploy` workflow’s `profile-validation` job installs Helm and runs `helm lint` + `helm template` against `deploy/helm/stellaops` for every `values*.yaml`, catching ConfigMap/templating drift before merges.
|
||||||
* **Analyzer smoke**: restart-time language plug-ins (currently Python) verified via `dotnet run --project src/Tools/LanguageAnalyzerSmoke` to ensure manifest integrity plus cold vs warm determinism (< 30 s / < 5 s budgets); the harness logs deviations from repository goldens for follow-up.
|
* **Analyzer smoke**: restart-time language plug-ins (currently Python) verified via `dotnet run --project src/Tools/LanguageAnalyzerSmoke` to ensure manifest integrity plus cold vs warm determinism (< 30 s / < 5 s budgets); the harness logs deviations from repository goldens for follow-up.
|
||||||
* **Canary cohort**: internal staging + selected customers; one week on **edge** before **stable** tag.
|
* **Canary cohort**: internal staging + selected customers; one week on **edge** before **stable** tag.
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ Excititor enforces the same ingestion covenant as Concelier, tailored to VEX pay
|
|||||||
1. **Immutable `vex_raw` documents.** Upstream OpenVEX/CSAF/CycloneDX files are stored verbatim (`content.raw`) with provenance (`issuer`, `statement_id`, timestamps, signatures). Revisions append new versions linked by `supersedes`.
|
1. **Immutable `vex_raw` documents.** Upstream OpenVEX/CSAF/CycloneDX files are stored verbatim (`content.raw`) with provenance (`issuer`, `statement_id`, timestamps, signatures). Revisions append new versions linked by `supersedes`.
|
||||||
2. **No derived consensus at ingest time.** Fields such as `effective_status`, `merged_state`, `severity`, or reachability are forbidden. Roslyn analyzers and runtime guards block violations before writes.
|
2. **No derived consensus at ingest time.** Fields such as `effective_status`, `merged_state`, `severity`, or reachability are forbidden. Roslyn analyzers and runtime guards block violations before writes.
|
||||||
3. **Linkset-only joins.** Product aliases, CVE keys, SBOM hints, and references live under `linkset`; ingestion must never mutate the underlying statement.
|
3. **Linkset-only joins.** Product aliases, CVE keys, SBOM hints, and references live under `linkset`; ingestion must never mutate the underlying statement.
|
||||||
|
|
||||||
|
**Raw VEX endpoints (WebService)**
|
||||||
|
|
||||||
|
- `POST /ingest/vex` (`scope: vex.admin`) accepts deterministic `VexIngestRequest` payloads. Clients must send `X-Stella-Tenant`. Optional dependencies (e.g., orchestrators, loggers) are wired through `[FromServices] SomeType? service = null` parameters so tests do not need bespoke service registrations.
|
||||||
|
- `GET /vex/raw`, `GET /vex/raw/{digest}`, and `GET /vex/raw/{digest}/provenance` (`scope: vex.read`) expose raw documents, cursored listings, and metadata-only projections.
|
||||||
|
- `POST /aoc/verify` replays stored documents through the Aggregation-Only Contract for audits and Grafana alert sources.
|
||||||
|
- To satisfy the AOC rule forbidding derived data, serialized raw responses omit the `statements` array unless replay tooling explicitly materializes it.
|
||||||
|
- Optional/minor DI dependencies must be declared as `[FromServices] IFoo? foo = null` parameters so host startup (and tests) remain stable when the service is not registered.
|
||||||
|
|
||||||
4. **Deterministic canonicalisation.** Writers sort JSON keys/arrays, normalize timestamps (UTC ISO‑8601), and hash content for reproducible exports.
|
4. **Deterministic canonicalisation.** Writers sort JSON keys/arrays, normalize timestamps (UTC ISO‑8601), and hash content for reproducible exports.
|
||||||
5. **AOC verifier.** `StellaOps.AOC.Verifier` runs in CI and production, checking schema compliance, provenance completeness, sorted collections, and signature metadata.
|
5. **AOC verifier.** `StellaOps.AOC.Verifier` runs in CI and production, checking schema compliance, provenance completeness, sorted collections, and signature metadata.
|
||||||
|
|
||||||
@@ -690,6 +699,10 @@ Excititor.Worker ships with a background refresh service that re-evaluates stale
|
|||||||
* `vex.exports.bytes_total{format}` / `vex.exports.latency_seconds{format}`
|
* `vex.exports.bytes_total{format}` / `vex.exports.latency_seconds{format}`
|
||||||
* **Tracing:** spans for fetch, verify, parse, map, observe, linkset, consensus, export.
|
* **Tracing:** spans for fetch, verify, parse, map, observe, linkset, consensus, export.
|
||||||
* **Dashboards:** provider staleness, linkset conflict hot spots, signature posture, export cache hit-rate.
|
* **Dashboards:** provider staleness, linkset conflict hot spots, signature posture, export cache hit-rate.
|
||||||
|
* **Telemetry configuration:** `Excititor:Telemetry` toggles OpenTelemetry for the host (`Enabled`, `EnableTracing`, `EnableMetrics`, `ServiceName`, `OtlpEndpoint`, optional `OtlpHeaders` and `ResourceAttributes`). Point it at the collector profile listed in `docs/observability/observability.md` so Excititor’s `ingestion_*` metrics land in the same Grafana dashboards as Concelier.
|
||||||
|
* **Health endpoint:** `/obs/excititor/health` (scope `vex.admin`) surfaces ingest/link/signature/conflict SLOs for Console + Grafana. Thresholds are configurable via `Excititor:Observability:*` (see `docs/observability/observability.md`).
|
||||||
|
* **Local replica set:** `tools/mongodb/local-mongo.sh start` downloads the vetted MongoDB binaries (6.0.x), boots a `rs0` single-node replica set, and prints the `EXCITITOR_TEST_MONGO_URI` export line so storage/integration tests can bypass Mongo2Go. `restart` restarts in-place, `clean` wipes the managed data/logs for deterministic runs, and `stop/status/logs` cover teardown/inspection.
|
||||||
|
* **API headers:** responses echo `X-Stella-TraceId` and `X-Stella-CorrelationId` to keep Console/Loki links deterministic; inbound correlation headers are preserved when present.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -700,6 +713,7 @@ Excititor.Worker ships with a background refresh service that re-evaluates stale
|
|||||||
* **Normalization edge cases:** platform-scoped statements, free-text justifications, non-purl products.
|
* **Normalization edge cases:** platform-scoped statements, free-text justifications, non-purl products.
|
||||||
* **Linksets:** conflict scenarios across tiers; verify confidence scoring + conflict payload stability.
|
* **Linksets:** conflict scenarios across tiers; verify confidence scoring + conflict payload stability.
|
||||||
* **Consensus (optional):** ensure tie-breakers honour policy weights/justification gates.
|
* **Consensus (optional):** ensure tie-breakers honour policy weights/justification gates.
|
||||||
|
* **Batch ingest validation:** `dotnet test src/Excititor/__Tests/StellaOps.Excititor.WebService.Tests/StellaOps.Excititor.WebService.Tests.csproj --filter "Category=BatchIngestValidation"` ingests mixed CycloneDX/CSAF/OpenVEX fixtures, asserts `/vex/raw` parity, confirms `ingestion_write_total` tags, and checks `/aoc/verify` output—run after touching ingest/telemetry code.
|
||||||
* **Performance:** 1M-row observation/linkset export timing; memory ceilings; stream correctness.
|
* **Performance:** 1M-row observation/linkset export timing; memory ceilings; stream correctness.
|
||||||
* **Determinism:** same inputs + policy → identical linkset hashes, conflict payloads, optional `consensusDigest`, and export bytes.
|
* **Determinism:** same inputs + policy → identical linkset hashes, conflict payloads, optional `consensusDigest`, and export bytes.
|
||||||
* **API contract tests:** pagination, filters, RBAC, rate limits.
|
* **API contract tests:** pagination, filters, RBAC, rate limits.
|
||||||
@@ -748,4 +762,3 @@ All exports and consensus entries are serialized via `VexCanonicalJsonSerializer
|
|||||||
* arrays sorted by `(providerId, vulnId, productKey, lastObserved)` unless semantic order mandated;
|
* arrays sorted by `(providerId, vulnId, productKey, lastObserved)` unless semantic order mandated;
|
||||||
* timestamps in `YYYY‑MM‑DDThh:mm:ssZ`;
|
* timestamps in `YYYY‑MM‑DDThh:mm:ssZ`;
|
||||||
* no insignificant whitespace.
|
* no insignificant whitespace.
|
||||||
|
|
||||||
|
|||||||
30
docs/modules/findings-ledger/workflow-inference.md
Normal file
30
docs/modules/findings-ledger/workflow-inference.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Findings Ledger — Workflow Inference Notes
|
||||||
|
|
||||||
|
> **Audience:** Findings Ledger Guild, Vuln Explorer API, Console Guild
|
||||||
|
> **Scope:** How workflow mutations (assign/comment/accept-risk/etc.) derive canonical ledger inputs when the caller omits low-level fields.
|
||||||
|
|
||||||
|
## 1. Chain + sequencing
|
||||||
|
- **Chain derivation.** When a request does not provide `chainId`, we deterministically derive it from `tenantId :: policyVersion` (`Guid` from SHA-256 first 16 bytes). This keeps per-policy ordering stable and avoids callers leaking raw GUID logic.
|
||||||
|
- **Sequence number.** We fetch the current chain head and expect `head.sequence + 1`. If the chain has no entries, the expected sequence is `1`. The append path still enforces optimistic concurrency and rejects mismatches.
|
||||||
|
- **Previous hash.** We reuse the head’s `event_hash` (defaulting to the all-zero hash for genesis events) so writers don’t need to manage hash pointers.
|
||||||
|
|
||||||
|
## 2. Event identifiers + timestamps
|
||||||
|
- **Event IDs.** If the caller omits `eventId`, we mint a V7 GUID to keep chronological ordering while remaining globally unique.
|
||||||
|
- **Occurred / recorded timestamps.** `occurredAt` defaults to “now” in UTC (based on `TimeProvider`). `recordedAt` always comes from the service’s `TimeProvider` to avoid caller-provided drift.
|
||||||
|
|
||||||
|
## 3. Status & severity fallbacks
|
||||||
|
- The reducer already maps event types to canonical statuses. Workflow service only writes `status` when the mutation explicitly changes it (e.g., `accept-risk` → `accepted_risk`, `target-fix` → `in_progress`, `verify-fix` → `verified`, `reopen` → `affected`). Otherwise we leave status null and let the reducer infer it.
|
||||||
|
- Severity is never inferred inside workflow handlers—they rely on policy evaluation or reducer logic.
|
||||||
|
|
||||||
|
## 4. Attachments metadata
|
||||||
|
- Attachments now include security context: every entry captures an AES-256-GCM envelope (`algorithm`, `ciphertext`, `nonce`, `tag`, `expiresAt`) derived from `attachments.encryptionKey`.
|
||||||
|
- Signed URLs are generated with HMAC-SHA256 using `attachments.signedUrlSecret` and inherit `attachments.signedUrlLifetime` (default 15 minutes). URLs plus envelopes share the same expiry window.
|
||||||
|
- Metadata is normalized (trimmed keys, deterministic ordering) before encryption so ledger hashes remain stable; duplicate IDs are deduplicated.
|
||||||
|
- Actual binary blobs stay in Evidence Locker/S3; the envelope is what downstream services use to decrypt the blob once downloaded.
|
||||||
|
|
||||||
|
## 5. Validation surface
|
||||||
|
- All handlers enforce tenant/policy/finding/artifact/vuln IDs, actor identity, and supported actor types.
|
||||||
|
- Mutation-specific requirements (e.g., assignment requires assignee, accept-risk needs justification) are validated before any ledger append occurs.
|
||||||
|
- Attachments are validated for ID/file name/MIME type/positive size and 64-char SHA-256 digests before encryption, preventing malformed payloads from burning hashes or emitting invalid URLs.
|
||||||
|
|
||||||
|
These rules let upstream APIs/clients send high-level workflow intents without micromanaging ledger sequencing or hashing, while preserving deterministic ledger entries. LEDGER-29-005 implements the service described here; LEDGER-29-006 builds on it for secure attachment handling.
|
||||||
@@ -150,6 +150,7 @@ sequenceDiagram
|
|||||||
2. `inputbundle.tar.zst` (feeds, policies, tools, environment snapshot).
|
2. `inputbundle.tar.zst` (feeds, policies, tools, environment snapshot).
|
||||||
3. `outputbundle.tar.zst` (SBOM, findings, VEX, logs, Merkle proofs).
|
3. `outputbundle.tar.zst` (SBOM, findings, VEX, logs, Merkle proofs).
|
||||||
Every artifact is signed with multi-profile keys (FIPS, GOST, SM, etc.) managed by Authority. See `docs/replay/DETERMINISTIC_REPLAY.md` §2–§5 for the full schema.
|
Every artifact is signed with multi-profile keys (FIPS, GOST, SM, etc.) managed by Authority. See `docs/replay/DETERMINISTIC_REPLAY.md` §2–§5 for the full schema.
|
||||||
|
- **Reachability subtree:** When reachability recording is enabled, Scanner uploads graphs & runtime traces under `cas://replay/<scan-id>/reachability/graphs/` and `cas://replay/<scan-id>/reachability/traces/`. Manifest references (StellaOps.Replay.Core) bind these URIs along with analyzer hashes so Replay + Signals can rehydrate explainability evidence deterministically.
|
||||||
- **Storage tiers:** Primary storage is Mongo (`replay_runs`, `replay_subjects`) plus the CAS bucket. Evidence Locker mirrors bundles for long-term retention and legal hold workflows (`docs/modules/evidence-locker/architecture.md`). Offline kits package bundles under `offline/replay/<scan-id>` with detached DSSE envelopes for air-gapped verification.
|
- **Storage tiers:** Primary storage is Mongo (`replay_runs`, `replay_subjects`) plus the CAS bucket. Evidence Locker mirrors bundles for long-term retention and legal hold workflows (`docs/modules/evidence-locker/architecture.md`). Offline kits package bundles under `offline/replay/<scan-id>` with detached DSSE envelopes for air-gapped verification.
|
||||||
- **APIs & ownership:** Scanner WebService produces the bundles via `record` mode, Scanner Worker emits Merkle metadata, Signer/Authority provide DSSE signatures, Attestor anchors manifests to Rekor, CLI/Evidence Locker handle retrieval, and Docs Guild maintains runbooks. Responsibilities are tracked in `docs/implplan/SPRINT_185_replay_core.md` through `SPRINT_187_evidence_cli_replay.md`.
|
- **APIs & ownership:** Scanner WebService produces the bundles via `record` mode, Scanner Worker emits Merkle metadata, Signer/Authority provide DSSE signatures, Attestor anchors manifests to Rekor, CLI/Evidence Locker handle retrieval, and Docs Guild maintains runbooks. Responsibilities are tracked in `docs/implplan/SPRINT_185_replay_core.md` through `SPRINT_187_evidence_cli_replay.md`.
|
||||||
- **Operational policies:** Retention defaults to 180 days for hot CAS storage and 2 years for cold Evidence Locker copies. Rotation and pruning follow the checklist in `docs/runbooks/replay_ops.md`.
|
- **Operational policies:** Retention defaults to 180 days for hot CAS storage and 2 years for cold Evidence Locker copies. Rotation and pruning follow the checklist in `docs/runbooks/replay_ops.md`.
|
||||||
|
|||||||
@@ -25,6 +25,51 @@ This guide captures the canonical signals emitted by Concelier and Excititor onc
|
|||||||
- **Stale ingestion:** Alert when `max_over_time(ingestion_latency_seconds_sum / ingestion_latency_seconds_count)[30m]` exceeds 30 s or if `ingestion_write_total` has no growth for > 60 min.
|
- **Stale ingestion:** Alert when `max_over_time(ingestion_latency_seconds_sum / ingestion_latency_seconds_count)[30m]` exceeds 30 s or if `ingestion_write_total` has no growth for > 60 min.
|
||||||
- **Signature drop:** Warn when `rate(ingestion_signature_verified_total{result="fail"}[1h]) > 0`.
|
- **Signature drop:** Warn when `rate(ingestion_signature_verified_total{result="fail"}[1h]) > 0`.
|
||||||
|
|
||||||
|
### 1.2 · `/obs/excititor/health`
|
||||||
|
|
||||||
|
`GET /obs/excititor/health` (scope `vex.admin`) returns a compact snapshot for Grafana tiles and Console widgets:
|
||||||
|
|
||||||
|
- `ingest` — overall status, worst lag (seconds), and the top connectors (status, lagSeconds, failure count, last success).
|
||||||
|
- `link` — freshness of consensus/linkset processing plus document counts and the number currently carrying conflicts.
|
||||||
|
- `signature` — recent coverage window (evaluated, with signatures, verified, failures, unsigned, coverage ratio).
|
||||||
|
- `conflicts` — rolling totals grouped by status plus per-bucket trend data for charts.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"generatedAt": "2025-11-08T11:00:00Z",
|
||||||
|
"ingest": { "status": "healthy", "connectors": [ { "connectorId": "excititor:redhat", "lagSeconds": 45.3 } ] },
|
||||||
|
"link": { "status": "warning", "lastConsensusAt": "2025-11-08T10:57:03Z" },
|
||||||
|
"signature": { "status": "critical", "documentsEvaluated": 120, "verified": 30, "failures": 2 },
|
||||||
|
"conflicts": { "status": "warning", "conflictStatements": 325, "trend": [ { "bucketStart": "2025-11-08T10:00:00Z", "conflicts": 130 } ] }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Setting | Default | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| `Excititor:Observability:IngestWarningThreshold` | `06:00:00` | Connector lag before `ingest.status` becomes `warning`. |
|
||||||
|
| `Excititor:Observability:IngestCriticalThreshold` | `24:00:00` | Connector lag before `ingest.status` becomes `critical`. |
|
||||||
|
| `Excititor:Observability:LinkWarningThreshold` | `00:15:00` | Maximum acceptable delay between consensus recalculations. |
|
||||||
|
| `Excititor:Observability:LinkCriticalThreshold` | `01:00:00` | Delay that marks link status as `critical`. |
|
||||||
|
| `Excititor:Observability:SignatureWindow` | `12:00:00` | Lookback window for signature coverage. |
|
||||||
|
| `Excititor:Observability:SignatureHealthyCoverage` | `0.8` | Coverage ratio that still counts as healthy. |
|
||||||
|
| `Excititor:Observability:SignatureWarningCoverage` | `0.5` | Coverage ratio that flips the status to `warning`. |
|
||||||
|
| `Excititor:Observability:ConflictTrendWindow` | `24:00:00` | Rolling window used for conflict aggregation. |
|
||||||
|
| `Excititor:Observability:ConflictTrendBucketMinutes` | `60` | Resolution of conflict `trend` buckets. |
|
||||||
|
| `Excititor:Observability:ConflictWarningRatio` | `0.15` | Fraction of consensus docs with conflicts that triggers `warning`. |
|
||||||
|
| `Excititor:Observability:ConflictCriticalRatio` | `0.3` | Ratio that marks `conflicts.status` as `critical`. |
|
||||||
|
| `Excititor:Observability:MaxConnectorDetails` | `50` | Number of connector entries returned (keeps payloads small). |
|
||||||
|
|
||||||
|
### 1.3 · Regression & DI hygiene
|
||||||
|
|
||||||
|
1. **Keep storage/integration tests green when telemetry touches persistence.**
|
||||||
|
- `./tools/mongodb/local-mongo.sh start` downloads MongoDB 6.0.16 (if needed), launches `rs0`, and prints `export EXCITITOR_TEST_MONGO_URI=mongodb://.../excititor-tests`. Copy that export into your shell.
|
||||||
|
- `./tools/mongodb/local-mongo.sh restart` is a shortcut for “stop if running, then start” using the same dataset—use it after tweaking config or when tests need a bounce without wiping fixtures.
|
||||||
|
- `./tools/mongodb/local-mongo.sh clean` stops the instance (if running) and deletes the managed data/log directories so storage tests begin from a pristine catalog.
|
||||||
|
- Run `dotnet test src/Excititor/__Tests/StellaOps.Excititor.Storage.Mongo.Tests/StellaOps.Excititor.Storage.Mongo.Tests.csproj -nologo -v minimal` (add `--filter` if you only touched specific suites). These tests exercise the same write paths that feed the dashboards, so regressions show up immediately.
|
||||||
|
- `./tools/mongodb/local-mongo.sh stop` when finished so CI/dev hosts stay clean; `status|logs|shell` are available for troubleshooting.
|
||||||
|
2. **Declare optional Minimal API dependencies with `[FromServices] ... = null`.** RequestDelegateFactory treats `[FromServices] IVexSigner? signer = null` (or similar) as optional, so host startup succeeds even when tests have not registered that service. This pattern keeps observability endpoints cancellable while avoiding brittle test overrides.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2 · Traces
|
## 2 · Traces
|
||||||
@@ -45,6 +90,35 @@ This guide captures the canonical signals emitted by Concelier and Excititor onc
|
|||||||
- Use `aoc.guard` spans to inspect guard payload snapshots. Sensitive fields are redacted automatically; raw JSON lives in secure logs only.
|
- Use `aoc.guard` spans to inspect guard payload snapshots. Sensitive fields are redacted automatically; raw JSON lives in secure logs only.
|
||||||
- For scheduled verification, filter traces by `initiator="scheduled"` to compare runtimes pre/post change.
|
- For scheduled verification, filter traces by `initiator="scheduled"` to compare runtimes pre/post change.
|
||||||
|
|
||||||
|
### 2.3 Telemetry configuration (Excititor)
|
||||||
|
|
||||||
|
- Configure the web service via `Excititor:Telemetry`:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"Excititor": {
|
||||||
|
"Telemetry": {
|
||||||
|
"Enabled": true,
|
||||||
|
"EnableTracing": true,
|
||||||
|
"EnableMetrics": true,
|
||||||
|
"ServiceName": "stellaops-excititor-web",
|
||||||
|
"OtlpEndpoint": "http://otel-collector:4317",
|
||||||
|
"OtlpHeaders": {
|
||||||
|
"Authorization": "Bearer ${OTEL_PUSH_TOKEN}"
|
||||||
|
},
|
||||||
|
"ResourceAttributes": {
|
||||||
|
"env": "prod-us",
|
||||||
|
"service.group": "ingestion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Point the OTLP endpoint at the shared collector profile from §1 so Excititor metrics land in the `ingestion_*` dashboards next to Concelier. Resource attributes drive Grafana filtering (e.g., `env`, `service.group`).
|
||||||
|
- For offline/air-gap bundles set `Enabled=false` and collect the file exporter artifacts from the Offline Kit; import them into Grafana after transfer to keep time-to-truth dashboards consistent.
|
||||||
|
- Local development templates: run `tools/mongodb/local-mongo.sh start` to spin up a single-node replica set plus the matching `mongosh` client. The script prints the `export EXCITITOR_TEST_MONGO_URI=...` command that integration tests (e.g., `StellaOps.Excititor.Storage.Mongo.Tests`) will honor. Use `restart` for a quick bounce, `clean` to wipe data between suites, and `stop` when finished.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3 · Logs
|
## 3 · Logs
|
||||||
@@ -61,6 +135,13 @@ Structured logs include the following keys (JSON):
|
|||||||
| `violation.code` | Present when guard rejects `ERR_AOC_00x`. |
|
| `violation.code` | Present when guard rejects `ERR_AOC_00x`. |
|
||||||
| `verification.window` | Present on `/aoc/verify` job logs. |
|
| `verification.window` | Present on `/aoc/verify` job logs. |
|
||||||
|
|
||||||
|
Excititor APIs mirror these identifiers via response headers:
|
||||||
|
|
||||||
|
| Header | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `X-Stella-TraceId` | W3C trace/span identifier for deep-linking from Console → Grafana/Loki. |
|
||||||
|
| `X-Stella-CorrelationId` | Stable correlation identifier (respects inbound header or falls back to the request trace ID). |
|
||||||
|
|
||||||
Logs are shipped to the central Loki/Elasticsearch cluster. Use the template query:
|
Logs are shipped to the central Loki/Elasticsearch cluster. Use the template query:
|
||||||
|
|
||||||
```logql
|
```logql
|
||||||
|
|||||||
@@ -25,6 +25,20 @@
|
|||||||
- Successful responses return camel-case DTOs matching `PolicyPackDto`, `PolicyRevisionDto`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`).
|
- 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.
|
- Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"Upstream authorization missing"` detail.
|
||||||
|
|
||||||
|
### Dual-control activation
|
||||||
|
|
||||||
|
- **Config-driven.** Set `PolicyEngine.activation.forceTwoPersonApproval=true` when every activation must collect two distinct `policy:activate` approvals. When false, operators can opt into dual-control per revision (`requiresTwoPersonApproval: true`).
|
||||||
|
- **Defaults.** `PolicyEngine.activation.defaultRequiresTwoPersonApproval` feeds the default when callers omit the checkbox/flag.
|
||||||
|
- **Statuses.** First approval on a dual-control revision returns `202 pending_second_approval`; duplicate actors get `400 duplicate_approval`; the second distinct approver receives the usual `200 activated`.
|
||||||
|
- **Audit trail.** With `PolicyEngine.activation.emitAuditLogs` on, Policy Engine emits structured `policy.activation.*` scopes (pack id, revision, tenant, approver IDs, comments) so the gateway metrics/ELK dashboards can show who approved what.
|
||||||
|
|
||||||
|
#### Activation configuration wiring
|
||||||
|
|
||||||
|
- **Helm ConfigMap.** `deploy/helm/stellaops/values*.yaml` now include a `policy-engine-activation` ConfigMap. The chart automatically injects it via `envFrom` into both the Policy Engine and Policy Gateway pods, so overriding the ConfigMap data updates the services with no manifest edits.
|
||||||
|
- **Type safety.** Quote ConfigMap values (e.g., `"true"`, `"false"`) because Kubernetes ConfigMaps carry string data. This mirrors the defaults checked into the repo and keeps `helm template` deterministic.
|
||||||
|
- **File-based overrides (optional).** The Policy Engine host already probes `/config/policy-engine/activation.yaml`, `../etc/policy-engine.activation.yaml`, and ambient `policy-engine.activation.yaml` files beside the binary. Mounting the ConfigMap as a file at `/config/policy-engine/activation.yaml` works immediately if/when we add a volume.
|
||||||
|
- **Offline/Compose.** Compose/offline bundles can continue exporting `STELLAOPS_POLICY_ENGINE__ACTIVATION__*` variables directly; the ConfigMap wiring simply mirrors those keys for Kubernetes clusters.
|
||||||
|
|
||||||
## 3 · Authentication & headers
|
## 3 · Authentication & headers
|
||||||
|
|
||||||
| Header | Source | Notes |
|
| Header | Source | Notes |
|
||||||
|
|||||||
@@ -104,6 +104,30 @@ C --> J[Blob Store: Input/Output Bundles]
|
|||||||
"vexHash": "sha256:...",
|
"vexHash": "sha256:...",
|
||||||
"logHash": "sha256:..."
|
"logHash": "sha256:..."
|
||||||
},
|
},
|
||||||
|
"reachability": {
|
||||||
|
"graphs": [
|
||||||
|
{
|
||||||
|
"kind": "static",
|
||||||
|
"analyzer": "scanner/java@sha256:...",
|
||||||
|
"casUri": "cas://replay/scan-123/reachability/static-graph.tar.zst",
|
||||||
|
"sha256": "abc123"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "framework",
|
||||||
|
"analyzer": "scanner/framework@sha256:...",
|
||||||
|
"casUri": "cas://replay/scan-123/reachability/framework-graph.tar.zst",
|
||||||
|
"sha256": "def456"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"runtimeTraces": [
|
||||||
|
{
|
||||||
|
"source": "zastava",
|
||||||
|
"casUri": "cas://replay/scan-123/reachability/runtime-trace.ndjson.zst",
|
||||||
|
"sha256": "feedface",
|
||||||
|
"recordedAt": "2025-11-07T11:10:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"signer": "scanner.authority",
|
"signer": "scanner.authority",
|
||||||
"dsseEnvelopeHash": "sha256:...",
|
"dsseEnvelopeHash": "sha256:...",
|
||||||
@@ -112,6 +136,17 @@ C --> J[Blob Store: Input/Output Bundles]
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3.2 Reachability Section
|
||||||
|
|
||||||
|
The optional `reachability` block captures the inputs needed to replay explainability decisions:
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `reachability.graphs[]` | References to static/framework callgraph bundles. Each entry records the producing analyzer (`analyzer`/`version`), the CAS URI under `cas://replay/<scan-id>/reachability/graphs/`, and the SHA-256 digest of the tarball. |
|
||||||
|
| `reachability.runtimeTraces[]` | References to runtime observation bundles (e.g., Zastava ND-JSON traces). Each item stores the emitting source, CAS URI (typically `cas://replay/<scan-id>/reachability/traces/`), SHA-256, and capture timestamp. |
|
||||||
|
|
||||||
|
Replay engines MUST verify every referenced artifact hash before re-evaluating reachability. Missing graphs downgrade affected signals to `reachability:unknown` and should raise policy warnings.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Deterministic Execution Rules
|
## 4. Deterministic Execution Rules
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Replay is the foundation for:
|
|||||||
| **Feeds/VEX sources** | Full or pruned snapshot with Merkle proofs | ✅ |
|
| **Feeds/VEX sources** | Full or pruned snapshot with Merkle proofs | ✅ |
|
||||||
| **Policy Bundle** | Lattice rules, mutes, trust profiles, thresholds | ✅ |
|
| **Policy Bundle** | Lattice rules, mutes, trust profiles, thresholds | ✅ |
|
||||||
| **Environment** | OS, arch, locale, TZ, deterministic seed, runtime flags | ✅ |
|
| **Environment** | OS, arch, locale, TZ, deterministic seed, runtime flags | ✅ |
|
||||||
|
| **Reachability Evidence** | Callgraphs (`graphs[]`), runtime traces (`runtimeTraces[]`), analyzer/version hashes | ✅ |
|
||||||
| **Crypto Profile** | Algorithm suites (FIPS, GOST, SM, eIDAS) | ✅ |
|
| **Crypto Profile** | Algorithm suites (FIPS, GOST, SM, eIDAS) | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -71,6 +72,7 @@ stella replay manifest.json --what-if --vary=feeds
|
|||||||
|
|
||||||
1. `stella scan image:tag --record out/`
|
1. `stella scan image:tag --record out/`
|
||||||
- Generates Replay Manifest, InputBundle, OutputBundle, DSSE sigs.
|
- Generates Replay Manifest, InputBundle, OutputBundle, DSSE sigs.
|
||||||
|
- Captures reachability graphs/traces (if enabled) and references them via `reachability.graphs[]` + `runtimeTraces[]`.
|
||||||
2. `stella verify manifest.json`
|
2. `stella verify manifest.json`
|
||||||
- Validates hashes, signatures, and completeness.
|
- Validates hashes, signatures, and completeness.
|
||||||
3. `stella replay manifest.json --strict`
|
3. `stella replay manifest.json --strict`
|
||||||
@@ -88,6 +90,7 @@ stella replay manifest.json --what-if --vary=feeds
|
|||||||
- `replay_runs`: manifest + DSSE envelopes + status
|
- `replay_runs`: manifest + DSSE envelopes + status
|
||||||
- `bundles`: content-addressed (input/output/rootpack)
|
- `bundles`: content-addressed (input/output/rootpack)
|
||||||
- `subjects`: OCI digests, Merkle roots per layer
|
- `subjects`: OCI digests, Merkle roots per layer
|
||||||
|
- `reachability_facts`: graph & runtime trace references tied to scan subjects
|
||||||
- **File store**
|
- **File store**
|
||||||
- Bundles stored as `<sha256>.tar.zst`
|
- Bundles stored as `<sha256>.tar.zst`
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ This playbook enumerates the deterministic replay validation suite. It guides th
|
|||||||
| T-RETENTION-006 | **Retention Sweep** | Ensure Evidence Locker prunes hot CAS after SLA while preserving cold storage copies. | Evidence Locker, Ops | Replay retention config, audit logs |
|
| T-RETENTION-006 | **Retention Sweep** | Ensure Evidence Locker prunes hot CAS after SLA while preserving cold storage copies. | Evidence Locker, Ops | Replay retention config, audit logs |
|
||||||
| T-OFFLINE-007 | **Offline Kit Replay** | Execute `stella replay` using only Offline Kit artifacts. | CLI, Evidence Locker | Offline kit bundle, local RootPack |
|
| T-OFFLINE-007 | **Offline Kit Replay** | Execute `stella replay` using only Offline Kit artifacts. | CLI, Evidence Locker | Offline kit bundle, local RootPack |
|
||||||
| T-OPA-008 | **Runbook Drill** | Simulate replay-driven incident response per `docs/runbooks/replay_ops.md`. | Ops Guild, Scanner, Authority | Runbook checklist, incident notes |
|
| T-OPA-008 | **Runbook Drill** | Simulate replay-driven incident response per `docs/runbooks/replay_ops.md`. | Ops Guild, Scanner, Authority | Runbook checklist, incident notes |
|
||||||
|
| T-REACH-009 | **Reachability Replay** | Rehydrate reachability graphs/traces from replay bundles and compare against reachbench fixtures. | Scanner, Signals, Replay | `reachbench-2025-expanded`, reachability CAS references |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ This playbook enumerates the deterministic replay validation suite. It guides th
|
|||||||
- [ ] Evidence Locker retention job validated against hot/cold tiers.
|
- [ ] Evidence Locker retention job validated against hot/cold tiers.
|
||||||
- [ ] CLI documentation updated with troubleshooting steps observed during tests.
|
- [ ] CLI documentation updated with troubleshooting steps observed during tests.
|
||||||
- [ ] Runbook drill logged with timestamp and owners in `docs/runbooks/replay_ops.md`.
|
- [ ] Runbook drill logged with timestamp and owners in `docs/runbooks/replay_ops.md`.
|
||||||
|
- [ ] Reachability replay drill captured (`T-REACH-009`) with fixture references and Signals verification logs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
113
docs/security/crypto-routing-audit-2025-11-07.md
Normal file
113
docs/security/crypto-routing-audit-2025-11-07.md
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Crypto Routing Audit — 07 Nov 2025
|
||||||
|
|
||||||
|
**Scope.** Inventory direct uses of `System.Security.Cryptography` (and related primitives) outside the `StellaOps.Cryptography*` stack to identify callers that must be routed through sovereign-aware providers (default, PKCS#11, CryptoPro, future PQC).
|
||||||
|
|
||||||
|
**Method.** `rg -l "using System.Security.Cryptography" src | grep -Ev "__Tests|\.Tests/"` (filtered for runtime code). Counts reflect unique files per top-level module.
|
||||||
|
|
||||||
|
## Summary (runtime files by module)
|
||||||
|
|
||||||
|
| Module/Area | Files bypassing shared crypto |
|
||||||
|
|-------------|------------------------------|
|
||||||
|
| Concelier | 34 |
|
||||||
|
| Scanner | 31 |
|
||||||
|
| Authority | 20 |
|
||||||
|
| Excititor | 18 |
|
||||||
|
| Attestor | 18 |
|
||||||
|
| EvidenceLocker | 10 |
|
||||||
|
| Findings / Vuln Explorer | 7 |
|
||||||
|
| Zastava | 6 |
|
||||||
|
| ExportCenter| 6 |
|
||||||
|
| Policy | 4 |
|
||||||
|
| Scheduler | 3 |
|
||||||
|
| CLI | 3 |
|
||||||
|
| Bench | 3 |
|
||||||
|
| AdvisoryAI | 3 |
|
||||||
|
| Other (Notify, Registry, Signals, etc.) | 11 combined |
|
||||||
|
|
||||||
|
## Configuring `crypto.regionalProfiles`
|
||||||
|
|
||||||
|
All hosts can now express provider ordering and profile overrides via configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Crypto:
|
||||||
|
registry:
|
||||||
|
preferredProviders:
|
||||||
|
- default
|
||||||
|
- ru.pkcs11
|
||||||
|
activeProfile: ru-offline
|
||||||
|
profiles:
|
||||||
|
ru-offline:
|
||||||
|
preferredProviders:
|
||||||
|
- ru.cryptopro.csp
|
||||||
|
- ru.pkcs11
|
||||||
|
pkcs11:
|
||||||
|
keys:
|
||||||
|
- keyId: ru-slot-token
|
||||||
|
libraryPath: /usr/local/lib/librutokenecp.so
|
||||||
|
slotId: "0x1"
|
||||||
|
privateKeyLabel: signing-key
|
||||||
|
certificateThumbprint: "<thumbprint>"
|
||||||
|
cryptopro:
|
||||||
|
keys:
|
||||||
|
- keyId: ru-csp-token
|
||||||
|
libraryPath: /opt/cprocsp/lib/libcapi20.so
|
||||||
|
containerLabel: KRYPTO_PRO_KEY
|
||||||
|
certificateThumbprint: "<thumbprint>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Each deployment picks a profile (`activeProfile`) that resolves to a deterministic provider order, and individual services call into `ICryptoProviderRegistry` rather than new-ing crypto stacks directly.
|
||||||
|
|
||||||
|
## Inspecting providers from the CLI
|
||||||
|
|
||||||
|
`stellaops crypto providers` now lists the registered providers, signing algorithms, certificate metadata, and the current preferred order. Use `--json` for machine-readable output or `--profile <name>` to preview another profile (e.g., `ru-offline`) before flipping configuration.
|
||||||
|
|
||||||
|
## High-priority hotspots
|
||||||
|
|
||||||
|
### Concelier (ingestion + mirror connectors)
|
||||||
|
- `src/Concelier/StellaOps.Concelier.WebService/Services/OpenApiDiscoveryDocumentProvider.cs` – builds SHA256 hashes for discovery docs inline.
|
||||||
|
- `src/Concelier/__Libraries/StellaOps.Concelier.Connector.StellaOpsMirror/Security/MirrorSignatureVerifier.cs` – performs RSA verification directly.
|
||||||
|
- `src/Concelier/__Libraries/StellaOps.Concelier.Connector.Ru.Nkcki/RuNkckiConnector.cs` and `.Ru.Bdu` – local hash/signature handling for regional advisories.
|
||||||
|
|
||||||
|
**Action:** Introduce `ICryptoProviderRegistry` consumption inside connector/lib assemblies (probably through lightweight adapter service). File follow-up tasks in `src/Concelier/StellaOps.Concelier.WebService/TASKS.md` and connector TASK boards to migrate hashing/signing to the new PKCS#11/CryptoPro providers (priority for RU feeds to unblock RootPack_RU).
|
||||||
|
|
||||||
|
### Scanner (web service, worker, Sbomer plug-ins)
|
||||||
|
- `src/Scanner/StellaOps.Scanner.WebService/Utilities/ScanIdGenerator.cs` – direct SHA256 for id derivation.
|
||||||
|
- `src/Scanner/StellaOps.Scanner.WebService/Services/ReportSigner.cs` – uses `ECDsa.Create()` directly for DSSE hand-off.
|
||||||
|
- `src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestPublisher.cs` – manual digesting before CAS writes.
|
||||||
|
|
||||||
|
**Action:** Create shared `IScanCryptoService` backed by `ICryptoProviderRegistry` so both web service and worker reuse sovereign providers. Add tasks under `src/Scanner/StellaOps.Scanner.WebService/TASKS.md` and `src/Scanner/StellaOps.Scanner.Worker/TASKS.md`.
|
||||||
|
|
||||||
|
### Authority (plugins + signing host)
|
||||||
|
- `StellaOps.Authority/Signing/*` classes still load PEM/PKCS#12 directly via `X509Certificate2` and `RSA`.
|
||||||
|
- `AuthoritySecretHasher` and `AuthorityClientCertificateValidator` maintain custom hashing.
|
||||||
|
|
||||||
|
**Action:** Wire Authority signing/loading paths to `ICryptoProviderRegistry` so active keys can point to `ru.cryptopro.csp` or `ru.pkcs11`. Open tasks in `src/Authority/StellaOps.Authority/TASKS.md` covering: signing key loading, JWKS generation, secret hashing migration.
|
||||||
|
|
||||||
|
### Excititor / Attestor
|
||||||
|
- Excititor connectors (e.g., `src/Excititor/__Libraries/StellaOps.Excititor.Connectors.Ghsa/GhsaConnector.cs`) re-hash payloads in place.
|
||||||
|
- Attestor submission cache uses SHA256 for bundle ids.
|
||||||
|
|
||||||
|
**Action:** Introduce shared hashing helper that internally calls `ICryptoProviderRegistry.ResolveOrThrow(CryptoCapability.Signing, SignatureAlgorithms.GostR3410_2012_256)` for digest+sign combos; log follow-ups in respective TASK boards.
|
||||||
|
|
||||||
|
### Evidence Locker / Export Center
|
||||||
|
- Export packaging code manually builds SHA/V1 digests before signing manifests.
|
||||||
|
|
||||||
|
**Action:** Add backlog tasks for both modules to replace `SHA256.Create()` usage with provider-backed hashing (especially for offline bundle sealing).
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
1. **Open remediation tasks per module** referencing this audit (minimum: Concelier, Scanner, Authority, Excititor, Attestor, Evidence Locker, Export Center). Each task should specify which files to migrate and target provider (default vs sovereign).
|
||||||
|
2. **Provide shared helpers** (e.g., `ICryptoDigestService`, `ICasSigner`) in `StellaOps.Cryptography` to ease adoption and avoid each module talking to the registry manually.
|
||||||
|
3. **Follow-up audit** once migrations land; rerun the command and ensure only `StellaOps.Cryptography*` and vetted crypto libraries contain direct `System.Security.Cryptography` usage.
|
||||||
|
4. **RootPack validation runbook** — see `docs/security/rootpack_ru_validation.md` for deterministic tests, hardware validation, and required audit artifacts before shipping RootPack_RU.
|
||||||
|
|
||||||
|
### Remediation tracking snapshot (2025-11-08)
|
||||||
|
|
||||||
|
- **Authority:** `AUTH-CRYPTO-90-001` (Authority TASKS board)
|
||||||
|
- **Scanner:** `SCANNER-CRYPTO-90-001` (WebService TASKS board)
|
||||||
|
- **Concelier:** `CONCELIER-CRYPTO-90-001` (WebService TASKS board)
|
||||||
|
- **Excititor:** `EXCITITOR-CRYPTO-90-001` (WebService TASKS board)
|
||||||
|
- **Attestor:** `ATTESTOR-CRYPTO-90-001` (Attestor TASKS board)
|
||||||
|
- **Evidence Locker:** `EVID-CRYPTO-90-001` (Evidence Locker TASKS board)
|
||||||
|
- **Export Center:** `EXPORT-CRYPTO-90-001` (Exporter Service TASKS board)
|
||||||
|
|
||||||
|
> Stored query artifacts: `/tmp/crypto_runtime_non_tests.txt` (157 runtime files) and aggregated counts above prepared on 2025-11-07.
|
||||||
45
docs/security/dpop-mtls-rollout.md
Normal file
45
docs/security/dpop-mtls-rollout.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Authority DPoP + mTLS Rollout Plan (Sprint 100)
|
||||||
|
|
||||||
|
_Last updated: 2025-11-07_
|
||||||
|
|
||||||
|
## Objectives
|
||||||
|
1. Enforce DPoP sender constraints (`AUTH-DPOP-11-001`).
|
||||||
|
2. Bind high-assurance tenants to mTLS tokens (`AUTH-MTLS-11-002`).
|
||||||
|
3. Provide telemetry + runbooks so plugins (SEC2/SEC3/SEC5) can validate enforcement without regressions.
|
||||||
|
|
||||||
|
## Phase 1 · Config & Telemetry (ETA 2025-11-08)
|
||||||
|
- [ ] Extend `authority.yaml` with `security.senderConstraints.dpop` section (nonce store, allowed algorithms, replay window).
|
||||||
|
- [ ] Wire structured logs (`authority.dpop.request`) containing tenant, client, cnf thumbprint, nonce status.
|
||||||
|
- [ ] Add `DPoPNonceStore` abstraction + Redis implementation for multi-node deployments.
|
||||||
|
- [ ] Update integration tests: `AuthorityTokenTests.DPoPNonceRequired`, `AuthorityTokenTests.DPoPMustMatchCnF`.
|
||||||
|
|
||||||
|
## Phase 2 · Enforcement & Fallback (ETA 2025-11-10)
|
||||||
|
- [ ] Reject `/token` requests lacking DPoP proof when tenant policy requires it.
|
||||||
|
- [ ] Persist `cnf.jkt` and expose through `/introspect` so downstream services validate sender.
|
||||||
|
- [ ] Add emergency bypass flag (`security.senderConstraints.dpop.allowTemporaryBypass`) for sealed recap drills; default disabled.
|
||||||
|
|
||||||
|
## Phase 3 · mTLS Binding (ETA 2025-11-10)
|
||||||
|
- [x] Capture client cert thumbprint on `/token` (mutual TLS) and store in `authority_tokens.senderCertificate`.
|
||||||
|
- [x] Validate cert hash on `/introspect` and `/fresh-auth`.
|
||||||
|
- [ ] Document bootstrap/rotation in `docs/11_AUTHORITY.md` + `docs/security/dpop-mtls-rollout.md` (this file).
|
||||||
|
|
||||||
|
## Verification Matrix
|
||||||
|
| Scenario | Test/Command | Expected |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| DPoP required w/out proof | `dotnet test Authority.Tests --filter DPoPRequiresProofTest` | 400 with `use_dpop_nonce` header. |
|
||||||
|
| Nonce replay | Replay previous proof within window | 401 + audit log entry. |
|
||||||
|
| mTLS mismatch | Reuse token with different cert | 401 + `senderCertificateMismatch` metric increment. |
|
||||||
|
|
||||||
|
## Telemetry & Alerting
|
||||||
|
- Metrics: `authority_dpop_nonce_miss_total`, `authority_mtls_mismatch_total` (emitted with `reason` tags for context-missing, missing-certificate, and thumbprint-mismatch cases).
|
||||||
|
- Logs: `authority.security.senderConstraint` (structured).
|
||||||
|
- Alerts: Page DevOps when nonce miss > 5% or mTLS mismatches > 0 over 10 min.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Authority Core & Security Guild owners.
|
||||||
|
- DevOps to provide sealed-mode CI coverage (`DEVOPS-AIRGAP-57-002`).
|
||||||
|
- Plugin Standard Guild to consume new telemetry once rolled out.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
- Daily async update in `#guild-authority` thread referencing this plan.
|
||||||
|
- Link this document from `docs/implplan/SPRINT_100_identity_signing.md` notes once Phase 1 merges.
|
||||||
68
docs/security/rootpack_ru_package.md
Normal file
68
docs/security/rootpack_ru_package.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# RootPack_RU Packaging Guide
|
||||||
|
|
||||||
|
This guide describes the reproducible process for assembling the sovereign cryptography bundle that backs RootPack_RU deployments.
|
||||||
|
|
||||||
|
## 1. What the bundle contains
|
||||||
|
|
||||||
|
| Directory | Purpose |
|
||||||
|
|-----------|---------|
|
||||||
|
| `artifacts/` | Published binaries for `StellaOps.Cryptography.Plugin.CryptoPro` and `StellaOps.Cryptography.Plugin.Pkcs11Gost` (targeting `net10.0`). |
|
||||||
|
| `config/rootpack_ru.crypto.yaml` | Opinionated configuration template that enables the `ru-offline` crypto profile and defines CryptoPro + PKCS#11 keys. |
|
||||||
|
| `docs/` | Validation runbook, audit report, and this packaging guide. |
|
||||||
|
| `trust/` | Russian trust-anchor PEM files copied from `certificates/russian_trusted_*`. |
|
||||||
|
| `README.txt` | High-level summary plus operator checklist. |
|
||||||
|
|
||||||
|
## 2. Build the bundle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from repository root
|
||||||
|
scripts/crypto/package-rootpack-ru.sh
|
||||||
|
# optionally specify destination
|
||||||
|
scripts/crypto/package-rootpack-ru.sh /tmp/rootpack_ru_$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
```
|
||||||
|
|
||||||
|
The script performs the following steps:
|
||||||
|
|
||||||
|
1. `dotnet publish` for the CryptoPro + PKCS#11 plug-ins (`Release` configuration).
|
||||||
|
2. Copies the relevant documentation (`docs/security/rootpack_ru_validation.md`, `docs/security/crypto-routing-audit-2025-11-07.md`, and this guide).
|
||||||
|
3. Includes the example configuration found at `etc/rootpack/ru/crypto.profile.yaml`.
|
||||||
|
4. Adds the Russian trust anchors from `certificates/russian_trusted_*`.
|
||||||
|
5. Emits `README.txt` and optionally creates a `*.tar.gz` archive (set `PACKAGE_TAR=0` to skip the tarball).
|
||||||
|
|
||||||
|
## 3. Attach deterministic test evidence
|
||||||
|
|
||||||
|
After running `scripts/crypto/package-rootpack-ru.sh`, execute the deterministic harness to capture logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/crypto/run-rootpack-ru-tests.sh
|
||||||
|
# or specify ROOTPACK_LOG_DIR=/tmp/rootpack_ru_tests scripts/crypto/run-rootpack-ru-tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the resulting `logs/rootpack_ru_<timestamp>/` directory into the bundle before distributing it (or store it alongside the tarball in your evidence store).
|
||||||
|
|
||||||
|
Each harness run produces a `README.tests` file plus matching `.log/.trx` pairs for every project. Move the entire directory under an evidence folder inside the bundle (for example `evidence/validation/<timestamp>/`) so operators can quickly locate the README, raw logs, and provider JSON snapshots when assembling compliance paperwork:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dest="${1:-out/rootpack_ru}"
|
||||||
|
ts="$(ls logs | grep rootpack_ru_ | sort | tail -n1)"
|
||||||
|
mkdir -p "${dest}/evidence/validation"
|
||||||
|
cp -a "logs/${ts}" "${dest}/evidence/validation/${ts}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Hardware validation + audit metadata
|
||||||
|
|
||||||
|
Follow `docs/security/rootpack_ru_validation.md` to:
|
||||||
|
|
||||||
|
- Validate CryptoPro CSP and PKCS#11 tokens.
|
||||||
|
- Capture `stellaops crypto providers --profile ru-offline --json` output.
|
||||||
|
- Archive JWKS snapshots and `CryptoProviderMetrics` samples.
|
||||||
|
- Document hardware serials and operator initials in `hardware_notes.md`.
|
||||||
|
|
||||||
|
Store these artifacts under `logs/rootpack_ru_<timestamp>/` (same directory as the test harness outputs) and reference them in release paperwork.
|
||||||
|
|
||||||
|
## 5. Deployment summary
|
||||||
|
|
||||||
|
1. Import the bundled trust anchors into the target installation (Authority + Scanner).
|
||||||
|
2. Apply `config/rootpack_ru.crypto.yaml`, update certificate thumbprints, slots, and container labels to match the operator tokens.
|
||||||
|
3. Restart the services so `ICryptoProviderRegistry` reloads the `ru-offline` profile.
|
||||||
|
4. Re-run the validation runbook to confirm JWKS, telemetry, and RootPack evidence are aligned with the shipping bundle.
|
||||||
45
docs/security/rootpack_ru_validation.md
Normal file
45
docs/security/rootpack_ru_validation.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# RootPack_RU Crypto Validation Runbook
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This runbook documents the repeatable steps for validating the Russian sovereign crypto profile (CryptoPro + PKCS#11) prior to publishing a RootPack bundle. It supplements the crypto routing audit by covering deterministic tests, hardware validation, and the audit metadata artifacts that must be attached to each release.
|
||||||
|
|
||||||
|
## 1. Deterministic Test Harness
|
||||||
|
|
||||||
|
1. Run `scripts/crypto/run-rootpack-ru-tests.sh` (optional `ROOTPACK_LOG_DIR=/tmp/rootpack_ru_logs` to override the output path). The script executes:
|
||||||
|
- `src/__Libraries/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj`
|
||||||
|
- `src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/StellaOps.Scanner.Worker.Tests.csproj`
|
||||||
|
- `src/Scanner/__Tests/StellaOps.Scanner.Sbomer.BuildXPlugin.Tests/StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.csproj`
|
||||||
|
and emits `.log` + `.trx` pairs plus `README.tests` under `logs/rootpack_ru_<timestamp>/`.
|
||||||
|
2. For ad-hoc runs (CI or IDE) ensure the same three projects succeed; the cryptography tests validate SHA-256/SHA-512 against BCL implementations and both Streebog variants against BouncyCastle digests.
|
||||||
|
3. Archive the generated log directory (`logs/rootpack_ru_<timestamp>/`) along with any additional test outputs inside the RootPack evidence bundle.
|
||||||
|
|
||||||
|
## 2. Hardware Validation (CryptoPro CSP)
|
||||||
|
|
||||||
|
1. Install CryptoPro CSP (v5.0 or later) on the validation host and import the qualified certificate configured for the deployment.
|
||||||
|
2. Configure `StellaOps:Crypto:CryptoPro:Keys` with the container handle and certificate thumbprint and set `StellaOps:Crypto:Registry:ActiveProfile=ru-offline`.
|
||||||
|
3. Run the provider diagnostics to confirm the key material is visible:
|
||||||
|
- `stellaops crypto providers --profile ru-offline --json > logs/ru_cryptopro_providers.json`
|
||||||
|
4. Issue a JWKS fetch (`curl https://authority.local/.well-known/jwks`) and verify the `kid` and `crv` values match the CryptoPro-backed key.
|
||||||
|
5. Capture the Authority logs showing `AuthoritySecretHasherInitializer` startup and the `CryptoProviderMetrics` counters for `ru.cryptopro.csp` usage.
|
||||||
|
|
||||||
|
## 3. Hardware Validation (PKCS#11 Tokens)
|
||||||
|
|
||||||
|
1. Install the vendor PKCS#11 library (e.g., Rutoken `rtPKCS11ECP.dll` or JaCarta) and configure the slot/PIN inside `StellaOps:Crypto:Pkcs11:Keys`.
|
||||||
|
2. Switch the registry profile to prioritize `ru.pkcs11` and rerun `stellaops crypto providers --profile ru-offline --json > logs/ru_pkcs11_providers.json`.
|
||||||
|
3. Execute a signing workflow (Authority JWKS refresh or Scanner manifest publish) and confirm the `CryptoProviderMetrics` counters record `ru.pkcs11` activity.
|
||||||
|
4. Export the token audit logs (if available) and store them with the RootPack evidence bundle.
|
||||||
|
|
||||||
|
## 4. RootPack Audit Metadata
|
||||||
|
|
||||||
|
Create a metadata bundle per validation run and store it under `logs/rootpack_ru_<timestamp>/` containing:
|
||||||
|
|
||||||
|
- `providers_ru_offline.json` – output from `stellaops crypto providers --profile ru-offline --json`.
|
||||||
|
- `crypto_tests.txt` – snippets from the unit-test executions listed above.
|
||||||
|
- `hardware_notes.md` – human-readable notes describing token serials, firmware, and operator initials.
|
||||||
|
- `jwks_snapshot.json` – raw JWKS response captured after sovereign providers are active.
|
||||||
|
- `metrics_snapshot.json` – scrape of `CryptoProviderMetrics` Prometheus samples for both providers.
|
||||||
|
|
||||||
|
Attach this directory to the RootPack artifact and reference it from the release checklist.
|
||||||
|
|
||||||
|
Refer back to `docs/security/crypto-routing-audit-2025-11-07.md` for the full inventory of components that must consume the shared cryptography stack, and `docs/security/rootpack_ru_package.md` for packaging/attachment steps.
|
||||||
@@ -22,6 +22,11 @@ workers:
|
|||||||
schedulerIntervalSeconds: 15
|
schedulerIntervalSeconds: 15
|
||||||
maxConcurrentEvaluations: 4
|
maxConcurrentEvaluations: 4
|
||||||
|
|
||||||
|
activation:
|
||||||
|
forceTwoPersonApproval: false
|
||||||
|
defaultRequiresTwoPersonApproval: false
|
||||||
|
emitAuditLogs: true
|
||||||
|
|
||||||
resourceServer:
|
resourceServer:
|
||||||
authority: "https://authority.stella-ops.local"
|
authority: "https://authority.stella-ops.local"
|
||||||
requireHttpsMetadata: true
|
requireHttpsMetadata: true
|
||||||
|
|||||||
30
etc/rootpack/ru/crypto.profile.yaml
Normal file
30
etc/rootpack/ru/crypto.profile.yaml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
StellaOps:
|
||||||
|
Crypto:
|
||||||
|
Registry:
|
||||||
|
ActiveProfile: ru-offline
|
||||||
|
PreferredProviders:
|
||||||
|
- default
|
||||||
|
Profiles:
|
||||||
|
ru-offline:
|
||||||
|
PreferredProviders:
|
||||||
|
- ru.cryptopro.csp
|
||||||
|
- ru.pkcs11
|
||||||
|
CryptoPro:
|
||||||
|
Keys:
|
||||||
|
- KeyId: ru-csp-default
|
||||||
|
LibraryPath: /opt/cprocsp/lib/amd64/libcapi20.so
|
||||||
|
ContainerLabel: CN=RootPack Signing
|
||||||
|
CertificateThumbprint: "<thumbprint>"
|
||||||
|
Pkcs11:
|
||||||
|
Keys:
|
||||||
|
- KeyId: ru-token-default
|
||||||
|
LibraryPath: /usr/local/lib/librutokenecp.so
|
||||||
|
SlotId: "0x1"
|
||||||
|
Pin: "${PKCS11_PIN}"
|
||||||
|
PrivateKeyLabel: rootpack-signing
|
||||||
|
CertificateThumbprint: "<thumbprint>"
|
||||||
|
Diagnostics:
|
||||||
|
Providers:
|
||||||
|
Enabled: true
|
||||||
|
Metrics:
|
||||||
|
LogLevel: Information
|
||||||
@@ -24,5 +24,6 @@ Signals:
|
|||||||
ConnectionString: "mongodb://localhost:27017/signals"
|
ConnectionString: "mongodb://localhost:27017/signals"
|
||||||
Database: "signals"
|
Database: "signals"
|
||||||
CallgraphsCollection: "callgraphs"
|
CallgraphsCollection: "callgraphs"
|
||||||
|
ReachabilityFactsCollection: "reachability_facts"
|
||||||
Storage:
|
Storage:
|
||||||
RootPath: "../data/signals-artifacts"
|
RootPath: "../data/signals-artifacts"
|
||||||
|
|||||||
@@ -46,3 +46,5 @@
|
|||||||
| HELM-45-001 | TODO | Deployment Guild | COMPOSE-44-001 | Scaffold `deploy/helm/stella` chart with values, component toggles, and pinned image digests for all services; include migration Job templates. | Chart installs in dev cluster; images pinned; lint/tests pass. |
|
| HELM-45-001 | TODO | Deployment Guild | COMPOSE-44-001 | Scaffold `deploy/helm/stella` chart with values, component toggles, and pinned image digests for all services; include migration Job templates. | Chart installs in dev cluster; images pinned; lint/tests pass. |
|
||||||
| HELM-45-002 | TODO | Deployment Guild, Security Guild | HELM-45-001 | Add TLS/Ingress, NetworkPolicy, PodSecurityContexts, Secrets integration (external secrets), and document security posture. | Helm values support secure defaults; policies validated; docs updated. |
|
| HELM-45-002 | TODO | Deployment Guild, Security Guild | HELM-45-001 | Add TLS/Ingress, NetworkPolicy, PodSecurityContexts, Secrets integration (external secrets), and document security posture. | Helm values support secure defaults; policies validated; docs updated. |
|
||||||
| HELM-45-003 | TODO | Deployment Guild, Observability Guild | HELM-45-001 | Implement HPA, PDB, readiness gates, Prometheus scraping annotations, OTel configuration hooks, and upgrade hooks. | Rolling upgrade succeeds in CI; observability wires confirmed; upgrade docs updated. |
|
| HELM-45-003 | TODO | Deployment Guild, Observability Guild | HELM-45-001 | Implement HPA, PDB, readiness gates, Prometheus scraping annotations, OTel configuration hooks, and upgrade hooks. | Rolling upgrade succeeds in CI; observability wires confirmed; upgrade docs updated. |
|
||||||
|
| HELM-45-004 | DONE (2025-11-08) | Deployment Guild, Policy Guild | HELM-45-001 | Wire Policy Engine / Gateway pods to consume the `policy-engine-activation` ConfigMap (envFrom/volume mounts), ensure host configuration loads activation overrides, and update Helm/Compose samples accordingly. | Pods mount config map deterministically; activation settings honored in Policy Engine; samples/tests updated for air-gap parity. |
|
||||||
|
> 2025-11-08: Added config builder support for `/config/policy-engine/activation.yaml`, templated envFrom injection for policy-engine/gateway pods, verified Policy Engine/Gateway tests, and CI now runs `helm lint` + `helm template` for every `values*.yaml`.
|
||||||
|
|||||||
@@ -45,7 +45,10 @@
|
|||||||
| DEVOPS-AIRGAP-56-002 | TODO | DevOps Guild, AirGap Importer Guild | AIRGAP-IMP-57-002 | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. | Scripts documented; smoke tests validate import; runbook updated. |
|
| DEVOPS-AIRGAP-56-002 | TODO | DevOps Guild, AirGap Importer Guild | AIRGAP-IMP-57-002 | Provide import tooling for bundle staging: checksum validation, offline object-store loader scripts, removable media guidance. | Scripts documented; smoke tests validate import; runbook updated. |
|
||||||
| DEVOPS-AIRGAP-56-003 | TODO | DevOps Guild, Container Distribution Guild | EXPORT-AIRGAP-56-002 | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. | Pipeline runs in connected env; pack verified in air-gap smoke test; manifest recorded. |
|
| DEVOPS-AIRGAP-56-003 | TODO | DevOps Guild, Container Distribution Guild | EXPORT-AIRGAP-56-002 | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. | Pipeline runs in connected env; pack verified in air-gap smoke test; manifest recorded. |
|
||||||
| DEVOPS-AIRGAP-57-001 | TODO | DevOps Guild, Mirror Creator Guild | MIRROR-CRT-56-002 | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. | Approval workflow enforced; CI artifact includes DSSE/TUF metadata; audit logs stored. |
|
| DEVOPS-AIRGAP-57-001 | TODO | DevOps Guild, Mirror Creator Guild | MIRROR-CRT-56-002 | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. | Approval workflow enforced; CI artifact includes DSSE/TUF metadata; audit logs stored. |
|
||||||
| DEVOPS-AIRGAP-57-002 | TODO | DevOps Guild, Authority Guild | AUTH-OBS-50-001 | Configure sealed-mode CI tests that run services with sealed flag and ensure no egress occurs (iptables + mock DNS). | CI suite fails on attempted egress; reports remediation; documentation updated. |
|
| DEVOPS-AIRGAP-57-002 | DOING (2025-11-08) | DevOps Guild, Authority Guild | AUTH-OBS-50-001 | Configure sealed-mode CI tests that run services with sealed flag and ensure no egress occurs (iptables + mock DNS). | CI suite fails on attempted egress; reports remediation; documentation updated. |
|
||||||
|
> 2025-11-08: Landed `sealed-mode-compose.yml`, `run-sealed-ci.sh`, and `egress_probe.py`, plus the `.gitea/workflows/build-test-deploy.yml` job that uploads `artifacts/sealed-mode-ci/<commit>/authority-sealed-ci.json`; waiting on Authority to consume the artefact before flipping DONE.
|
||||||
|
> 2025-11-07: Blocking AUTH-AIRGAP-57-001 (Authority gating); prioritize sealed-mode CI artifacts so Authority can flip the enforcement switch.
|
||||||
|
> 2025-11-07: Target ETA agreed with Authority is 2025-11-10 for first CI run (iptables + mock DNS) plus doc updates.
|
||||||
| DEVOPS-AIRGAP-58-001 | TODO | DevOps Guild, Notifications Guild | NOTIFY-AIRGAP-56-002 | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. | Templates deployed successfully; health checks in CI; docs updated. |
|
| DEVOPS-AIRGAP-58-001 | TODO | DevOps Guild, Notifications Guild | NOTIFY-AIRGAP-56-002 | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. | Templates deployed successfully; health checks in CI; docs updated. |
|
||||||
| DEVOPS-AIRGAP-58-002 | TODO | DevOps Guild, Observability Guild | DEVOPS-AIRGAP-56-001, DEVOPS-OBS-51-001 | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. | Stack boots offline; dashboards available; verification script confirms zero egress. |
|
| DEVOPS-AIRGAP-58-002 | TODO | DevOps Guild, Observability Guild | DEVOPS-AIRGAP-56-001, DEVOPS-OBS-51-001 | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. | Stack boots offline; dashboards available; verification script confirms zero egress. |
|
||||||
| DEVOPS-REL-17-004 | BLOCKED (2025-10-26) | DevOps Guild | DEVOPS-REL-17-002 | Ensure release workflow publishes `out/release/debug` (build-id tree + manifest) and fails when symbols are missing. | Release job emits debug artefacts, `mirror_debug_store.py` summary committed, warning cleared from build logs, docs updated. |
|
| DEVOPS-REL-17-004 | BLOCKED (2025-10-26) | DevOps Guild | DEVOPS-REL-17-002 | Ensure release workflow publishes `out/release/debug` (build-id tree + manifest) and fails when symbols are missing. | Release job emits debug artefacts, `mirror_debug_store.py` summary committed, warning cleared from build logs, docs updated. |
|
||||||
|
|||||||
25
ops/devops/sealed-mode-ci/README.md
Normal file
25
ops/devops/sealed-mode-ci/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Sealed-Mode CI Harness
|
||||||
|
|
||||||
|
This harness supports `DEVOPS-AIRGAP-57-002` by exercising services with the `sealed` flag, verifying that no outbound network traffic succeeds, and producing artefacts Authority can use for `AUTH-AIRGAP-57-001` gating.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
1. Run `./run-sealed-ci.sh` from this directory (the script now boots the stack, applies the iptables guard, and captures artefacts automatically).
|
||||||
|
2. The harness:
|
||||||
|
- Launches `sealed-mode-compose.yml` with Authority/Signer/Attestor + Mongo.
|
||||||
|
- Snapshots iptables, injects a `STELLAOPS_SEALED` chain into `DOCKER-USER`/`OUTPUT`, and whitelists only loopback + RFC1918 ranges so container egress is denied.
|
||||||
|
- Repeatedly polls `/healthz` on `5088/6088/7088` to verify sealed-mode bindings stay healthy while egress is blocked.
|
||||||
|
- Executes `egress_probe.py`, which runs curl probes from inside the compose network to confirm off-cluster addresses are unreachable.
|
||||||
|
- Writes logs, iptables counters, and the summary contract to `artifacts/sealed-mode-ci/<timestamp>`.
|
||||||
|
3. `.gitea/workflows/build-test-deploy.yml` now includes a `sealed-mode-ci` job that runs this script on every push/PR and uploads the artefacts for `AUTH-AIRGAP-57-001`.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
- `authority.health.log`, `signer.health.log`, `attestor.health.log`
|
||||||
|
- `iptables-docker-user.txt`, `iptables-output.txt`
|
||||||
|
- `egress-probe.json`
|
||||||
|
- `compose.log`, `compose.ps`
|
||||||
|
- `authority-sealed-ci.json` (single file Authority uses to validate the run)
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
- [ ] Wire into offline kit smoke tests (DEVOPS-AIRGAP-58-001).
|
||||||
|
|
||||||
|
Refer to `docs/security/dpop-mtls-rollout.md` for cross-guild milestones.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
The command 'docker' could not be found in this WSL 2 distro.
|
||||||
|
We recommend to activate the WSL integration in Docker Desktop settings.
|
||||||
|
|
||||||
|
For details about using Docker Desktop with WSL 2, visit:
|
||||||
|
|
||||||
|
https://docs.docker.com/go/wsl2/
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
The command 'docker' could not be found in this WSL 2 distro.
|
||||||
|
We recommend to activate the WSL integration in Docker Desktop settings.
|
||||||
|
|
||||||
|
For details about using Docker Desktop with WSL 2, visit:
|
||||||
|
|
||||||
|
https://docs.docker.com/go/wsl2/
|
||||||
|
|
||||||
54
ops/devops/sealed-mode-ci/authority.harness.yaml
Normal file
54
ops/devops/sealed-mode-ci/authority.harness.yaml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
schemaVersion: 1
|
||||||
|
issuer: http://authority.sealed-ci.local
|
||||||
|
accessTokenLifetime: 00:02:00
|
||||||
|
refreshTokenLifetime: 01:00:00
|
||||||
|
identityTokenLifetime: 00:05:00
|
||||||
|
authorizationCodeLifetime: 00:05:00
|
||||||
|
deviceCodeLifetime: 00:15:00
|
||||||
|
pluginDirectories:
|
||||||
|
- /app
|
||||||
|
plugins:
|
||||||
|
configurationDirectory: /app/plugins
|
||||||
|
descriptors:
|
||||||
|
standard:
|
||||||
|
type: standard
|
||||||
|
assemblyName: StellaOps.Authority.Plugin.Standard
|
||||||
|
enabled: true
|
||||||
|
configFile: standard.yaml
|
||||||
|
storage:
|
||||||
|
connectionString: mongodb://sealedci:sealedci@mongo:27017/authority?authSource=admin
|
||||||
|
databaseName: authority
|
||||||
|
commandTimeout: 00:00:30
|
||||||
|
signing:
|
||||||
|
enabled: true
|
||||||
|
activeKeyId: sealed-ci
|
||||||
|
keyPath: /certificates/authority-signing-dev.pem
|
||||||
|
algorithm: ES256
|
||||||
|
keySource: file
|
||||||
|
bootstrap:
|
||||||
|
enabled: false
|
||||||
|
crypto:
|
||||||
|
providers: []
|
||||||
|
security:
|
||||||
|
senderConstraints:
|
||||||
|
dpop:
|
||||||
|
enabled: true
|
||||||
|
proofLifetime: 00:02:00
|
||||||
|
replayWindow: 00:05:00
|
||||||
|
nonce:
|
||||||
|
enabled: false
|
||||||
|
mtls:
|
||||||
|
enabled: false
|
||||||
|
airGap:
|
||||||
|
egress:
|
||||||
|
mode: Sealed
|
||||||
|
allowLoopback: true
|
||||||
|
allowPrivateNetworks: true
|
||||||
|
remediationDocumentationUrl: https://docs.stella-ops.org/airgap/sealed-ci
|
||||||
|
supportContact: airgap-ops@stella-ops.org
|
||||||
|
tenants:
|
||||||
|
- name: sealed-ci
|
||||||
|
roles:
|
||||||
|
operators:
|
||||||
|
scopes:
|
||||||
|
- policy:read
|
||||||
83
ops/devops/sealed-mode-ci/egress_probe.py
Normal file
83
ops/devops/sealed-mode-ci/egress_probe.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run egress probes from the sealed-mode Docker network."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
DEFAULT_TARGETS = [
|
||||||
|
"https://example.com",
|
||||||
|
"https://www.cloudflare.com",
|
||||||
|
"https://releases.stella-ops.org/healthz",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run_probe(image: str, network: str, target: str, timeout: int) -> dict:
|
||||||
|
cmd: List[str] = [
|
||||||
|
"docker",
|
||||||
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"--network",
|
||||||
|
network,
|
||||||
|
image,
|
||||||
|
"-fsS",
|
||||||
|
"--max-time",
|
||||||
|
str(timeout),
|
||||||
|
target,
|
||||||
|
]
|
||||||
|
started = time.monotonic()
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
duration = time.monotonic() - started
|
||||||
|
status = "blocked" if proc.returncode != 0 else "connected"
|
||||||
|
return {
|
||||||
|
"target": target,
|
||||||
|
"status": status,
|
||||||
|
"durationSeconds": round(duration, 3),
|
||||||
|
"exitCode": proc.returncode,
|
||||||
|
"command": " ".join(shlex.quote(part) for part in cmd),
|
||||||
|
"stdout": proc.stdout.strip(),
|
||||||
|
"stderr": proc.stderr.strip(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--network", required=True, help="Docker network to join (compose project network)")
|
||||||
|
parser.add_argument("--image", default="curlimages/curl:8.6.0", help="Container image providing curl")
|
||||||
|
parser.add_argument("--timeout", type=int, default=10, help="Curl max-time for each probe (seconds)")
|
||||||
|
parser.add_argument("--output", required=True, help="Path to write JSON results")
|
||||||
|
parser.add_argument("targets", nargs="*", help="Override target URLs")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
targets = args.targets or DEFAULT_TARGETS
|
||||||
|
results = [run_probe(args.image, args.network, target, args.timeout) for target in targets]
|
||||||
|
passed = all(result["status"] == "blocked" for result in results)
|
||||||
|
payload = {
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"network": args.network,
|
||||||
|
"image": args.image,
|
||||||
|
"targets": results,
|
||||||
|
"passed": passed,
|
||||||
|
}
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||||
|
with open(args.output, "w", encoding="utf-8") as handle:
|
||||||
|
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
||||||
|
handle.write("\n")
|
||||||
|
|
||||||
|
return 0 if passed else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except Exception as exc: # pragma: no cover
|
||||||
|
print(f"egress probe failed: {exc}", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
18
ops/devops/sealed-mode-ci/plugins/standard.yaml
Normal file
18
ops/devops/sealed-mode-ci/plugins/standard.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
bootstrapUser:
|
||||||
|
username: sealed-admin
|
||||||
|
password: ChangeMe11!
|
||||||
|
passwordPolicy:
|
||||||
|
minimumLength: 8
|
||||||
|
requireUppercase: false
|
||||||
|
requireLowercase: true
|
||||||
|
requireDigit: true
|
||||||
|
requireSymbol: false
|
||||||
|
passwordHashing:
|
||||||
|
algorithm: Argon2id
|
||||||
|
memorySizeInKib: 8192
|
||||||
|
iterations: 2
|
||||||
|
parallelism: 1
|
||||||
|
lockout:
|
||||||
|
enabled: false
|
||||||
|
tokenSigning:
|
||||||
|
keyDirectory: /certificates
|
||||||
169
ops/devops/sealed-mode-ci/run-sealed-ci.sh
Normal file
169
ops/devops/sealed-mode-ci/run-sealed-ci.sh
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
COMPOSE_FILE=${COMPOSE_FILE:-"$SCRIPT_DIR/sealed-mode-compose.yml"}
|
||||||
|
PROJECT_NAME=${COMPOSE_PROJECT_NAME:-sealedmode}
|
||||||
|
NETWORK_NAME="${PROJECT_NAME}_sealed-ci"
|
||||||
|
ARTIFACT_ROOT=${ARTIFACT_ROOT:-"$SCRIPT_DIR/artifacts/sealed-mode-ci"}
|
||||||
|
STAMP=$(date -u +"%Y%m%dT%H%M%SZ")
|
||||||
|
OUT_DIR="$ARTIFACT_ROOT/$STAMP"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
EXIT_CODE=0
|
||||||
|
IPTABLES_SNAPSHOT=""
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local exit_code=$?
|
||||||
|
log "Collecting docker compose logs"
|
||||||
|
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" logs >"$OUT_DIR/compose.log" 2>&1 || true
|
||||||
|
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" ps -a >"$OUT_DIR/compose.ps" 2>&1 || true
|
||||||
|
log "Tearing down sealed-mode stack"
|
||||||
|
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" down -v >"$OUT_DIR/docker-down.log" 2>&1 || true
|
||||||
|
if [[ -n "$IPTABLES_SNAPSHOT" && -f "$IPTABLES_SNAPSHOT" ]]; then
|
||||||
|
log "Restoring iptables snapshot"
|
||||||
|
sudo iptables-restore <"$IPTABLES_SNAPSHOT" || true
|
||||||
|
rm -f "$IPTABLES_SNAPSHOT"
|
||||||
|
fi
|
||||||
|
log "Artifacts stored at $OUT_DIR"
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
log "Pulling compose images (best effort)"
|
||||||
|
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" pull --ignore-pull-failures || true
|
||||||
|
|
||||||
|
log "Starting sealed-mode stack"
|
||||||
|
docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d --remove-orphans
|
||||||
|
|
||||||
|
wait_for_port() {
|
||||||
|
local port=$1
|
||||||
|
local label=$2
|
||||||
|
for attempt in $(seq 1 30); do
|
||||||
|
if curl -fsS --max-time 5 "http://127.0.0.1:${port}/healthz" >/dev/null 2>&1; then
|
||||||
|
log "$label responded on port $port"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
log "$label failed to respond on port $port"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_port 5088 "Authority" || EXIT_CODE=1
|
||||||
|
wait_for_port 6088 "Signer" || EXIT_CODE=1
|
||||||
|
wait_for_port 7088 "Attestor" || EXIT_CODE=1
|
||||||
|
|
||||||
|
log "Fetching probe helper image"
|
||||||
|
docker pull curlimages/curl:8.6.0 >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
log "Snapshotting iptables state"
|
||||||
|
IPTABLES_SNAPSHOT=$(mktemp)
|
||||||
|
sudo iptables-save >"$IPTABLES_SNAPSHOT"
|
||||||
|
|
||||||
|
log "Applying sealed-mode egress policy"
|
||||||
|
CHAIN="STELLAOPS_SEALED"
|
||||||
|
sudo iptables -N "$CHAIN" 2>/dev/null || sudo iptables -F "$CHAIN"
|
||||||
|
for cidr in 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16; do
|
||||||
|
sudo iptables -A "$CHAIN" -d "$cidr" -j RETURN
|
||||||
|
done
|
||||||
|
sudo iptables -A "$CHAIN" -j LOG --log-prefix "stellaops-sealed-deny " --log-level 4
|
||||||
|
sudo iptables -A "$CHAIN" -j DROP
|
||||||
|
sudo iptables -I DOCKER-USER 1 -j "$CHAIN"
|
||||||
|
sudo iptables -I OUTPUT 1 -j "$CHAIN"
|
||||||
|
|
||||||
|
check_health() {
|
||||||
|
local name=$1
|
||||||
|
local port=$2
|
||||||
|
local url="http://127.0.0.1:${port}/healthz"
|
||||||
|
local log_file="$OUT_DIR/${name}.health.log"
|
||||||
|
local status="fail"
|
||||||
|
for attempt in $(seq 1 20); do
|
||||||
|
if curl -fsS --max-time 5 "$url" >"$log_file" 2>&1; then
|
||||||
|
status="pass"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [[ "$status" == "pass" ]]; then
|
||||||
|
log "$name health check succeeded"
|
||||||
|
else
|
||||||
|
log "$name health check failed"
|
||||||
|
EXIT_CODE=1
|
||||||
|
fi
|
||||||
|
local upper
|
||||||
|
upper=$(echo "$name" | tr '[:lower:]' '[:upper:]')
|
||||||
|
eval "${upper}_HEALTH_STATUS=$status"
|
||||||
|
eval "${upper}_HEALTH_URL=$url"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_health authority 5088
|
||||||
|
check_health signer 6088
|
||||||
|
check_health attestor 7088
|
||||||
|
|
||||||
|
log "Running egress probe via docker network $NETWORK_NAME"
|
||||||
|
EGRESS_JSON="$OUT_DIR/egress-probe.json"
|
||||||
|
if python3 "$SCRIPT_DIR/egress_probe.py" --network "$NETWORK_NAME" --image curlimages/curl:8.6.0 --timeout 8 --output "$EGRESS_JSON"; then
|
||||||
|
EGRESS_STATUS="pass"
|
||||||
|
else
|
||||||
|
EGRESS_STATUS="fail"
|
||||||
|
EXIT_CODE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Dumping iptables counters"
|
||||||
|
sudo iptables -v -x -L DOCKER-USER >"$OUT_DIR/iptables-docker-user.txt"
|
||||||
|
sudo iptables -v -x -L OUTPUT >"$OUT_DIR/iptables-output.txt"
|
||||||
|
|
||||||
|
log "Recording summary JSON"
|
||||||
|
export PROJECT_NAME NETWORK_NAME EGRESS_STATUS EGRESS_JSON
|
||||||
|
export AUTHORITY_HEALTH_STATUS SIGNER_HEALTH_STATUS ATTESTOR_HEALTH_STATUS
|
||||||
|
export AUTHORITY_HEALTH_URL SIGNER_HEALTH_URL ATTESTOR_HEALTH_URL
|
||||||
|
python3 - <<'PY' >"$OUT_DIR/authority-sealed-ci.json"
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"project": os.environ.get("PROJECT_NAME"),
|
||||||
|
"network": os.environ.get("NETWORK_NAME"),
|
||||||
|
"health": {
|
||||||
|
"authority": {
|
||||||
|
"status": os.environ.get("AUTHORITY_HEALTH_STATUS", "unknown"),
|
||||||
|
"url": os.environ.get("AUTHORITY_HEALTH_URL"),
|
||||||
|
"log": "authority.health.log",
|
||||||
|
},
|
||||||
|
"signer": {
|
||||||
|
"status": os.environ.get("SIGNER_HEALTH_STATUS", "unknown"),
|
||||||
|
"url": os.environ.get("SIGNER_HEALTH_URL"),
|
||||||
|
"log": "signer.health.log",
|
||||||
|
},
|
||||||
|
"attestor": {
|
||||||
|
"status": os.environ.get("ATTESTOR_HEALTH_STATUS", "unknown"),
|
||||||
|
"url": os.environ.get("ATTESTOR_HEALTH_URL"),
|
||||||
|
"log": "attestor.health.log",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"egressProbe": {
|
||||||
|
"status": os.environ.get("EGRESS_STATUS", "unknown"),
|
||||||
|
"report": os.path.basename(os.environ.get("EGRESS_JSON", "egress-probe.json")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
json.dump(summary, sys.stdout, indent=2)
|
||||||
|
print()
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [[ $EXIT_CODE -eq 0 ]]; then
|
||||||
|
log "Sealed-mode CI run completed successfully"
|
||||||
|
else
|
||||||
|
log "Sealed-mode CI run completed with failures"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $EXIT_CODE
|
||||||
83
ops/devops/sealed-mode-ci/sealed-mode-compose.yml
Normal file
83
ops/devops/sealed-mode-ci/sealed-mode-compose.yml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
x-release-labels: &release-labels
|
||||||
|
com.stellaops.profile: 'sealed-ci'
|
||||||
|
com.stellaops.airgap.mode: 'sealed'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
sealed-ci:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sealed-mongo-data:
|
||||||
|
|
||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: docker.io/library/mongo@sha256:c258b26dbb7774f97f52aff52231ca5f228273a84329c5f5e451c3739457db49
|
||||||
|
command: ['mongod', '--bind_ip_all']
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MONGO_INITDB_ROOT_USERNAME: sealedci
|
||||||
|
MONGO_INITDB_ROOT_PASSWORD: sealedci-secret
|
||||||
|
volumes:
|
||||||
|
- sealed-mongo-data:/data/db
|
||||||
|
networks:
|
||||||
|
- sealed-ci
|
||||||
|
labels: *release-labels
|
||||||
|
|
||||||
|
authority:
|
||||||
|
image: registry.stella-ops.org/stellaops/authority@sha256:a8e8faec44a579aa5714e58be835f25575710430b1ad2ccd1282a018cd9ffcdd
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_URLS: http://+:5088
|
||||||
|
STELLAOPS_AUTHORITY__ISSUER: http://authority.sealed-ci.local
|
||||||
|
STELLAOPS_AUTHORITY__MONGO__CONNECTIONSTRING: mongodb://sealedci:sealedci-secret@mongo:27017/authority?authSource=admin
|
||||||
|
STELLAOPS_AUTHORITY__PLUGINDIRECTORIES__0: /app/plugins
|
||||||
|
STELLAOPS_AUTHORITY__PLUGINS__CONFIGURATIONDIRECTORY: /app/plugins
|
||||||
|
STELLAOPS_AUTHORITY__SECURITY__SENDERCONSTRAINTS__DPOP__ENABLED: 'true'
|
||||||
|
STELLAOPS_AUTHORITY__SECURITY__SENDERCONSTRAINTS__MTLS__ENABLED: 'true'
|
||||||
|
STELLAOPS_AUTHORITY__AIRGAP__EGRESS__MODE: Sealed
|
||||||
|
volumes:
|
||||||
|
- ./authority.harness.yaml:/etc/authority.yaml:ro
|
||||||
|
- ./plugins:/app/plugins:ro
|
||||||
|
- ../../../certificates:/certificates:ro
|
||||||
|
ports:
|
||||||
|
- '5088:5088'
|
||||||
|
networks:
|
||||||
|
- sealed-ci
|
||||||
|
labels: *release-labels
|
||||||
|
|
||||||
|
signer:
|
||||||
|
image: registry.stella-ops.org/stellaops/signer@sha256:8bfef9a75783883d49fc18e3566553934e970b00ee090abee9cb110d2d5c3298
|
||||||
|
depends_on:
|
||||||
|
- authority
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_URLS: http://+:6088
|
||||||
|
SIGNER__AUTHORITY__BASEURL: http://authority:5088
|
||||||
|
SIGNER__POE__INTROSPECTURL: http://authority:5088/device-code
|
||||||
|
SIGNER__STORAGE__MONGO__CONNECTIONSTRING: mongodb://sealedci:sealedci-secret@mongo:27017/signer?authSource=admin
|
||||||
|
SIGNER__SEALED__MODE: Enabled
|
||||||
|
ports:
|
||||||
|
- '6088:6088'
|
||||||
|
networks:
|
||||||
|
- sealed-ci
|
||||||
|
labels: *release-labels
|
||||||
|
|
||||||
|
attestor:
|
||||||
|
image: registry.stella-ops.org/stellaops/attestor@sha256:5cc417948c029da01dccf36e4645d961a3f6d8de7e62fe98d845f07cd2282114
|
||||||
|
depends_on:
|
||||||
|
- signer
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ASPNETCORE_URLS: http://+:7088
|
||||||
|
ATTESTOR__SIGNER__BASEURL: http://signer:6088
|
||||||
|
ATTESTOR__MONGO__CONNECTIONSTRING: mongodb://sealedci:sealedci-secret@mongo:27017/attestor?authSource=admin
|
||||||
|
ATTESTOR__SEALED__MODE: Enabled
|
||||||
|
ports:
|
||||||
|
- '7088:7088'
|
||||||
|
networks:
|
||||||
|
- sealed-ci
|
||||||
|
labels: *release-labels
|
||||||
57
scripts/crypto/package-rootpack-ru.sh
Normal file
57
scripts/crypto/package-rootpack-ru.sh
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||||
|
TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
OUTPUT_ROOT="${1:-${ROOT_DIR}/build/rootpack_ru_${TIMESTAMP}}"
|
||||||
|
ARTIFACT_DIR="${OUTPUT_ROOT}/artifacts"
|
||||||
|
DOC_DIR="${OUTPUT_ROOT}/docs"
|
||||||
|
CONFIG_DIR="${OUTPUT_ROOT}/config"
|
||||||
|
TRUST_DIR="${OUTPUT_ROOT}/trust"
|
||||||
|
|
||||||
|
mkdir -p "$ARTIFACT_DIR" "$DOC_DIR" "$CONFIG_DIR" "$TRUST_DIR"
|
||||||
|
|
||||||
|
publish_plugin() {
|
||||||
|
local project="$1"
|
||||||
|
local name="$2"
|
||||||
|
local publish_dir="${ARTIFACT_DIR}/${name}"
|
||||||
|
echo "[rootpack-ru] Publishing ${project} -> ${publish_dir}"
|
||||||
|
dotnet publish "$project" -c Release -o "$publish_dir" --nologo >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
publish_plugin "src/__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/StellaOps.Cryptography.Plugin.CryptoPro.csproj" "StellaOps.Cryptography.Plugin.CryptoPro"
|
||||||
|
publish_plugin "src/__Libraries/StellaOps.Cryptography.Plugin.Pkcs11Gost/StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" "StellaOps.Cryptography.Plugin.Pkcs11Gost"
|
||||||
|
|
||||||
|
cp docs/security/rootpack_ru_validation.md "$DOC_DIR/"
|
||||||
|
cp docs/security/crypto-routing-audit-2025-11-07.md "$DOC_DIR/"
|
||||||
|
cp docs/security/rootpack_ru_package.md "$DOC_DIR/"
|
||||||
|
cp etc/rootpack/ru/crypto.profile.yaml "$CONFIG_DIR/rootpack_ru.crypto.yaml"
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
for pem in "$ROOT_DIR"/certificates/russian_trusted_*; do
|
||||||
|
cp "$pem" "$TRUST_DIR/"
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
cat <<README >"${OUTPUT_ROOT}/README.txt"
|
||||||
|
RootPack_RU bundle (${TIMESTAMP})
|
||||||
|
--------------------------------
|
||||||
|
Contents:
|
||||||
|
- artifacts/ : Sovereign crypto plug-ins published for net10.0 (CryptoPro + PKCS#11)
|
||||||
|
- config/rootpack_ru.crypto.yaml : example configuration binding registry profiles
|
||||||
|
- docs/ : validation + audit documentation
|
||||||
|
- trust/ : Russian trust anchor PEM bundle copied from certificates/
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
1. Review docs/rootpack_ru_package.md for installation steps.
|
||||||
|
2. Execute scripts/crypto/run-rootpack-ru-tests.sh (or CI equivalent) and attach the logs to this bundle.
|
||||||
|
3. Record hardware validation outputs per docs/rootpack_ru_validation.md and store alongside this directory.
|
||||||
|
README
|
||||||
|
|
||||||
|
if [[ "${PACKAGE_TAR:-1}" != "0" ]]; then
|
||||||
|
tarball="${OUTPUT_ROOT}.tar.gz"
|
||||||
|
echo "[rootpack-ru] Creating ${tarball}"
|
||||||
|
tar -czf "$tarball" -C "$(dirname "$OUTPUT_ROOT")" "$(basename "$OUTPUT_ROOT")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[rootpack-ru] Bundle staged under $OUTPUT_ROOT"
|
||||||
51
scripts/crypto/run-rootpack-ru-tests.sh
Normal file
51
scripts/crypto/run-rootpack-ru-tests.sh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(git rev-parse --show-toplevel)"
|
||||||
|
DEFAULT_LOG_ROOT="${ROOT_DIR}/logs/rootpack_ru_$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
LOG_ROOT="${ROOTPACK_LOG_DIR:-$DEFAULT_LOG_ROOT}"
|
||||||
|
mkdir -p "$LOG_ROOT"
|
||||||
|
|
||||||
|
PROJECTS=(
|
||||||
|
"src/__Libraries/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj"
|
||||||
|
"src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/StellaOps.Scanner.Worker.Tests.csproj"
|
||||||
|
"src/Scanner/__Tests/StellaOps.Scanner.Sbomer.BuildXPlugin.Tests/StellaOps.Scanner.Sbomer.BuildXPlugin.Tests.csproj"
|
||||||
|
)
|
||||||
|
|
||||||
|
run_test() {
|
||||||
|
local project="$1"
|
||||||
|
local safe_name
|
||||||
|
safe_name="$(basename "${project%.csproj}")"
|
||||||
|
local log_file="${LOG_ROOT}/${safe_name}.log"
|
||||||
|
local trx_name="${safe_name}.trx"
|
||||||
|
|
||||||
|
echo "[rootpack-ru] Running tests for ${project}" | tee "$log_file"
|
||||||
|
dotnet test "$project" \
|
||||||
|
--nologo \
|
||||||
|
--verbosity minimal \
|
||||||
|
--results-directory "$LOG_ROOT" \
|
||||||
|
--logger "trx;LogFileName=${trx_name}" | tee -a "$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
PROJECT_SUMMARY=()
|
||||||
|
for project in "${PROJECTS[@]}"; do
|
||||||
|
run_test "$project"
|
||||||
|
safe_name="$(basename "${project%.csproj}")"
|
||||||
|
PROJECT_SUMMARY+=("$project|$safe_name")
|
||||||
|
echo "[rootpack-ru] Wrote logs for ${project} -> ${LOG_ROOT}/${safe_name}.log"
|
||||||
|
done
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "RootPack_RU deterministic test harness"
|
||||||
|
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
echo "Log Directory: $LOG_ROOT"
|
||||||
|
echo ""
|
||||||
|
echo "Projects:"
|
||||||
|
for entry in "${PROJECT_SUMMARY[@]}"; do
|
||||||
|
project_path="${entry%%|*}"
|
||||||
|
safe_name="${entry##*|}"
|
||||||
|
printf ' - %s (log: %s.log, trx: %s.trx)\n' "$project_path" "$safe_name" "$safe_name"
|
||||||
|
done
|
||||||
|
} > "$LOG_ROOT/README.tests"
|
||||||
|
|
||||||
|
echo "Logs and TRX files available under $LOG_ROOT"
|
||||||
@@ -24,7 +24,9 @@ public sealed record AocGuardOptions
|
|||||||
"createdAt",
|
"createdAt",
|
||||||
"created_at",
|
"created_at",
|
||||||
"ingestedAt",
|
"ingestedAt",
|
||||||
"ingested_at"
|
"ingested_at",
|
||||||
|
"links",
|
||||||
|
"advisory_key"
|
||||||
}, StringComparer.OrdinalIgnoreCase)
|
}, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
|
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,36 @@ public sealed class AocWriteGuardTests
|
|||||||
Assert.Empty(result.Violations);
|
Assert.Empty(result.Violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Validate_AllowsLinksAndAdvisoryKey_ByDefault()
|
||||||
|
{
|
||||||
|
using var document = JsonDocument.Parse("""
|
||||||
|
{
|
||||||
|
"tenant": "default",
|
||||||
|
"source": {"vendor": "osv"},
|
||||||
|
"upstream": {
|
||||||
|
"upstream_id": "GHSA-xxxx",
|
||||||
|
"content_hash": "sha256:abc",
|
||||||
|
"signature": { "present": false }
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"format": "OSV",
|
||||||
|
"raw": {"id": "GHSA-xxxx"}
|
||||||
|
},
|
||||||
|
"linkset": {},
|
||||||
|
"links": [
|
||||||
|
{ "scheme": "cve", "value": "CVE-2025-0001" }
|
||||||
|
],
|
||||||
|
"advisory_key": "ghsa-xxxx"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
var result = Guard.Validate(document.RootElement);
|
||||||
|
|
||||||
|
Assert.True(result.IsValid);
|
||||||
|
Assert.Empty(result.Violations);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Validate_FlagsMissingTenant()
|
public void Validate_FlagsMissingTenant()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
> Remark (2025-10-19): Wave 0 prerequisites reviewed (none outstanding); ATTESTOR-API-11-201, ATTESTOR-VERIFY-11-202, and ATTESTOR-OBS-11-203 tracked as DOING per Wave 0A kickoff.
|
> Remark (2025-10-19): Wave 0 prerequisites reviewed (none outstanding); ATTESTOR-API-11-201, ATTESTOR-VERIFY-11-202, and ATTESTOR-OBS-11-203 tracked as DOING per Wave 0A kickoff.
|
||||||
> Remark (2025-10-19): Dual-log submissions, signature/proof verification, and observability hardening landed; attestor endpoints now rate-limited per client with correlation-ID logging and updated docs/tests.
|
> Remark (2025-10-19): Dual-log submissions, signature/proof verification, and observability hardening landed; attestor endpoints now rate-limited per client with correlation-ID logging and updated docs/tests.
|
||||||
|
| ATTESTOR-CRYPTO-90-001 | TODO | Attestor Service Guild, Security Guild | SEC-CRYPTO-90-003, SEC-CRYPTO-90-004 | Migrate bundle hashing, witness proof caching, and signing submissions to `ICryptoProviderRegistry`/`ICryptoHash` so RootPack_RU deployments use CryptoPro or PKCS#11 per `docs/security/crypto-routing-audit-2025-11-07.md`. | Attestor services resolve registry providers; DSSE signing/verifying honors config profiles; tests cover default + `ru-offline` modes; docs updated. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
|----|--------|----------|------------|-------------|---------------|
|
|----|--------|----------|------------|-------------|---------------|
|
||||||
| SEC2.PLG | BLOCKED (2025-10-21) | Security Guild, Storage Guild | SEC2.A (audit contract) | Emit audit events from password verification outcomes and persist via `IAuthorityLoginAttemptStore`. <br>⛔ Waiting on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 to stabilise Authority auth surfaces before final verification + publish. | ✅ Serilog events enriched with subject/client/IP/outcome; ✅ Mongo records written per attempt; ✅ Tests assert success/lockout/failure cases. |
|
| SEC2.PLG | BLOCKED (2025-10-21) | Security Guild, Storage Guild | SEC2.A (audit contract) | Emit audit events from password verification outcomes and persist via `IAuthorityLoginAttemptStore`. <br>⛔ Waiting on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 to stabilise Authority auth surfaces (PLUGIN-DI-08-001 landed 2025-10-21). | ✅ Serilog events enriched with subject/client/IP/outcome; ✅ Mongo records written per attempt; ✅ Tests assert success/lockout/failure cases. |
|
||||||
| SEC3.PLG | BLOCKED (2025-10-21) | Security Guild, BE-Auth Plugin | CORE8, SEC3.A (rate limiter) | Ensure lockout responses and rate-limit metadata flow through plugin logs/events (include retry-after). <br>⛔ Pending AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 so limiter telemetry contract matches final authority surface. | ✅ Audit record includes retry-after; ✅ Tests confirm lockout + limiter interplay. |
|
| SEC3.PLG | BLOCKED (2025-10-21) | Security Guild, BE-Auth Plugin | CORE8, SEC3.A (rate limiter) | Ensure lockout responses and rate-limit metadata flow through plugin logs/events (include retry-after). <br>⛔ Pending AUTH-DPOP-11-001 / AUTH-MTLS-11-002; PLUGIN-DI-08-001 is done, limiter telemetry just awaits the updated Authority surface. | ✅ Audit record includes retry-after; ✅ Tests confirm lockout + limiter interplay. |
|
||||||
| SEC5.PLG | BLOCKED (2025-10-21) | Security Guild | SEC5.A (threat model) | Address plugin-specific mitigations (bootstrap user handling, password policy docs) in threat model backlog. <br>⛔ Final documentation depends on AUTH-DPOP-11-001 / AUTH-MTLS-11-002 / PLUGIN-DI-08-001 outcomes. | ✅ Threat model lists plugin attack surfaces; ✅ Mitigation items filed. |
|
| SEC5.PLG | BLOCKED (2025-10-21) | Security Guild | SEC5.A (threat model) | Address plugin-specific mitigations (bootstrap user handling, password policy docs) in threat model backlog. <br>⛔ Final documentation now hinges on AUTH-DPOP-11-001 / AUTH-MTLS-11-002; scoped DI work is complete. | ✅ Threat model lists plugin attack surfaces; ✅ Mitigation items filed. |
|
||||||
| PLG4-6.CAPABILITIES | BLOCKED (2025-10-12) | BE-Auth Plugin, Docs Guild | PLG1–PLG3 | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | ✅ Capability metadata + validation merged; ✅ Plugin guide updated with final copy & diagrams; ✅ Release notes mention new toggles. <br>⛔ Blocked awaiting Authority rate-limiter stream (CORE8/SEC3) to resume so doc updates reflect final limiter behaviour. |
|
| PLG4-6.CAPABILITIES | BLOCKED (2025-10-12) | BE-Auth Plugin, Docs Guild | PLG1–PLG3 | Finalise capability metadata exposure, config validation, and developer guide updates; remaining action is Docs polish/diagram export. | ✅ Capability metadata + validation merged; ✅ Plugin guide updated with final copy & diagrams; ✅ Release notes mention new toggles. <br>⛔ Blocked awaiting Authority rate-limiter stream (CORE8/SEC3) to resume so doc updates reflect final limiter behaviour. |
|
||||||
| PLG7.RFC | DONE (2025-11-03) | BE-Auth Plugin, Security Guild | PLG4 | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | ✅ Guild review sign-off recorded; ✅ Follow-up issues filed in module boards. |
|
| PLG7.RFC | DONE (2025-11-03) | BE-Auth Plugin, Security Guild | PLG4 | Socialize LDAP plugin RFC (`docs/rfcs/authority-plugin-ldap.md`) and capture guild feedback. | ✅ Guild review sign-off recorded; ✅ Follow-up issues filed in module boards. |
|
||||||
| PLG7.IMPL-001 | DONE (2025-11-03) | BE-Auth Plugin | PLG7.RFC | Scaffold `StellaOps.Authority.Plugin.Ldap` + tests, bind configuration (client certificate, trust-store, insecure toggle) with validation and docs samples. | ✅ Project + test harness build; ✅ Configuration bound & validated; ✅ Sample config updated. |
|
| PLG7.IMPL-001 | DONE (2025-11-03) | BE-Auth Plugin | PLG7.RFC | Scaffold `StellaOps.Authority.Plugin.Ldap` + tests, bind configuration (client certificate, trust-store, insecure toggle) with validation and docs samples. | ✅ Project + test harness build; ✅ Configuration bound & validated; ✅ Sample config updated. |
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using System;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using StellaOps.Cryptography;
|
||||||
|
|
||||||
namespace StellaOps.Authority.Plugins.Abstractions;
|
namespace StellaOps.Authority.Plugins.Abstractions;
|
||||||
|
|
||||||
@@ -8,18 +11,55 @@ namespace StellaOps.Authority.Plugins.Abstractions;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class AuthoritySecretHasher
|
public static class AuthoritySecretHasher
|
||||||
{
|
{
|
||||||
|
private static ICryptoHash? configuredHash;
|
||||||
|
private static string defaultAlgorithm = HashAlgorithms.Sha256;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes a stable SHA-256 hash for the provided secret.
|
/// Configures the shared crypto hash service used for secret hashing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ComputeHash(string secret)
|
public static void Configure(ICryptoHash hash, string? algorithmId = null)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(hash);
|
||||||
|
Volatile.Write(ref configuredHash, hash);
|
||||||
|
if (!string.IsNullOrWhiteSpace(algorithmId))
|
||||||
|
{
|
||||||
|
defaultAlgorithm = NormalizeAlgorithm(algorithmId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a stable hash for the provided secret using the configured crypto provider.
|
||||||
|
/// </summary>
|
||||||
|
public static string ComputeHash(string secret, string? algorithmId = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(secret))
|
if (string.IsNullOrEmpty(secret))
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var algorithm = string.IsNullOrWhiteSpace(algorithmId)
|
||||||
|
? defaultAlgorithm
|
||||||
|
: NormalizeAlgorithm(algorithmId);
|
||||||
|
|
||||||
|
var hasher = Volatile.Read(ref configuredHash);
|
||||||
|
if (hasher is not null)
|
||||||
|
{
|
||||||
|
var digest = hasher.ComputeHash(Encoding.UTF8.GetBytes(secret), algorithm);
|
||||||
|
return Convert.ToBase64String(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.Equals(algorithm, HashAlgorithms.Sha256, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Authority secret hasher is not configured for the requested algorithm.");
|
||||||
|
}
|
||||||
|
|
||||||
using var sha256 = SHA256.Create();
|
using var sha256 = SHA256.Create();
|
||||||
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(secret));
|
var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(secret));
|
||||||
return Convert.ToBase64String(bytes);
|
return Convert.ToBase64String(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string NormalizeAlgorithm(string algorithmId)
|
||||||
|
=> string.IsNullOrWhiteSpace(algorithmId)
|
||||||
|
? HashAlgorithms.Sha256
|
||||||
|
: algorithmId.Trim().ToUpperInvariant();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ public sealed class AuthorityTokenDocument
|
|||||||
[BsonIgnoreIfNull]
|
[BsonIgnoreIfNull]
|
||||||
public string? SenderKeyThumbprint { get; set; }
|
public string? SenderKeyThumbprint { get; set; }
|
||||||
|
|
||||||
|
[BsonElement("senderCertificateHex")]
|
||||||
|
[BsonIgnoreIfNull]
|
||||||
|
public string? SenderCertificateHex { get; set; }
|
||||||
|
|
||||||
[BsonElement("senderNonce")]
|
[BsonElement("senderNonce")]
|
||||||
[BsonIgnoreIfNull]
|
[BsonIgnoreIfNull]
|
||||||
public string? SenderNonce { get; set; }
|
public string? SenderNonce { get; set; }
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http.Json;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
@@ -192,6 +194,116 @@ public sealed class ConsoleEndpointsTests
|
|||||||
Assert.Equal(2, events.Count);
|
Assert.Equal(2, events.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task VulnerabilityFindings_ReturnsSamplePayload()
|
||||||
|
{
|
||||||
|
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-08T12: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.AdvisoryRead, StellaOpsScopes.VexRead },
|
||||||
|
expiresAt: timeProvider.GetUtcNow().AddMinutes(30));
|
||||||
|
|
||||||
|
var client = app.CreateTestClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||||
|
|
||||||
|
var response = await client.GetAsync("/console/vuln/findings?severity=high");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var items = json.RootElement.GetProperty("items");
|
||||||
|
Assert.True(items.GetArrayLength() >= 1);
|
||||||
|
Assert.Equal("CVE-2024-12345", items[0].GetProperty("coordinates").GetProperty("advisoryId").GetString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task VulnerabilityFindingDetail_ReturnsExpandedDocument()
|
||||||
|
{
|
||||||
|
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-08T12: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.AdvisoryRead, StellaOpsScopes.VexRead },
|
||||||
|
expiresAt: timeProvider.GetUtcNow().AddMinutes(30));
|
||||||
|
|
||||||
|
var client = app.CreateTestClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||||
|
|
||||||
|
var response = await client.GetAsync("/console/vuln/tenant-default:advisory-ai:sha256:5d1a");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var summary = json.RootElement.GetProperty("summary");
|
||||||
|
Assert.Equal("tenant-default:advisory-ai:sha256:5d1a", summary.GetProperty("findingId").GetString());
|
||||||
|
Assert.Equal("reachable", summary.GetProperty("reachability").GetProperty("status").GetString());
|
||||||
|
var detailReachability = json.RootElement.GetProperty("reachability");
|
||||||
|
Assert.Equal("reachable", detailReachability.GetProperty("status").GetString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task VulnerabilityTicket_ReturnsDeterministicPayload()
|
||||||
|
{
|
||||||
|
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-08T12: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.AdvisoryRead, StellaOpsScopes.VexRead },
|
||||||
|
expiresAt: timeProvider.GetUtcNow().AddMinutes(30));
|
||||||
|
|
||||||
|
var client = app.CreateTestClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||||
|
|
||||||
|
var payload = new ConsoleVulnerabilityTicketRequest(
|
||||||
|
Selection: new[] { "tenant-default:advisory-ai:sha256:5d1a" },
|
||||||
|
TargetSystem: "servicenow",
|
||||||
|
Metadata: new Dictionary<string, string> { ["assignmentGroup"] = "runtime-security" });
|
||||||
|
|
||||||
|
var response = await client.PostAsJsonAsync("/console/vuln/tickets", payload);
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
Assert.StartsWith("console-ticket::tenant-default::", json.RootElement.GetProperty("ticketId").GetString());
|
||||||
|
Assert.Equal("servicenow", payload.TargetSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task VexStatements_ReturnsSampleRows()
|
||||||
|
{
|
||||||
|
var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2025-11-08T12: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.VexRead },
|
||||||
|
expiresAt: timeProvider.GetUtcNow().AddMinutes(30));
|
||||||
|
|
||||||
|
var client = app.CreateTestClient();
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(TestAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
client.DefaultRequestHeaders.Add(AuthorityHttpHeaders.Tenant, "tenant-default");
|
||||||
|
|
||||||
|
var response = await client.GetAsync("/console/vex/statements?advisoryId=CVE-2024-12345");
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
|
||||||
|
using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var items = json.RootElement.GetProperty("items");
|
||||||
|
Assert.True(items.GetArrayLength() >= 1);
|
||||||
|
Assert.Equal("CVE-2024-12345", items[0].GetProperty("advisoryId").GetString());
|
||||||
|
}
|
||||||
|
|
||||||
private static ClaimsPrincipal CreatePrincipal(
|
private static ClaimsPrincipal CreatePrincipal(
|
||||||
string tenant,
|
string tenant,
|
||||||
IReadOnlyCollection<string> scopes,
|
IReadOnlyCollection<string> scopes,
|
||||||
@@ -262,6 +374,7 @@ public sealed class ConsoleEndpointsTests
|
|||||||
builder.Services.AddSingleton<TestPrincipalAccessor>();
|
builder.Services.AddSingleton<TestPrincipalAccessor>();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddSingleton<StellaOpsBypassEvaluator>();
|
builder.Services.AddSingleton<StellaOpsBypassEvaluator>();
|
||||||
|
builder.Services.AddSingleton<IConsoleWorkspaceService, ConsoleWorkspaceSampleService>();
|
||||||
|
|
||||||
var authBuilder = builder.Services.AddAuthentication(options =>
|
var authBuilder = builder.Services.AddAuthentication(options =>
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -186,6 +186,7 @@ public sealed class TokenPersistenceIntegrationTests
|
|||||||
auditSink,
|
auditSink,
|
||||||
clock,
|
clock,
|
||||||
TestActivitySource,
|
TestActivitySource,
|
||||||
|
TestInstruments.Meter,
|
||||||
NullLogger<ValidateAccessTokenHandler>.Instance);
|
NullLogger<ValidateAccessTokenHandler>.Instance);
|
||||||
|
|
||||||
var transaction = new OpenIddictServerTransaction
|
var transaction = new OpenIddictServerTransaction
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ public sealed class AuthorityJwksServiceTests
|
|||||||
var registry = new TestRegistry(provider);
|
var registry = new TestRegistry(provider);
|
||||||
using var cache = new MemoryCache(new MemoryCacheOptions());
|
using var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-30T12:00:00Z"));
|
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-30T12:00:00Z"));
|
||||||
|
var hash = CryptoHashFactory.CreateDefault();
|
||||||
var service = new AuthorityJwksService(
|
var service = new AuthorityJwksService(
|
||||||
registry,
|
registry,
|
||||||
|
hash,
|
||||||
NullLogger<AuthorityJwksService>.Instance,
|
NullLogger<AuthorityJwksService>.Instance,
|
||||||
cache,
|
cache,
|
||||||
clock,
|
clock,
|
||||||
@@ -64,8 +66,10 @@ public sealed class AuthorityJwksServiceTests
|
|||||||
var registry = new TestRegistry(provider);
|
var registry = new TestRegistry(provider);
|
||||||
using var cache = new MemoryCache(new MemoryCacheOptions());
|
using var cache = new MemoryCache(new MemoryCacheOptions());
|
||||||
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-30T12:00:00Z"));
|
var clock = new FakeTimeProvider(DateTimeOffset.Parse("2025-10-30T12:00:00Z"));
|
||||||
|
var hash = CryptoHashFactory.CreateDefault();
|
||||||
var service = new AuthorityJwksService(
|
var service = new AuthorityJwksService(
|
||||||
registry,
|
registry,
|
||||||
|
hash,
|
||||||
NullLogger<AuthorityJwksService>.Instance,
|
NullLogger<AuthorityJwksService>.Instance,
|
||||||
cache,
|
cache,
|
||||||
clock,
|
clock,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public sealed class KmsAuthoritySigningKeySourceTests
|
|||||||
var signingKey = source.Load(request);
|
var signingKey = source.Load(request);
|
||||||
|
|
||||||
Assert.Equal(CryptoSigningKeyKind.Raw, signingKey.Kind);
|
Assert.Equal(CryptoSigningKeyKind.Raw, signingKey.Kind);
|
||||||
Assert.Equal(material.KeyId, signingKey.Reference.KeyId);
|
Assert.Equal(request.KeyId, signingKey.Reference.KeyId);
|
||||||
Assert.True(signingKey.PrivateKey.Length > 0);
|
Assert.True(signingKey.PrivateKey.Length > 0);
|
||||||
Assert.True(signingKey.PublicKey.Length > 0);
|
Assert.True(signingKey.PublicKey.Length > 0);
|
||||||
Assert.Equal(material.VersionId, signingKey.Metadata[KmsAuthoritySigningKeySource.KmsMetadataKeys.Version]);
|
Assert.Equal(material.VersionId, signingKey.Metadata[KmsAuthoritySigningKeySource.KmsMetadataKeys.Version]);
|
||||||
|
|||||||
@@ -41,6 +41,37 @@ internal static class ConsoleEndpointExtensions
|
|||||||
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.UiRead))
|
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(StellaOpsScopes.UiRead))
|
||||||
.WithName("ConsoleTokenIntrospect")
|
.WithName("ConsoleTokenIntrospect")
|
||||||
.WithSummary("Introspect the current access token and return expiry, scope, and tenant metadata.");
|
.WithSummary("Introspect the current access token and return expiry, scope, and tenant metadata.");
|
||||||
|
|
||||||
|
var vulnGroup = group.MapGroup("/vuln")
|
||||||
|
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(
|
||||||
|
StellaOpsScopes.UiRead,
|
||||||
|
StellaOpsScopes.AdvisoryRead,
|
||||||
|
StellaOpsScopes.VexRead));
|
||||||
|
|
||||||
|
vulnGroup.MapGet("/findings", GetVulnerabilityFindings)
|
||||||
|
.WithName("ConsoleVulnerabilityFindings")
|
||||||
|
.WithSummary("List tenant-scoped vulnerability findings with policy/VEX metadata.");
|
||||||
|
|
||||||
|
vulnGroup.MapGet("/{findingId}", GetVulnerabilityFindingById)
|
||||||
|
.WithName("ConsoleVulnerabilityFindingDetail")
|
||||||
|
.WithSummary("Return the full finding document, including evidence and policy overlays.");
|
||||||
|
|
||||||
|
vulnGroup.MapPost("/tickets", CreateVulnerabilityTicket)
|
||||||
|
.WithName("ConsoleVulnerabilityTickets")
|
||||||
|
.WithSummary("Generate a signed payload payload for external ticketing workflows.");
|
||||||
|
|
||||||
|
var vexGroup = group.MapGroup("/vex")
|
||||||
|
.RequireAuthorization(policy => policy.RequireStellaOpsScopes(
|
||||||
|
StellaOpsScopes.UiRead,
|
||||||
|
StellaOpsScopes.VexRead));
|
||||||
|
|
||||||
|
vexGroup.MapGet("/statements", GetVexStatements)
|
||||||
|
.WithName("ConsoleVexStatements")
|
||||||
|
.WithSummary("List VEX statements impacting the tenant.");
|
||||||
|
|
||||||
|
vexGroup.MapGet("/events", StreamVexEvents)
|
||||||
|
.WithName("ConsoleVexEvents")
|
||||||
|
.WithSummary("Server-sent events feed for live VEX updates (placeholder).");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<IResult> GetTenants(
|
private static async Task<IResult> GetTenants(
|
||||||
@@ -168,6 +199,199 @@ internal static class ConsoleEndpointExtensions
|
|||||||
return Results.Ok(introspection);
|
return Results.Ok(introspection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetVulnerabilityFindings(
|
||||||
|
HttpContext httpContext,
|
||||||
|
IConsoleWorkspaceService workspaceService,
|
||||||
|
TimeProvider timeProvider,
|
||||||
|
IAuthEventSink auditSink,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(httpContext);
|
||||||
|
ArgumentNullException.ThrowIfNull(workspaceService);
|
||||||
|
|
||||||
|
var tenant = TenantHeaderFilter.GetTenant(httpContext);
|
||||||
|
if (string.IsNullOrWhiteSpace(tenant))
|
||||||
|
{
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.findings",
|
||||||
|
AuthEventOutcome.Failure,
|
||||||
|
"tenant_header_missing",
|
||||||
|
BuildProperties(("tenant.header", null)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.BadRequest(new { error = "tenant_header_missing", message = $"Header '{AuthorityHttpHeaders.Tenant}' is required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = BuildVulnerabilityQuery(httpContext.Request);
|
||||||
|
var response = await workspaceService.SearchFindingsAsync(tenant, query, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.findings",
|
||||||
|
AuthEventOutcome.Success,
|
||||||
|
null,
|
||||||
|
BuildProperties(("tenant.resolved", tenant), ("pagination.next_token", response.NextPageToken)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetVulnerabilityFindingById(
|
||||||
|
HttpContext httpContext,
|
||||||
|
string findingId,
|
||||||
|
IConsoleWorkspaceService workspaceService,
|
||||||
|
TimeProvider timeProvider,
|
||||||
|
IAuthEventSink auditSink,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(httpContext);
|
||||||
|
ArgumentNullException.ThrowIfNull(workspaceService);
|
||||||
|
|
||||||
|
var tenant = TenantHeaderFilter.GetTenant(httpContext);
|
||||||
|
if (string.IsNullOrWhiteSpace(tenant))
|
||||||
|
{
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.finding",
|
||||||
|
AuthEventOutcome.Failure,
|
||||||
|
"tenant_header_missing",
|
||||||
|
BuildProperties(("tenant.header", null)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.BadRequest(new { error = "tenant_header_missing", message = $"Header '{AuthorityHttpHeaders.Tenant}' is required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var detail = await workspaceService.GetFindingAsync(tenant, findingId, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (detail is null)
|
||||||
|
{
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.finding",
|
||||||
|
AuthEventOutcome.Failure,
|
||||||
|
"finding_not_found",
|
||||||
|
BuildProperties(("tenant.resolved", tenant), ("finding.id", findingId)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.NotFound(new { error = "finding_not_found", message = $"Finding '{findingId}' not found." });
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.finding",
|
||||||
|
AuthEventOutcome.Success,
|
||||||
|
null,
|
||||||
|
BuildProperties(("tenant.resolved", tenant), ("finding.id", findingId)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.Ok(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> CreateVulnerabilityTicket(
|
||||||
|
HttpContext httpContext,
|
||||||
|
ConsoleVulnerabilityTicketRequest request,
|
||||||
|
IConsoleWorkspaceService workspaceService,
|
||||||
|
TimeProvider timeProvider,
|
||||||
|
IAuthEventSink auditSink,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(httpContext);
|
||||||
|
ArgumentNullException.ThrowIfNull(workspaceService);
|
||||||
|
|
||||||
|
if (request is null || request.Selection.Count == 0)
|
||||||
|
{
|
||||||
|
return Results.BadRequest(new { error = "invalid_request", message = "At least one finding must be selected." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenant = TenantHeaderFilter.GetTenant(httpContext);
|
||||||
|
if (string.IsNullOrWhiteSpace(tenant))
|
||||||
|
{
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.ticket",
|
||||||
|
AuthEventOutcome.Failure,
|
||||||
|
"tenant_header_missing",
|
||||||
|
BuildProperties(("tenant.header", null)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.BadRequest(new { error = "tenant_header_missing", message = $"Header '{AuthorityHttpHeaders.Tenant}' is required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var ticket = await workspaceService.CreateTicketAsync(tenant, request, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vuln.ticket",
|
||||||
|
AuthEventOutcome.Success,
|
||||||
|
null,
|
||||||
|
BuildProperties(
|
||||||
|
("tenant.resolved", tenant),
|
||||||
|
("ticket.id", ticket.TicketId),
|
||||||
|
("ticket.selection.count", request.Selection.Count.ToString(CultureInfo.InvariantCulture))),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.Ok(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<IResult> GetVexStatements(
|
||||||
|
HttpContext httpContext,
|
||||||
|
IConsoleWorkspaceService workspaceService,
|
||||||
|
TimeProvider timeProvider,
|
||||||
|
IAuthEventSink auditSink,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(httpContext);
|
||||||
|
ArgumentNullException.ThrowIfNull(workspaceService);
|
||||||
|
|
||||||
|
var tenant = TenantHeaderFilter.GetTenant(httpContext);
|
||||||
|
if (string.IsNullOrWhiteSpace(tenant))
|
||||||
|
{
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vex.statements",
|
||||||
|
AuthEventOutcome.Failure,
|
||||||
|
"tenant_header_missing",
|
||||||
|
BuildProperties(("tenant.header", null)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.BadRequest(new { error = "tenant_header_missing", message = $"Header '{AuthorityHttpHeaders.Tenant}' is required." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = BuildVexQuery(httpContext.Request);
|
||||||
|
var response = await workspaceService.GetVexStatementsAsync(tenant, query, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await WriteAuditAsync(
|
||||||
|
httpContext,
|
||||||
|
auditSink,
|
||||||
|
timeProvider,
|
||||||
|
"authority.console.vex.statements",
|
||||||
|
AuthEventOutcome.Success,
|
||||||
|
null,
|
||||||
|
BuildProperties(("tenant.resolved", tenant), ("pagination.next_token", response.NextPageToken)),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return Results.Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IResult StreamVexEvents() =>
|
||||||
|
Results.StatusCode(StatusCodes.Status501NotImplemented);
|
||||||
|
|
||||||
private static ConsoleProfileResponse BuildProfile(ClaimsPrincipal principal, TimeProvider timeProvider)
|
private static ConsoleProfileResponse BuildProfile(ClaimsPrincipal principal, TimeProvider timeProvider)
|
||||||
{
|
{
|
||||||
var tenant = Normalize(principal.FindFirstValue(StellaOpsClaimTypes.Tenant)) ?? string.Empty;
|
var tenant = Normalize(principal.FindFirstValue(StellaOpsClaimTypes.Tenant)) ?? string.Empty;
|
||||||
@@ -258,6 +482,64 @@ internal static class ConsoleEndpointExtensions
|
|||||||
return authTime.Value.AddSeconds(defaultFreshAuthWindowSeconds) > now;
|
return authTime.Value.AddSeconds(defaultFreshAuthWindowSeconds) > now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ConsoleVulnerabilityQuery BuildVulnerabilityQuery(HttpRequest request)
|
||||||
|
{
|
||||||
|
var builder = new ConsoleVulnerabilityQueryBuilder()
|
||||||
|
.SetPageSize(ParseInt(request.Query["pageSize"], 50))
|
||||||
|
.SetPageToken(request.Query.TryGetValue("pageToken", out var tokenValues) ? tokenValues.FirstOrDefault() : null)
|
||||||
|
.AddSeverity(ReadMulti(request, "severity"))
|
||||||
|
.AddPolicyBadges(ReadMulti(request, "policyBadge"))
|
||||||
|
.AddReachability(ReadMulti(request, "reachability"))
|
||||||
|
.AddProducts(ReadMulti(request, "product"))
|
||||||
|
.AddVexStates(ReadMulti(request, "vexState"));
|
||||||
|
|
||||||
|
var search = request.Query.TryGetValue("search", out var searchValues)
|
||||||
|
? searchValues
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.SelectMany(value => value!.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
|
: Array.Empty<string>();
|
||||||
|
|
||||||
|
builder.AddSearchTerms(search);
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConsoleVexQuery BuildVexQuery(HttpRequest request)
|
||||||
|
{
|
||||||
|
var builder = new ConsoleVexQueryBuilder()
|
||||||
|
.SetPageSize(ParseInt(request.Query["pageSize"], 50))
|
||||||
|
.SetPageToken(request.Query.TryGetValue("pageToken", out var pageValues) ? pageValues.FirstOrDefault() : null)
|
||||||
|
.AddAdvisories(ReadMulti(request, "advisoryId"))
|
||||||
|
.AddTypes(ReadMulti(request, "statementType"))
|
||||||
|
.AddStates(ReadMulti(request, "state"));
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> ReadMulti(HttpRequest request, string key)
|
||||||
|
{
|
||||||
|
if (!request.Query.TryGetValue(key, out var values))
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.SelectMany(value => value!.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
|
.Where(value => value.Length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ParseInt(StringValues values, int fallback)
|
||||||
|
{
|
||||||
|
if (values.Count == 0)
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return int.TryParse(values[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out var number)
|
||||||
|
? number
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<string> ExtractRoles(ClaimsPrincipal principal)
|
private static IReadOnlyList<string> ExtractRoles(ClaimsPrincipal principal)
|
||||||
{
|
{
|
||||||
var roles = principal.FindAll(OpenIddictConstants.Claims.Role)
|
var roles = principal.FindAll(OpenIddictConstants.Claims.Role)
|
||||||
|
|||||||
@@ -0,0 +1,304 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
|
namespace StellaOps.Authority.Console;
|
||||||
|
|
||||||
|
internal sealed record ConsoleFacetBucket(string Value, int Count);
|
||||||
|
|
||||||
|
internal sealed record ConsoleFacetDistribution(
|
||||||
|
IReadOnlyList<ConsoleFacetBucket> Severity,
|
||||||
|
IReadOnlyList<ConsoleFacetBucket> PolicyBadge,
|
||||||
|
IReadOnlyList<ConsoleFacetBucket> Reachability);
|
||||||
|
|
||||||
|
internal sealed record ConsoleFindingTimestamps(
|
||||||
|
DateTimeOffset FirstSeen,
|
||||||
|
DateTimeOffset LastSeen,
|
||||||
|
DateTimeOffset? VexLastUpdated);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnCoordinates(
|
||||||
|
string AdvisoryId,
|
||||||
|
string Package,
|
||||||
|
string Component,
|
||||||
|
string Image);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnVexSummary(
|
||||||
|
string StatementId,
|
||||||
|
string State,
|
||||||
|
string? Justification);
|
||||||
|
|
||||||
|
internal sealed record ConsoleReachabilitySummary(
|
||||||
|
string Status,
|
||||||
|
DateTimeOffset? LastObserved,
|
||||||
|
string? SignalsVersion);
|
||||||
|
|
||||||
|
internal sealed record ConsoleReachabilityDetail(
|
||||||
|
string Status,
|
||||||
|
IReadOnlyList<string> CallPathSamples,
|
||||||
|
DateTimeOffset? LastObserved,
|
||||||
|
string? SignalsVersion);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnEvidenceSummary(
|
||||||
|
string? SbomDigest,
|
||||||
|
string? PolicyRunId,
|
||||||
|
string? AttestationId);
|
||||||
|
|
||||||
|
internal sealed record ConsoleEvidenceDetail(
|
||||||
|
ConsoleVulnEvidenceSummary Summary,
|
||||||
|
IReadOnlyList<string> ComponentPath,
|
||||||
|
IReadOnlyList<ConsoleAttestationReference> Attestations);
|
||||||
|
|
||||||
|
internal sealed record ConsoleAttestationReference(
|
||||||
|
string Type,
|
||||||
|
string AttestationId,
|
||||||
|
string Signer,
|
||||||
|
string BundleDigest);
|
||||||
|
|
||||||
|
internal sealed record ConsolePolicyBadge(
|
||||||
|
string PolicyId,
|
||||||
|
string Verdict,
|
||||||
|
string? ExplainUrl);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnVexDetail(
|
||||||
|
string StatementId,
|
||||||
|
string State,
|
||||||
|
string Justification,
|
||||||
|
string? ImpactStatement,
|
||||||
|
IReadOnlyList<ConsoleRemediation> Remediations);
|
||||||
|
|
||||||
|
internal sealed record ConsoleRemediation(
|
||||||
|
string Type,
|
||||||
|
string Description,
|
||||||
|
DateTimeOffset? Deadline);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilityFinding(
|
||||||
|
string Tenant,
|
||||||
|
string FindingId,
|
||||||
|
ConsoleVulnCoordinates Coordinates,
|
||||||
|
string Summary,
|
||||||
|
string Severity,
|
||||||
|
double? Cvss,
|
||||||
|
bool Kev,
|
||||||
|
string PolicyBadge,
|
||||||
|
ConsoleVulnVexSummary? Vex,
|
||||||
|
ConsoleReachabilitySummary? Reachability,
|
||||||
|
ConsoleVulnEvidenceSummary? Evidence,
|
||||||
|
ConsoleFindingTimestamps Timestamps);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilityFindingDetail(
|
||||||
|
ConsoleVulnerabilityFinding Summary,
|
||||||
|
string Description,
|
||||||
|
IReadOnlyList<string> References,
|
||||||
|
IReadOnlyList<ConsolePolicyBadge> PolicyBadges,
|
||||||
|
ConsoleVulnVexDetail? Vex,
|
||||||
|
ConsoleReachabilityDetail? Reachability,
|
||||||
|
ConsoleEvidenceDetail Evidence);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilitySearchResponse(
|
||||||
|
IReadOnlyList<ConsoleVulnerabilityFinding> Items,
|
||||||
|
ConsoleFacetDistribution Facets,
|
||||||
|
string? NextPageToken);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilityQuery(
|
||||||
|
IReadOnlyList<string> Severity,
|
||||||
|
IReadOnlyList<string> PolicyBadges,
|
||||||
|
IReadOnlyList<string> Reachability,
|
||||||
|
IReadOnlyList<string> Products,
|
||||||
|
IReadOnlyList<string> VexStates,
|
||||||
|
IReadOnlyList<string> SearchTerms,
|
||||||
|
int PageSize,
|
||||||
|
string? PageToken);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilityTicketRequest(
|
||||||
|
IReadOnlyList<string> Selection,
|
||||||
|
string TargetSystem,
|
||||||
|
IReadOnlyDictionary<string, string>? Metadata);
|
||||||
|
|
||||||
|
internal sealed record ConsoleTicketPayload(
|
||||||
|
string Version,
|
||||||
|
string Tenant,
|
||||||
|
IReadOnlyList<ConsoleTicketSelection> Findings,
|
||||||
|
string PolicyBadge,
|
||||||
|
string VexSummary,
|
||||||
|
IReadOnlyList<ConsoleTicketAttachment> Attachments);
|
||||||
|
|
||||||
|
internal sealed record ConsoleTicketSelection(string FindingId, string Severity);
|
||||||
|
|
||||||
|
internal sealed record ConsoleTicketAttachment(
|
||||||
|
string Type,
|
||||||
|
string Name,
|
||||||
|
string Digest,
|
||||||
|
string ContentType,
|
||||||
|
DateTimeOffset ExpiresAt);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVulnerabilityTicketResponse(
|
||||||
|
string TicketId,
|
||||||
|
ConsoleTicketPayload Payload,
|
||||||
|
string AuditEventId);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVexStatementSummary(
|
||||||
|
string Tenant,
|
||||||
|
string StatementId,
|
||||||
|
string AdvisoryId,
|
||||||
|
string Product,
|
||||||
|
string State,
|
||||||
|
string Justification,
|
||||||
|
string StatementType,
|
||||||
|
DateTimeOffset LastUpdated,
|
||||||
|
ConsoleVexSourceMetadata Source);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVexSourceMetadata(
|
||||||
|
string Type,
|
||||||
|
string? ModelBuild,
|
||||||
|
double? Confidence);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVexStatementPage(
|
||||||
|
IReadOnlyList<ConsoleVexStatementSummary> Items,
|
||||||
|
string? NextPageToken);
|
||||||
|
|
||||||
|
internal sealed record ConsoleVexQuery(
|
||||||
|
IReadOnlyList<string> AdvisoryIds,
|
||||||
|
IReadOnlyList<string> StatementTypes,
|
||||||
|
IReadOnlyList<string> States,
|
||||||
|
int PageSize,
|
||||||
|
string? PageToken);
|
||||||
|
|
||||||
|
internal interface IConsoleWorkspaceService
|
||||||
|
{
|
||||||
|
Task<ConsoleVulnerabilitySearchResponse> SearchFindingsAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVulnerabilityQuery query,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<ConsoleVulnerabilityFindingDetail?> GetFindingAsync(
|
||||||
|
string tenant,
|
||||||
|
string findingId,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<ConsoleVulnerabilityTicketResponse> CreateTicketAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVulnerabilityTicketRequest request,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<ConsoleVexStatementPage> GetVexStatementsAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVexQuery query,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ConsoleVulnerabilityQueryBuilder
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _severity = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _policy = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _reachability = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _products = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _vexStates = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _searchTerms = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private int _pageSize = 50;
|
||||||
|
private string? _pageToken;
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder SetPageSize(int value)
|
||||||
|
{
|
||||||
|
_pageSize = Math.Clamp(value, 1, 200);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder SetPageToken(string? token)
|
||||||
|
{
|
||||||
|
_pageToken = string.IsNullOrWhiteSpace(token) ? null : token;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddSeverity(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_severity.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddPolicyBadges(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_policy.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddReachability(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_reachability.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddProducts(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_products.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddVexStates(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_vexStates.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQueryBuilder AddSearchTerms(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_searchTerms.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVulnerabilityQuery Build() =>
|
||||||
|
new(
|
||||||
|
_severity.ToImmutableArray(),
|
||||||
|
_policy.ToImmutableArray(),
|
||||||
|
_reachability.ToImmutableArray(),
|
||||||
|
_products.ToImmutableArray(),
|
||||||
|
_vexStates.ToImmutableArray(),
|
||||||
|
_searchTerms.ToImmutableArray(),
|
||||||
|
_pageSize,
|
||||||
|
_pageToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class ConsoleVexQueryBuilder
|
||||||
|
{
|
||||||
|
private readonly HashSet<string> _advisories = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _types = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly HashSet<string> _states = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private int _pageSize = 50;
|
||||||
|
private string? _pageToken;
|
||||||
|
|
||||||
|
public ConsoleVexQueryBuilder SetPageSize(int value)
|
||||||
|
{
|
||||||
|
_pageSize = Math.Clamp(value, 1, 200);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVexQueryBuilder SetPageToken(string? token)
|
||||||
|
{
|
||||||
|
_pageToken = string.IsNullOrWhiteSpace(token) ? null : token;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVexQueryBuilder AddAdvisories(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_advisories.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVexQueryBuilder AddTypes(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_types.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVexQueryBuilder AddStates(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
_states.UnionWith(values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleVexQuery Build() =>
|
||||||
|
new(
|
||||||
|
_advisories.ToImmutableArray(),
|
||||||
|
_types.ToImmutableArray(),
|
||||||
|
_states.ToImmutableArray(),
|
||||||
|
_pageSize,
|
||||||
|
_pageToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,364 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace StellaOps.Authority.Console;
|
||||||
|
|
||||||
|
internal sealed class ConsoleWorkspaceSampleService : IConsoleWorkspaceService
|
||||||
|
{
|
||||||
|
private static readonly ImmutableArray<ConsoleVulnerabilityFindingDetail> SampleFindings;
|
||||||
|
private static readonly ImmutableArray<ConsoleVexStatementSummary> SampleStatements;
|
||||||
|
|
||||||
|
static ConsoleWorkspaceSampleService()
|
||||||
|
{
|
||||||
|
var finding1Summary = new ConsoleVulnerabilityFinding(
|
||||||
|
Tenant: "tenant-default",
|
||||||
|
FindingId: "tenant-default:advisory-ai:sha256:5d1a",
|
||||||
|
Coordinates: new ConsoleVulnCoordinates(
|
||||||
|
AdvisoryId: "CVE-2024-12345",
|
||||||
|
Package: "pkg:npm/jsonwebtoken@9.0.2",
|
||||||
|
Component: "jwt-auth-service",
|
||||||
|
Image: "registry.local/ops/auth:2025.10.0"),
|
||||||
|
Summary: "jsonwebtoken <10.0.0 allows algorithm downgrade.",
|
||||||
|
Severity: "high",
|
||||||
|
Cvss: 8.1,
|
||||||
|
Kev: true,
|
||||||
|
PolicyBadge: "fail",
|
||||||
|
Vex: new ConsoleVulnVexSummary(
|
||||||
|
StatementId: "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
State: "under_investigation",
|
||||||
|
Justification: "Advisory AI flagged reachable path via Scheduler run 42."),
|
||||||
|
Reachability: new ConsoleReachabilitySummary(
|
||||||
|
Status: "reachable",
|
||||||
|
LastObserved: DateTimeOffset.Parse("2025-11-07T23:11:04Z"),
|
||||||
|
SignalsVersion: "signals-2025.310.1"),
|
||||||
|
Evidence: new ConsoleVulnEvidenceSummary(
|
||||||
|
SbomDigest: "sha256:6c81f2bbd8bd7336f197f3f68fba2f76d7287dd1a5e2a0f0e9f14f23f3c2f917",
|
||||||
|
PolicyRunId: "policy-run::2025-11-07::ca9f",
|
||||||
|
AttestationId: "dsse://authority/attest/84a2"),
|
||||||
|
Timestamps: new ConsoleFindingTimestamps(
|
||||||
|
FirstSeen: DateTimeOffset.Parse("2025-10-31T04:22:18Z"),
|
||||||
|
LastSeen: DateTimeOffset.Parse("2025-11-07T23:16:51Z"),
|
||||||
|
VexLastUpdated: DateTimeOffset.Parse("2025-11-07T23:10:09Z")));
|
||||||
|
|
||||||
|
var finding2Summary = new ConsoleVulnerabilityFinding(
|
||||||
|
Tenant: "tenant-default",
|
||||||
|
FindingId: "tenant-default:advisory-ai:sha256:9bf4",
|
||||||
|
Coordinates: new ConsoleVulnCoordinates(
|
||||||
|
AdvisoryId: "GHSA-xxxx-yyyy-zzzz",
|
||||||
|
Package: "pkg:docker/library/nginx@1.25.2",
|
||||||
|
Component: "ingress-gateway",
|
||||||
|
Image: "registry.local/ops/ingress:2025.09.1"),
|
||||||
|
Summary: "Heap overflow in nginx HTTP/3 parsing.",
|
||||||
|
Severity: "critical",
|
||||||
|
Cvss: 9.8,
|
||||||
|
Kev: false,
|
||||||
|
PolicyBadge: "warn",
|
||||||
|
Vex: new ConsoleVulnVexSummary(
|
||||||
|
StatementId: "vex:tenant-default:ingress:9bf4",
|
||||||
|
State: "not_affected",
|
||||||
|
Justification: "component_not_present"),
|
||||||
|
Reachability: new ConsoleReachabilitySummary(
|
||||||
|
Status: "unknown",
|
||||||
|
LastObserved: null,
|
||||||
|
SignalsVersion: "signals-2025.309.0"),
|
||||||
|
Evidence: new ConsoleVulnEvidenceSummary(
|
||||||
|
SbomDigest: "sha256:99f1e2a7aa0f7c970dcb6674244f0bfb5f37148e3ee09fd4f925d3358dea2239",
|
||||||
|
PolicyRunId: "policy-run::2025-11-06::b210",
|
||||||
|
AttestationId: "dsse://authority/attest/1d34"),
|
||||||
|
Timestamps: new ConsoleFindingTimestamps(
|
||||||
|
FirstSeen: DateTimeOffset.Parse("2025-10-29T18:03:11Z"),
|
||||||
|
LastSeen: DateTimeOffset.Parse("2025-11-07T10:45:03Z"),
|
||||||
|
VexLastUpdated: DateTimeOffset.Parse("2025-11-06T18:44:00Z")));
|
||||||
|
|
||||||
|
SampleFindings = ImmutableArray.Create(
|
||||||
|
new ConsoleVulnerabilityFindingDetail(
|
||||||
|
Summary: finding1Summary,
|
||||||
|
Description: "jsonwebtoken accepts untrusted algorithm overrides which allow downgrade attacks.",
|
||||||
|
References: new[]
|
||||||
|
{
|
||||||
|
"https://nvd.nist.gov/vuln/detail/CVE-2024-12345",
|
||||||
|
"https://github.com/auth0/node-jsonwebtoken/security/advisories/GHSA-45mw-4jw3-g2wg"
|
||||||
|
},
|
||||||
|
PolicyBadges: new[]
|
||||||
|
{
|
||||||
|
new ConsolePolicyBadge("policy://tenant-default/runtime-hardening", "fail", "https://console.local/policy/runs/policy-run::2025-11-07::ca9f")
|
||||||
|
},
|
||||||
|
Vex: new ConsoleVulnVexDetail(
|
||||||
|
StatementId: "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
State: "under_investigation",
|
||||||
|
Justification: "Runtime telemetry confirmed exploitation path.",
|
||||||
|
ImpactStatement: "Token exchange service remains exposed until patch 2025.11.2.",
|
||||||
|
Remediations: new[]
|
||||||
|
{
|
||||||
|
new ConsoleRemediation("patch", "Upgrade jwt-auth-service to 2025.11.2.", DateTimeOffset.Parse("2025-11-12T00:00:00Z"))
|
||||||
|
}),
|
||||||
|
Reachability: new ConsoleReachabilityDetail(
|
||||||
|
Status: "reachable",
|
||||||
|
CallPathSamples: new[]
|
||||||
|
{
|
||||||
|
"api-gateway -> jwt-auth-service -> jsonwebtoken.verify"
|
||||||
|
},
|
||||||
|
LastObserved: DateTimeOffset.Parse("2025-11-07T23:11:04Z"),
|
||||||
|
SignalsVersion: "signals-2025.310.1"),
|
||||||
|
Evidence: new ConsoleEvidenceDetail(
|
||||||
|
Summary: finding1Summary.Evidence!,
|
||||||
|
ComponentPath: new[]
|
||||||
|
{
|
||||||
|
"/src/jwt-auth/package.json",
|
||||||
|
"/src/jwt-auth/node_modules/jsonwebtoken"
|
||||||
|
},
|
||||||
|
Attestations: new[]
|
||||||
|
{
|
||||||
|
new ConsoleAttestationReference("scan-report", "dsse://authority/attest/84a2", "attestor@stella-ops.org", "sha256:e2bb5c7a0a8b2d16ff42e7f8decb4bb8be71ad0a1606dbc5d28be43675fbad32")
|
||||||
|
})),
|
||||||
|
new ConsoleVulnerabilityFindingDetail(
|
||||||
|
Summary: finding2Summary,
|
||||||
|
Description: "nginx HTTP/3 heap overflow affecting unpatched ingress nodes.",
|
||||||
|
References: new[]
|
||||||
|
{
|
||||||
|
"https://security.nginx.org/announcements/2024/http3-overflow",
|
||||||
|
},
|
||||||
|
PolicyBadges: new[]
|
||||||
|
{
|
||||||
|
new ConsolePolicyBadge("policy://tenant-default/network-hardening", "warn", null)
|
||||||
|
},
|
||||||
|
Vex: new ConsoleVulnVexDetail(
|
||||||
|
StatementId: "vex:tenant-default:ingress:9bf4",
|
||||||
|
State: "not_affected",
|
||||||
|
Justification: "component_not_present",
|
||||||
|
ImpactStatement: "HTTP/3 disabled on ingress, exposure limited.",
|
||||||
|
Remediations: Array.Empty<ConsoleRemediation>()),
|
||||||
|
Reachability: new ConsoleReachabilityDetail(
|
||||||
|
Status: "unknown",
|
||||||
|
CallPathSamples: Array.Empty<string>(),
|
||||||
|
LastObserved: null,
|
||||||
|
SignalsVersion: "signals-2025.309.0"),
|
||||||
|
Evidence: new ConsoleEvidenceDetail(
|
||||||
|
Summary: finding2Summary.Evidence!,
|
||||||
|
ComponentPath: new[]
|
||||||
|
{
|
||||||
|
"/charts/ingress/templates/deployment.yaml"
|
||||||
|
},
|
||||||
|
Attestations: new[]
|
||||||
|
{
|
||||||
|
new ConsoleAttestationReference("scan-report", "dsse://authority/attest/1d34", "attestor@stella-ops.org", "sha256:91e6dd2c1bbf9a4ac797e050d71bf7f1b958d1a0c27469364c44d8ed74bcb9dc")
|
||||||
|
})));
|
||||||
|
|
||||||
|
SampleStatements = ImmutableArray.Create(
|
||||||
|
new ConsoleVexStatementSummary(
|
||||||
|
Tenant: "tenant-default",
|
||||||
|
StatementId: "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
AdvisoryId: "CVE-2024-12345",
|
||||||
|
Product: "registry.local/ops/auth:2025.10.0",
|
||||||
|
State: "under_investigation",
|
||||||
|
Justification: "exploit_observed",
|
||||||
|
StatementType: "advisory_ai",
|
||||||
|
LastUpdated: DateTimeOffset.Parse("2025-11-07T23:10:09Z"),
|
||||||
|
Source: new ConsoleVexSourceMetadata("advisory_ai", "aiai-console-2025-10-28", 0.74)),
|
||||||
|
new ConsoleVexStatementSummary(
|
||||||
|
Tenant: "tenant-default",
|
||||||
|
StatementId: "vex:tenant-default:jwt-auth:5d1a",
|
||||||
|
AdvisoryId: "CVE-2024-12345",
|
||||||
|
Product: "registry.local/ops/auth:2025.10.0",
|
||||||
|
State: "fixed",
|
||||||
|
Justification: "solution_available",
|
||||||
|
StatementType: "advisory_ai",
|
||||||
|
LastUpdated: DateTimeOffset.Parse("2025-11-08T11:44:32Z"),
|
||||||
|
Source: new ConsoleVexSourceMetadata("advisory_ai", "aiai-console-2025-10-28", 0.92)),
|
||||||
|
new ConsoleVexStatementSummary(
|
||||||
|
Tenant: "tenant-default",
|
||||||
|
StatementId: "vex:tenant-default:ingress:9bf4",
|
||||||
|
AdvisoryId: "GHSA-xxxx-yyyy-zzzz",
|
||||||
|
Product: "registry.local/ops/ingress:2025.09.1",
|
||||||
|
State: "not_affected",
|
||||||
|
Justification: "component_not_present",
|
||||||
|
StatementType: "excitor",
|
||||||
|
LastUpdated: DateTimeOffset.Parse("2025-11-06T18:44:00Z"),
|
||||||
|
Source: new ConsoleVexSourceMetadata("excitor", null, null)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ConsoleVulnerabilitySearchResponse> SearchFindingsAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVulnerabilityQuery query,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var filtered = SampleFindings
|
||||||
|
.Where(detail => IsTenantMatch(tenant, detail.Summary))
|
||||||
|
.Where(detail => MatchesSeverity(detail, query))
|
||||||
|
.Where(detail => MatchesPolicy(detail, query))
|
||||||
|
.Where(detail => MatchesReachability(detail, query))
|
||||||
|
.Where(detail => MatchesSearch(detail, query))
|
||||||
|
.Take(query.PageSize)
|
||||||
|
.Select(detail => detail.Summary)
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
var facets = BuildFacets(tenant);
|
||||||
|
var response = new ConsoleVulnerabilitySearchResponse(filtered, facets, NextPageToken: null);
|
||||||
|
return Task.FromResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ConsoleVulnerabilityFindingDetail?> GetFindingAsync(
|
||||||
|
string tenant,
|
||||||
|
string findingId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var detail = SampleFindings.FirstOrDefault(f =>
|
||||||
|
string.Equals(f.Summary.FindingId, findingId, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
IsTenantMatch(tenant, f.Summary));
|
||||||
|
|
||||||
|
return Task.FromResult(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ConsoleVulnerabilityTicketResponse> CreateTicketAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVulnerabilityTicketRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
ImmutableArray<ConsoleTicketSelection> selection;
|
||||||
|
if (string.IsNullOrWhiteSpace(tenant))
|
||||||
|
{
|
||||||
|
selection = ImmutableArray<ConsoleTicketSelection>.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selection = request.Selection
|
||||||
|
.Select(id => SampleFindings.FirstOrDefault(f => string.Equals(f.Summary.FindingId, id, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
.Where(detail => detail is not null)
|
||||||
|
.Select(detail => new ConsoleTicketSelection(detail!.Summary.FindingId, detail.Summary.Severity))
|
||||||
|
.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var ticketId = BuildTicketId(tenant, request.Selection);
|
||||||
|
var attachmentName = $"console-ticket-{DateTimeOffset.UtcNow:yyyyMMdd}.json";
|
||||||
|
var payload = new ConsoleTicketPayload(
|
||||||
|
Version: "2025-11-01",
|
||||||
|
Tenant: tenant,
|
||||||
|
Findings: selection,
|
||||||
|
PolicyBadge: selection.Any(sel => string.Equals(sel.Severity, "critical", StringComparison.OrdinalIgnoreCase)) ? "fail" : "warn",
|
||||||
|
VexSummary: $"{selection.Length} findings included in ticket.",
|
||||||
|
Attachments: new[]
|
||||||
|
{
|
||||||
|
new ConsoleTicketAttachment(
|
||||||
|
Type: "json",
|
||||||
|
Name: attachmentName,
|
||||||
|
Digest: HashAttachmentName(attachmentName),
|
||||||
|
ContentType: "application/json",
|
||||||
|
ExpiresAt: DateTimeOffset.UtcNow.AddDays(7))
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = new ConsoleVulnerabilityTicketResponse(
|
||||||
|
TicketId: ticketId,
|
||||||
|
Payload: payload,
|
||||||
|
AuditEventId: $"{ticketId}::audit");
|
||||||
|
|
||||||
|
return Task.FromResult(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ConsoleVexStatementPage> GetVexStatementsAsync(
|
||||||
|
string tenant,
|
||||||
|
ConsoleVexQuery query,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var filtered = SampleStatements
|
||||||
|
.Where(statement => string.Equals(statement.Tenant, tenant, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Where(statement => MatchesAdvisory(statement, query))
|
||||||
|
.Where(statement => MatchesState(statement, query))
|
||||||
|
.Where(statement => MatchesType(statement, query))
|
||||||
|
.Take(query.PageSize)
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
var page = new ConsoleVexStatementPage(filtered, NextPageToken: null);
|
||||||
|
return Task.FromResult(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesSeverity(ConsoleVulnerabilityFindingDetail detail, ConsoleVulnerabilityQuery query) =>
|
||||||
|
query.Severity.Count == 0 ||
|
||||||
|
query.Severity.Any(sev => string.Equals(sev, detail.Summary.Severity, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool MatchesPolicy(ConsoleVulnerabilityFindingDetail detail, ConsoleVulnerabilityQuery query) =>
|
||||||
|
query.PolicyBadges.Count == 0 ||
|
||||||
|
query.PolicyBadges.Any(badge => string.Equals(badge, detail.Summary.PolicyBadge, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool MatchesReachability(ConsoleVulnerabilityFindingDetail detail, ConsoleVulnerabilityQuery query)
|
||||||
|
{
|
||||||
|
if (query.Reachability.Count == 0 || detail.Summary.Reachability is null)
|
||||||
|
{
|
||||||
|
return query.Reachability.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.Reachability.Any(state =>
|
||||||
|
string.Equals(state, detail.Summary.Reachability.Status, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesSearch(ConsoleVulnerabilityFindingDetail detail, ConsoleVulnerabilityQuery query)
|
||||||
|
{
|
||||||
|
if (query.SearchTerms.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query.SearchTerms.Any(term =>
|
||||||
|
Contains(term, detail.Summary.FindingId) ||
|
||||||
|
Contains(term, detail.Summary.Coordinates.AdvisoryId) ||
|
||||||
|
Contains(term, detail.Summary.Coordinates.Component));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesAdvisory(ConsoleVexStatementSummary summary, ConsoleVexQuery query) =>
|
||||||
|
query.AdvisoryIds.Count == 0 ||
|
||||||
|
query.AdvisoryIds.Any(advisory => string.Equals(advisory, summary.AdvisoryId, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool MatchesType(ConsoleVexStatementSummary summary, ConsoleVexQuery query) =>
|
||||||
|
query.StatementTypes.Count == 0 ||
|
||||||
|
query.StatementTypes.Any(type => string.Equals(type, summary.StatementType, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool MatchesState(ConsoleVexStatementSummary summary, ConsoleVexQuery query) =>
|
||||||
|
query.States.Count == 0 ||
|
||||||
|
query.States.Any(state => string.Equals(state, summary.State, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool Contains(string term, string value) =>
|
||||||
|
value?.IndexOf(term, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
|
||||||
|
private static bool IsTenantMatch(string tenant, ConsoleVulnerabilityFinding summary) =>
|
||||||
|
string.Equals(summary.Tenant, tenant, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static ConsoleFacetDistribution BuildFacets(string tenant)
|
||||||
|
{
|
||||||
|
var findings = SampleFindings.Where(detail => IsTenantMatch(tenant, detail.Summary)).Select(detail => detail.Summary);
|
||||||
|
return new ConsoleFacetDistribution(
|
||||||
|
Severity: AggregateFacet(findings, finding => finding.Severity),
|
||||||
|
PolicyBadge: AggregateFacet(findings, finding => finding.PolicyBadge),
|
||||||
|
Reachability: AggregateFacet(findings, finding => finding.Reachability?.Status ?? "unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<ConsoleFacetBucket> AggregateFacet(
|
||||||
|
IEnumerable<ConsoleVulnerabilityFinding> findings,
|
||||||
|
Func<ConsoleVulnerabilityFinding, string> selector) =>
|
||||||
|
findings
|
||||||
|
.GroupBy(selector, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderByDescending(group => group.Count())
|
||||||
|
.ThenBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(group => new ConsoleFacetBucket(group.Key ?? "unknown", group.Count()))
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
private static string BuildTicketId(string tenant, IEnumerable<string> selection)
|
||||||
|
{
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var joined = string.Join("|", selection.Order());
|
||||||
|
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(joined));
|
||||||
|
var prefix = Convert.ToHexString(hash[..8]).ToLowerInvariant();
|
||||||
|
return $"console-ticket::{tenant}::{prefix}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string HashAttachmentName(string name)
|
||||||
|
{
|
||||||
|
using var sha256 = SHA256.Create();
|
||||||
|
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(name));
|
||||||
|
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ using System.IO;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@@ -19,16 +19,22 @@ internal sealed class AuthorityOpenApiDocumentProvider
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
using YamlDotNet.Core;
|
using YamlDotNet.Core;
|
||||||
|
|
||||||
using YamlDotNet.RepresentationModel;
|
using YamlDotNet.RepresentationModel;
|
||||||
public AuthorityOpenApiDocumentProvider(IWebHostEnvironment environment, ILogger<AuthorityOpenApiDocumentProvider> logger)
|
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace StellaOps.Authority.OpenApi;
|
namespace StellaOps.Authority.OpenApi;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal sealed class AuthorityOpenApiDocumentProvider
|
internal sealed class AuthorityOpenApiDocumentProvider
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
private readonly string specificationPath;
|
private readonly string specificationPath;
|
||||||
|
|
||||||
@@ -278,11 +284,10 @@ internal sealed class AuthorityOpenApiDocumentProvider
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static string CreateStrongEtag(string jsonRepresentation)
|
var grants = new SortedSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(jsonRepresentation));
|
var scopes = new SortedSet<string>(StringComparer.Ordinal);
|
||||||
var hash = Convert.ToHexString(bytes).ToLowerInvariant();
|
|
||||||
return $"\"{hash}\"";
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var scheme in securitySchemes.Children.Values.OfType<YamlMappingNode>())
|
foreach (var scheme in securitySchemes.Children.Values.OfType<YamlMappingNode>())
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ internal static class AuthorityOpenIddictConstants
|
|||||||
internal const string DpopConsumedNonceProperty = "authority:dpop_nonce";
|
internal const string DpopConsumedNonceProperty = "authority:dpop_nonce";
|
||||||
internal const string ConfirmationClaimType = "cnf";
|
internal const string ConfirmationClaimType = "cnf";
|
||||||
internal const string SenderConstraintClaimType = "authority_sender_constraint";
|
internal const string SenderConstraintClaimType = "authority_sender_constraint";
|
||||||
|
internal const string SenderNonceClaimType = "authority_sender_nonce";
|
||||||
internal const string MtlsCertificateThumbprintProperty = "authority:mtls_thumbprint";
|
internal const string MtlsCertificateThumbprintProperty = "authority:mtls_thumbprint";
|
||||||
internal const string MtlsCertificateHexProperty = "authority:mtls_thumbprint_hex";
|
internal const string MtlsCertificateHexProperty = "authority:mtls_thumbprint_hex";
|
||||||
|
internal const string MtlsCertificateHexClaimType = "authority_sender_certificate_hex";
|
||||||
internal const string ClientTenantProperty = "authority:client_tenant";
|
internal const string ClientTenantProperty = "authority:client_tenant";
|
||||||
internal const string ClientProjectProperty = "authority:client_project";
|
internal const string ClientProjectProperty = "authority:client_project";
|
||||||
internal const string ClientAttributesProperty = "authority:client_attributes";
|
internal const string ClientAttributesProperty = "authority:client_attributes";
|
||||||
|
|||||||
@@ -1177,6 +1177,50 @@ internal sealed class ValidateClientCredentialsHandler : IOpenIddictServerHandle
|
|||||||
|
|
||||||
var extraProperties = new List<AuthEventProperty>();
|
var extraProperties = new List<AuthEventProperty>();
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.SenderConstraintProperty, out var auditSenderConstraintObj) &&
|
||||||
|
auditSenderConstraintObj is string auditSenderConstraint &&
|
||||||
|
!string.IsNullOrWhiteSpace(auditSenderConstraint))
|
||||||
|
{
|
||||||
|
extraProperties.Add(new AuthEventProperty
|
||||||
|
{
|
||||||
|
Name = "sender.constraint",
|
||||||
|
Value = ClassifiedString.Public(auditSenderConstraint)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.DpopKeyThumbprintProperty, out var auditDpopThumbprintObj) &&
|
||||||
|
auditDpopThumbprintObj is string auditDpopThumbprint &&
|
||||||
|
!string.IsNullOrWhiteSpace(auditDpopThumbprint))
|
||||||
|
{
|
||||||
|
extraProperties.Add(new AuthEventProperty
|
||||||
|
{
|
||||||
|
Name = "sender.dpop.jkt",
|
||||||
|
Value = ClassifiedString.Sensitive(auditDpopThumbprint)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateThumbprintProperty, out var auditMtlsThumbprintObj) &&
|
||||||
|
auditMtlsThumbprintObj is string auditMtlsThumbprint &&
|
||||||
|
!string.IsNullOrWhiteSpace(auditMtlsThumbprint))
|
||||||
|
{
|
||||||
|
extraProperties.Add(new AuthEventProperty
|
||||||
|
{
|
||||||
|
Name = "sender.mtls.x5t",
|
||||||
|
Value = ClassifiedString.Sensitive(auditMtlsThumbprint)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateHexProperty, out var auditMtlsHexObj) &&
|
||||||
|
auditMtlsHexObj is string auditMtlsHex &&
|
||||||
|
!string.IsNullOrWhiteSpace(auditMtlsHex))
|
||||||
|
{
|
||||||
|
extraProperties.Add(new AuthEventProperty
|
||||||
|
{
|
||||||
|
Name = "sender.mtls.x5t_hex",
|
||||||
|
Value = ClassifiedString.Sensitive(auditMtlsHex)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.OperatorReasonProperty, out var operatorReasonObj) &&
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.OperatorReasonProperty, out var operatorReasonObj) &&
|
||||||
operatorReasonObj is string operatorReason &&
|
operatorReasonObj is string operatorReason &&
|
||||||
!string.IsNullOrWhiteSpace(operatorReason))
|
!string.IsNullOrWhiteSpace(operatorReason))
|
||||||
@@ -1873,6 +1917,13 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
|
|||||||
record.SenderKeyThumbprint = senderThumbprint;
|
record.SenderKeyThumbprint = senderThumbprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateHexProperty, out var senderCertHexObj) &&
|
||||||
|
senderCertHexObj is string senderCertHex &&
|
||||||
|
!string.IsNullOrWhiteSpace(senderCertHex))
|
||||||
|
{
|
||||||
|
record.SenderCertificateHex = senderCertHex;
|
||||||
|
}
|
||||||
|
|
||||||
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.ClientTenantProperty, out var tenantObj) &&
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.ClientTenantProperty, out var tenantObj) &&
|
||||||
tenantObj is string tenantValue &&
|
tenantObj is string tenantValue &&
|
||||||
!string.IsNullOrWhiteSpace(tenantValue))
|
!string.IsNullOrWhiteSpace(tenantValue))
|
||||||
@@ -1976,6 +2027,13 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
|
|||||||
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.DpopConsumedNonceProperty, out var nonceObj) &&
|
||||||
|
nonceObj is string consumedNonce &&
|
||||||
|
!string.IsNullOrWhiteSpace(consumedNonce))
|
||||||
|
{
|
||||||
|
identity.SetClaim(AuthorityOpenIddictConstants.SenderNonceClaimType, consumedNonce);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case AuthoritySenderConstraintKinds.Mtls:
|
case AuthoritySenderConstraintKinds.Mtls:
|
||||||
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateThumbprintProperty, out var mtlsThumbprintObj) &&
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateThumbprintProperty, out var mtlsThumbprintObj) &&
|
||||||
@@ -1990,6 +2048,13 @@ internal sealed class HandleClientCredentialsHandler : IOpenIddictServerHandler<
|
|||||||
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.Transaction.Properties.TryGetValue(AuthorityOpenIddictConstants.MtlsCertificateHexProperty, out var mtlsHexObj) &&
|
||||||
|
mtlsHexObj is string mtlsHex &&
|
||||||
|
!string.IsNullOrWhiteSpace(mtlsHex))
|
||||||
|
{
|
||||||
|
identity.SetClaim(AuthorityOpenIddictConstants.MtlsCertificateHexClaimType, mtlsHex);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ namespace StellaOps.Authority.OpenIddict.Handlers;
|
|||||||
|
|
||||||
internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ValidateTokenRequestContext>
|
internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenIddictServerEvents.ValidateTokenRequestContext>
|
||||||
{
|
{
|
||||||
|
private const string AnyDpopKeyThumbprint = "__authority_any_dpop_key__";
|
||||||
|
|
||||||
private readonly StellaOpsAuthorityOptions authorityOptions;
|
private readonly StellaOpsAuthorityOptions authorityOptions;
|
||||||
private readonly IAuthorityClientStore clientStore;
|
private readonly IAuthorityClientStore clientStore;
|
||||||
private readonly IDpopProofValidator proofValidator;
|
private readonly IDpopProofValidator proofValidator;
|
||||||
@@ -89,14 +91,33 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
}
|
}
|
||||||
|
|
||||||
var senderConstraint = NormalizeSenderConstraint(clientDocument);
|
var senderConstraint = NormalizeSenderConstraint(clientDocument);
|
||||||
context.Transaction.Properties[AuthorityOpenIddictConstants.ClientSenderConstraintProperty] = senderConstraint;
|
var configuredAudiences = EnsureRequestAudiences(context.Request, clientDocument);
|
||||||
|
var nonceOptions = senderConstraintOptions.Dpop.Nonce;
|
||||||
|
|
||||||
if (!string.Equals(senderConstraint, AuthoritySenderConstraintKinds.Dpop, StringComparison.Ordinal))
|
string? matchedNonceAudience = null;
|
||||||
|
if (senderConstraintOptions.Dpop.Enabled && nonceOptions.Enabled)
|
||||||
|
{
|
||||||
|
matchedNonceAudience = ResolveNonceAudience(context.Request, nonceOptions, configuredAudiences);
|
||||||
|
}
|
||||||
|
|
||||||
|
var requiresClientSenderConstraint = string.Equals(senderConstraint, AuthoritySenderConstraintKinds.Dpop, StringComparison.Ordinal);
|
||||||
|
var requiresConfiguredAudience = senderConstraintOptions.Dpop.Enabled && matchedNonceAudience is not null;
|
||||||
|
|
||||||
|
var effectiveSenderConstraint = requiresClientSenderConstraint || requiresConfiguredAudience
|
||||||
|
? AuthoritySenderConstraintKinds.Dpop
|
||||||
|
: senderConstraint;
|
||||||
|
|
||||||
|
context.Transaction.Properties[AuthorityOpenIddictConstants.ClientSenderConstraintProperty] = effectiveSenderConstraint;
|
||||||
|
|
||||||
|
if (!requiresClientSenderConstraint && !requiresConfiguredAudience)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var configuredAudiences = EnsureRequestAudiences(context.Request, clientDocument);
|
if (requiresConfiguredAudience && !requiresClientSenderConstraint)
|
||||||
|
{
|
||||||
|
logger.LogDebug("DPoP enforcement enabled for client {ClientId} targeting audience {Audience}.", clientId, matchedNonceAudience);
|
||||||
|
}
|
||||||
|
|
||||||
if (!senderConstraintOptions.Dpop.Enabled)
|
if (!senderConstraintOptions.Dpop.Enabled)
|
||||||
{
|
{
|
||||||
@@ -131,7 +152,7 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
await ChallengeNonceAsync(
|
await ChallengeNonceAsync(
|
||||||
context,
|
context,
|
||||||
clientDocument,
|
clientDocument,
|
||||||
audience: null,
|
audience: matchedNonceAudience,
|
||||||
thumbprint: null,
|
thumbprint: null,
|
||||||
reasonCode: "missing_proof",
|
reasonCode: "missing_proof",
|
||||||
description: "DPoP proof is required.",
|
description: "DPoP proof is required.",
|
||||||
@@ -159,7 +180,7 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
await ChallengeNonceAsync(
|
await ChallengeNonceAsync(
|
||||||
context,
|
context,
|
||||||
clientDocument,
|
clientDocument,
|
||||||
audience: null,
|
audience: matchedNonceAudience,
|
||||||
thumbprint: null,
|
thumbprint: null,
|
||||||
reasonCode: validationResult.ErrorCode ?? "invalid_proof",
|
reasonCode: validationResult.ErrorCode ?? "invalid_proof",
|
||||||
description: error,
|
description: error,
|
||||||
@@ -174,7 +195,7 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
await ChallengeNonceAsync(
|
await ChallengeNonceAsync(
|
||||||
context,
|
context,
|
||||||
clientDocument,
|
clientDocument,
|
||||||
audience: null,
|
audience: matchedNonceAudience,
|
||||||
thumbprint: null,
|
thumbprint: null,
|
||||||
reasonCode: "invalid_key",
|
reasonCode: "invalid_key",
|
||||||
description: "DPoP proof must embed a JSON Web Key.",
|
description: "DPoP proof must embed a JSON Web Key.",
|
||||||
@@ -209,8 +230,7 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
context.Transaction.Properties[AuthorityOpenIddictConstants.DpopIssuedAtProperty] = issuedAt;
|
context.Transaction.Properties[AuthorityOpenIddictConstants.DpopIssuedAtProperty] = issuedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nonceOptions = senderConstraintOptions.Dpop.Nonce;
|
var requiredAudience = matchedNonceAudience;
|
||||||
var requiredAudience = ResolveNonceAudience(context.Request, nonceOptions, configuredAudiences);
|
|
||||||
|
|
||||||
if (nonceOptions.Enabled && requiredAudience is not null)
|
if (nonceOptions.Enabled && requiredAudience is not null)
|
||||||
{
|
{
|
||||||
@@ -232,10 +252,10 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var consumeResult = await nonceStore.TryConsumeAsync(
|
var consumeResult = await ConsumeNonceAsync(
|
||||||
suppliedNonce,
|
suppliedNonce,
|
||||||
requiredAudience,
|
requiredAudience,
|
||||||
clientDocument.ClientId,
|
clientDocument,
|
||||||
thumbprint,
|
thumbprint,
|
||||||
context.CancellationToken).ConfigureAwait(false);
|
context.CancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -457,14 +477,19 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
|
|
||||||
string? issuedNonce = null;
|
string? issuedNonce = null;
|
||||||
DateTimeOffset? expiresAt = null;
|
DateTimeOffset? expiresAt = null;
|
||||||
if (audience is not null && thumbprint is not null && senderConstraintOptions.Dpop.Nonce.Enabled)
|
var nonceOptions = senderConstraintOptions.Dpop.Nonce;
|
||||||
|
if (audience is not null && nonceOptions.Enabled)
|
||||||
{
|
{
|
||||||
|
var issuanceThumbprint = string.IsNullOrWhiteSpace(thumbprint)
|
||||||
|
? AnyDpopKeyThumbprint
|
||||||
|
: thumbprint;
|
||||||
|
|
||||||
var issuance = await nonceStore.IssueAsync(
|
var issuance = await nonceStore.IssueAsync(
|
||||||
audience,
|
audience,
|
||||||
clientDocument.ClientId,
|
clientDocument.ClientId,
|
||||||
thumbprint,
|
issuanceThumbprint,
|
||||||
senderConstraintOptions.Dpop.Nonce.Ttl,
|
nonceOptions.Ttl,
|
||||||
senderConstraintOptions.Dpop.Nonce.MaxIssuancePerMinute,
|
nonceOptions.MaxIssuancePerMinute,
|
||||||
context.CancellationToken).ConfigureAwait(false);
|
context.CancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (issuance.Status == DpopNonceIssueStatus.Success)
|
if (issuance.Status == DpopNonceIssueStatus.Success)
|
||||||
@@ -503,6 +528,34 @@ internal sealed class ValidateDpopProofHandler : IOpenIddictServerHandler<OpenId
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ValueTask<DpopNonceConsumeResult> ConsumeNonceAsync(
|
||||||
|
string nonce,
|
||||||
|
string audience,
|
||||||
|
AuthorityClientDocument clientDocument,
|
||||||
|
string keyThumbprint,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = await nonceStore.TryConsumeAsync(
|
||||||
|
nonce,
|
||||||
|
audience,
|
||||||
|
clientDocument.ClientId,
|
||||||
|
keyThumbprint,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (result.Status == DpopNonceConsumeStatus.NotFound &&
|
||||||
|
!string.Equals(keyThumbprint, AnyDpopKeyThumbprint, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
result = await nonceStore.TryConsumeAsync(
|
||||||
|
nonce,
|
||||||
|
audience,
|
||||||
|
clientDocument.ClientId,
|
||||||
|
AnyDpopKeyThumbprint,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private static string BuildAuthenticateHeader(string reasonCode, string description, string? nonce)
|
private static string BuildAuthenticateHeader(string reasonCode, string description, string? nonce)
|
||||||
{
|
{
|
||||||
var parameters = new Dictionary<string, string?>
|
var parameters = new Dictionary<string, string?>
|
||||||
|
|||||||
@@ -117,6 +117,18 @@ internal sealed class PersistTokensHandler : IOpenIddictServerHandler<OpenIddict
|
|||||||
document.SenderConstraint = senderConstraint;
|
document.SenderConstraint = senderConstraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var senderNonce = principal.GetClaim(AuthorityOpenIddictConstants.SenderNonceClaimType);
|
||||||
|
if (!string.IsNullOrWhiteSpace(senderNonce))
|
||||||
|
{
|
||||||
|
document.SenderNonce = senderNonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderCertificateHex = principal.GetClaim(AuthorityOpenIddictConstants.MtlsCertificateHexClaimType);
|
||||||
|
if (!string.IsNullOrWhiteSpace(senderCertificateHex))
|
||||||
|
{
|
||||||
|
document.SenderCertificateHex = senderCertificateHex;
|
||||||
|
}
|
||||||
|
|
||||||
var serviceAccountId = principal.GetClaim(StellaOpsClaimTypes.ServiceAccount);
|
var serviceAccountId = principal.GetClaim(StellaOpsClaimTypes.ServiceAccount);
|
||||||
if (!string.IsNullOrWhiteSpace(serviceAccountId))
|
if (!string.IsNullOrWhiteSpace(serviceAccountId))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.Metrics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OpenIddict.Abstractions;
|
using OpenIddict.Abstractions;
|
||||||
using OpenIddict.Extensions;
|
using OpenIddict.Extensions;
|
||||||
@@ -32,6 +35,7 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
private readonly TimeProvider clock;
|
private readonly TimeProvider clock;
|
||||||
private readonly ActivitySource activitySource;
|
private readonly ActivitySource activitySource;
|
||||||
private readonly ILogger<ValidateAccessTokenHandler> logger;
|
private readonly ILogger<ValidateAccessTokenHandler> logger;
|
||||||
|
private readonly Counter<long> mtlsMismatchCounter;
|
||||||
private static readonly TimeSpan IncidentFreshAuthWindow = TimeSpan.FromMinutes(5);
|
private static readonly TimeSpan IncidentFreshAuthWindow = TimeSpan.FromMinutes(5);
|
||||||
private static readonly TimeSpan PolicyAttestationFreshAuthWindow = TimeSpan.FromMinutes(5);
|
private static readonly TimeSpan PolicyAttestationFreshAuthWindow = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
@@ -44,6 +48,7 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
IAuthEventSink auditSink,
|
IAuthEventSink auditSink,
|
||||||
TimeProvider clock,
|
TimeProvider clock,
|
||||||
ActivitySource activitySource,
|
ActivitySource activitySource,
|
||||||
|
Meter meter,
|
||||||
ILogger<ValidateAccessTokenHandler> logger)
|
ILogger<ValidateAccessTokenHandler> logger)
|
||||||
{
|
{
|
||||||
this.tokenStore = tokenStore ?? throw new ArgumentNullException(nameof(tokenStore));
|
this.tokenStore = tokenStore ?? throw new ArgumentNullException(nameof(tokenStore));
|
||||||
@@ -54,7 +59,14 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
this.auditSink = auditSink ?? throw new ArgumentNullException(nameof(auditSink));
|
this.auditSink = auditSink ?? throw new ArgumentNullException(nameof(auditSink));
|
||||||
this.clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
this.clock = clock ?? throw new ArgumentNullException(nameof(clock));
|
||||||
this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource));
|
this.activitySource = activitySource ?? throw new ArgumentNullException(nameof(activitySource));
|
||||||
|
if (meter is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(meter));
|
||||||
|
}
|
||||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
mtlsMismatchCounter = meter.CreateCounter<long>(
|
||||||
|
name: "authority_mtls_mismatch_total",
|
||||||
|
description: "Count of mTLS-bound token requests rejected due to missing or mismatched certificates.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateTokenContext context)
|
public async ValueTask HandleAsync(OpenIddictServerEvents.ValidateTokenContext context)
|
||||||
@@ -126,6 +138,11 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
{
|
{
|
||||||
EnsureSenderConstraintClaims(context.Principal, tokenDocument);
|
EnsureSenderConstraintClaims(context.Principal, tokenDocument);
|
||||||
|
|
||||||
|
if (!EnsureMtlsBinding(context, tokenDocument))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var documentTenant = NormalizeTenant(tokenDocument.Tenant);
|
var documentTenant = NormalizeTenant(tokenDocument.Tenant);
|
||||||
if (documentTenant is not null)
|
if (documentTenant is not null)
|
||||||
{
|
{
|
||||||
@@ -538,6 +555,76 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
await auditSink.WriteAsync(record, cancellationToken).ConfigureAwait(false);
|
await auditSink.WriteAsync(record, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool EnsureMtlsBinding(OpenIddictServerEvents.ValidateTokenContext context, AuthorityTokenDocument tokenDocument)
|
||||||
|
{
|
||||||
|
if (!string.Equals(tokenDocument.SenderConstraint, AuthoritySenderConstraintKinds.Mtls, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedCertificateHex = tokenDocument.SenderCertificateHex;
|
||||||
|
if (string.IsNullOrWhiteSpace(expectedCertificateHex))
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"Token {TokenId} marked as mTLS but missing certificate metadata.",
|
||||||
|
tokenDocument.TokenId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetHttpContext(context.Transaction, out var httpContext))
|
||||||
|
{
|
||||||
|
logger.LogWarning("mTLS-bound token {TokenId} used without HTTP context.", tokenDocument.TokenId);
|
||||||
|
context.Reject(OpenIddictConstants.Errors.InvalidToken, "Sender certificate verification unavailable.");
|
||||||
|
RecordMtlsMismatch("context_missing");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var certificate = httpContext.Connection.ClientCertificate;
|
||||||
|
if (certificate is null)
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"mTLS-bound token {TokenId} used without presenting a client certificate.",
|
||||||
|
tokenDocument.TokenId);
|
||||||
|
context.Reject(OpenIddictConstants.Errors.InvalidToken, "Sender certificate required for this token.");
|
||||||
|
RecordMtlsMismatch("missing_certificate");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var presentedHex = Convert.ToHexString(certificate.GetCertHash(HashAlgorithmName.SHA256));
|
||||||
|
if (!string.Equals(presentedHex, expectedCertificateHex, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
logger.LogWarning(
|
||||||
|
"mTLS-bound token {TokenId} rejected: certificate thumbprint mismatch.",
|
||||||
|
tokenDocument.TokenId);
|
||||||
|
context.Reject(OpenIddictConstants.Errors.InvalidToken, "Sender certificate mismatch.");
|
||||||
|
RecordMtlsMismatch("thumbprint_mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetHttpContext(OpenIddictServerTransaction transaction, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out HttpContext? httpContext)
|
||||||
|
{
|
||||||
|
if (transaction.Properties.TryGetValue(typeof(HttpContext).FullName!, out var property) &&
|
||||||
|
property is HttpContext typedContext)
|
||||||
|
{
|
||||||
|
httpContext = typedContext;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpContext = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RecordMtlsMismatch(string reason)
|
||||||
|
{
|
||||||
|
mtlsMismatchCounter.Add(1, new KeyValuePair<string, object?>[]
|
||||||
|
{
|
||||||
|
new("reason", reason)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureSenderConstraintClaims(ClaimsPrincipal? principal, AuthorityTokenDocument tokenDocument)
|
private static void EnsureSenderConstraintClaims(ClaimsPrincipal? principal, AuthorityTokenDocument tokenDocument)
|
||||||
{
|
{
|
||||||
if (principal?.Identity is not ClaimsIdentity identity)
|
if (principal?.Identity is not ClaimsIdentity identity)
|
||||||
@@ -578,5 +665,12 @@ internal sealed class ValidateAccessTokenHandler : IOpenIddictServerHandler<Open
|
|||||||
{
|
{
|
||||||
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
identity.SetClaim(AuthorityOpenIddictConstants.ConfirmationClaimType, confirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tokenDocument.SenderConstraint == AuthoritySenderConstraintKinds.Mtls &&
|
||||||
|
!string.IsNullOrWhiteSpace(tokenDocument.SenderCertificateHex) &&
|
||||||
|
!identity.HasClaim(claim => claim.Type == AuthorityOpenIddictConstants.MtlsCertificateHexClaimType))
|
||||||
|
{
|
||||||
|
identity.SetClaim(AuthorityOpenIddictConstants.MtlsCertificateHexClaimType, tokenDocument.SenderCertificateHex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ using StellaOps.Authority.Notifications.Ack;
|
|||||||
using StellaOps.Authority.Plugins.Abstractions;
|
using StellaOps.Authority.Plugins.Abstractions;
|
||||||
using StellaOps.Authority.Plugins;
|
using StellaOps.Authority.Plugins;
|
||||||
using StellaOps.Authority.Bootstrap;
|
using StellaOps.Authority.Bootstrap;
|
||||||
|
using StellaOps.Authority.Console;
|
||||||
using StellaOps.Authority.Storage.Mongo.Extensions;
|
using StellaOps.Authority.Storage.Mongo.Extensions;
|
||||||
using StellaOps.Authority.Storage.Mongo.Initialization;
|
using StellaOps.Authority.Storage.Mongo.Initialization;
|
||||||
using StellaOps.Authority.Storage.Mongo.Stores;
|
using StellaOps.Authority.Storage.Mongo.Stores;
|
||||||
@@ -115,6 +116,8 @@ builder.Host.UseSerilog((context, _, loggerConfiguration) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
var authorityOptions = authorityConfiguration.Options;
|
var authorityOptions = authorityConfiguration.Options;
|
||||||
|
builder.Services.AddStellaOpsCrypto(authorityOptions.Crypto);
|
||||||
|
builder.Services.AddHostedService<AuthoritySecretHasherInitializer>();
|
||||||
var issuerUri = authorityOptions.Issuer;
|
var issuerUri = authorityOptions.Issuer;
|
||||||
if (issuerUri is null)
|
if (issuerUri is null)
|
||||||
{
|
{
|
||||||
@@ -138,6 +141,7 @@ builder.Services.TryAddSingleton<IAuthorityRateLimiterPartitionKeyResolver, Defa
|
|||||||
builder.Services.AddSingleton<IAuthorityClientCertificateValidator, AuthorityClientCertificateValidator>();
|
builder.Services.AddSingleton<IAuthorityClientCertificateValidator, AuthorityClientCertificateValidator>();
|
||||||
builder.Services.TryAddSingleton<IAuthorityAirgapAuditService, AuthorityAirgapAuditService>();
|
builder.Services.TryAddSingleton<IAuthorityAirgapAuditService, AuthorityAirgapAuditService>();
|
||||||
builder.Services.AddSingleton<AuthorityOpenApiDocumentProvider>();
|
builder.Services.AddSingleton<AuthorityOpenApiDocumentProvider>();
|
||||||
|
builder.Services.TryAddSingleton<IConsoleWorkspaceService, ConsoleWorkspaceSampleService>();
|
||||||
|
|
||||||
#if STELLAOPS_AUTH_SECURITY
|
#if STELLAOPS_AUTH_SECURITY
|
||||||
var senderConstraints = authorityOptions.Security.SenderConstraints;
|
var senderConstraints = authorityOptions.Security.SenderConstraints;
|
||||||
@@ -210,7 +214,6 @@ if (requiresKms)
|
|||||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthoritySigningKeySource, KmsAuthoritySigningKeySource>());
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthoritySigningKeySource, KmsAuthoritySigningKeySource>());
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Services.AddStellaOpsCrypto();
|
|
||||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthoritySigningKeySource, FileAuthoritySigningKeySource>());
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IAuthoritySigningKeySource, FileAuthoritySigningKeySource>());
|
||||||
builder.Services.AddSingleton<AuthoritySigningKeyManager>();
|
builder.Services.AddSingleton<AuthoritySigningKeyManager>();
|
||||||
builder.Services.AddSingleton<AuthorityAckTokenKeyManager>();
|
builder.Services.AddSingleton<AuthorityAckTokenKeyManager>();
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using StellaOps.Authority.Plugins.Abstractions;
|
||||||
|
using StellaOps.Configuration;
|
||||||
|
using StellaOps.Cryptography;
|
||||||
|
|
||||||
|
namespace StellaOps.Authority.Security;
|
||||||
|
|
||||||
|
internal sealed class AuthoritySecretHasherInitializer : IHostedService
|
||||||
|
{
|
||||||
|
private readonly ICryptoHash hash;
|
||||||
|
private readonly IOptions<StellaOpsAuthorityOptions> authorityOptions;
|
||||||
|
private readonly ILogger<AuthoritySecretHasherInitializer> logger;
|
||||||
|
|
||||||
|
public AuthoritySecretHasherInitializer(
|
||||||
|
ICryptoHash hash,
|
||||||
|
IOptions<StellaOpsAuthorityOptions> authorityOptions,
|
||||||
|
ILogger<AuthoritySecretHasherInitializer> logger)
|
||||||
|
{
|
||||||
|
this.hash = hash ?? throw new ArgumentNullException(nameof(hash));
|
||||||
|
this.authorityOptions = authorityOptions ?? throw new ArgumentNullException(nameof(authorityOptions));
|
||||||
|
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var options = authorityOptions.Value;
|
||||||
|
var algorithm = options?.Crypto?.DefaultHashAlgorithm;
|
||||||
|
AuthoritySecretHasher.Configure(hash, algorithm);
|
||||||
|
logger.LogInformation("Authority secret hasher configured with default algorithm {Algorithm}.",
|
||||||
|
string.IsNullOrWhiteSpace(algorithm) ? HashAlgorithms.Sha256 : algorithm);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -24,6 +23,7 @@ internal sealed class AuthorityJwksService
|
|||||||
};
|
};
|
||||||
|
|
||||||
private readonly ICryptoProviderRegistry registry;
|
private readonly ICryptoProviderRegistry registry;
|
||||||
|
private readonly ICryptoHash hash;
|
||||||
private readonly ILogger<AuthorityJwksService> logger;
|
private readonly ILogger<AuthorityJwksService> logger;
|
||||||
private readonly IMemoryCache cache;
|
private readonly IMemoryCache cache;
|
||||||
private readonly TimeProvider timeProvider;
|
private readonly TimeProvider timeProvider;
|
||||||
@@ -31,12 +31,14 @@ internal sealed class AuthorityJwksService
|
|||||||
|
|
||||||
public AuthorityJwksService(
|
public AuthorityJwksService(
|
||||||
ICryptoProviderRegistry registry,
|
ICryptoProviderRegistry registry,
|
||||||
|
ICryptoHash hash,
|
||||||
ILogger<AuthorityJwksService> logger,
|
ILogger<AuthorityJwksService> logger,
|
||||||
IMemoryCache cache,
|
IMemoryCache cache,
|
||||||
TimeProvider timeProvider,
|
TimeProvider timeProvider,
|
||||||
IOptions<StellaOpsAuthorityOptions> authorityOptions)
|
IOptions<StellaOpsAuthorityOptions> authorityOptions)
|
||||||
{
|
{
|
||||||
this.registry = registry ?? throw new ArgumentNullException(nameof(registry));
|
this.registry = registry ?? throw new ArgumentNullException(nameof(registry));
|
||||||
|
this.hash = hash ?? throw new ArgumentNullException(nameof(hash));
|
||||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
this.cache = cache ?? throw new ArgumentNullException(nameof(cache));
|
||||||
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
this.timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
||||||
@@ -133,12 +135,12 @@ internal sealed class AuthorityJwksService
|
|||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ComputeEtag(AuthorityJwksResponse response, DateTimeOffset expiresAt)
|
private string ComputeEtag(AuthorityJwksResponse response, DateTimeOffset expiresAt)
|
||||||
{
|
{
|
||||||
var payload = JsonSerializer.Serialize(response, SerializerOptions);
|
var payload = JsonSerializer.Serialize(response, SerializerOptions);
|
||||||
var buffer = Encoding.UTF8.GetBytes(payload + "|" + expiresAt.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture));
|
var buffer = Encoding.UTF8.GetBytes(payload + "|" + expiresAt.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture));
|
||||||
var hash = SHA256.HashData(buffer);
|
var digest = hash.ComputeHash(buffer, HashAlgorithms.Sha256);
|
||||||
return $"\"{Convert.ToHexString(hash)}\"";
|
return $"\"{Convert.ToHexString(digest)}\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed record AuthorityJwksCacheEntry(AuthorityJwksResult Result, DateTimeOffset ExpiresAt);
|
private sealed record AuthorityJwksCacheEntry(AuthorityJwksResult Result, DateTimeOffset ExpiresAt);
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
> 2025-10-27: Scope catalogue aligned with `advisory:ingest/advisory:read/vex:ingest/vex:read`, `aoc:verify` pairing documented, console/CLI references refreshed, and `etc/authority.yaml.sample` updated to require read scopes for verification clients.
|
> 2025-10-27: Scope catalogue aligned with `advisory:ingest/advisory:read/vex:ingest/vex:read`, `aoc:verify` pairing documented, console/CLI references refreshed, and `etc/authority.yaml.sample` updated to require read scopes for verification clients.
|
||||||
> 2025-10-31: Client credentials and password grants now reject advisory/vex read or signals scopes without `aoc:verify`, enforce tenant assignment for `aoc:verify`, tag violations via `authority.aoc_scope_violation`, extend tests, and refresh scope catalogue docs/sample roles.
|
> 2025-10-31: Client credentials and password grants now reject advisory/vex read or signals scopes without `aoc:verify`, enforce tenant assignment for `aoc:verify`, tag violations via `authority.aoc_scope_violation`, extend tests, and refresh scope catalogue docs/sample roles.
|
||||||
|
|
||||||
|
| AUTH-CRYPTO-90-001 | DOING (2025-11-08) | Authority Core & Security Guild | SEC-CRYPTO-90-003, SEC-CRYPTO-90-004 | Migrate signing/key-loading paths (`KmsAuthoritySigningKeySource`, `FileAuthoritySigningKeySource`, `AuthorityJwksService`, secret hashers) to `ICryptoProviderRegistry` so regional bundles can pick `ru.cryptopro.csp` / `ru.pkcs11` providers as defined in `docs/security/crypto-routing-audit-2025-11-07.md`. | All signing + hashing code paths resolve registry providers; Authority config exposes provider selection; JWKS output references sovereign keys; regression tests updated. |
|
||||||
|
|
||||||
## Link-Not-Merge v1
|
## Link-Not-Merge v1
|
||||||
|
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
@@ -30,10 +32,18 @@
|
|||||||
|
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
|----|--------|----------|------------|-------------|---------------|
|
|----|--------|----------|------------|-------------|---------------|
|
||||||
| AUTH-POLICY-23-002 | BLOCKED (2025-10-29) | Authority Core & Security Guild | AUTH-POLICY-23-001 | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. | Activation endpoint enforces rule; audit logs contain approver IDs; tests cover 2-person path. |
|
| AUTH-DPOP-11-001 | DOING (2025-11-07) | Authority Core & Security Guild | AUTH-AOC-19-002 | Enforce DPoP sender constraints for all Authority token flows (nonce store selection, algorithm allowlist, `cnf.jkt` persistence, structured telemetry). | `/token` enforces configured DPoP policies (nonce, allowed algorithms); cnf claims verified in integration tests; docs/runbooks updated with configuration guidance. |
|
||||||
|
> 2025-11-07: Joint Authority/DevOps stand-up committed to shipping nonce store + telemetry updates by 2025-11-10; config samples and integration tests being updated in tandem.
|
||||||
|
| AUTH-MTLS-11-002 | DOING (2025-11-07) | Authority Core & Security Guild | AUTH-DPOP-11-001 | Add mTLS-bound access token issuance/validation (client certificate thumbprints, JWKS rotation hooks) for high-assurance tenants and services. | mTLS certificate binding validated end-to-end; audit logs capture cert hashes; docs describe bootstrap/rotation steps. |
|
||||||
|
> 2025-11-08: Wiring cert thumbprint persistence + audit hooks now that DPoP nonce enforcement is in place; targeting shared delivery window with DEVOPS-AIRGAP-57-002.
|
||||||
|
> 2025-11-07: Same stand-up aligned on 2025-11-10 target for mTLS enforcement + JWKS rotation docs so plugin mitigations can unblock.
|
||||||
|
| AUTH-POLICY-23-001 | DONE (2025-10-27) | Authority Core & Docs Guild | AUTH-AOC-19-002 | Introduce fine-grained policy scopes (`policy:read`, `policy:author`, `policy:review`, `policy:simulate`, `findings:read`) for CLI/service identities; refresh discovery metadata, issuer templates, and offline defaults. | Scope catalogue and sample configs updated; `policy-cli` seed credentials rotated; docs recorded migration steps. |
|
||||||
|
| AUTH-POLICY-23-002 | DONE (2025-11-08) | Authority Core & Security Guild | AUTH-POLICY-23-001 | Implement optional two-person rule for activation: require two distinct `policy:activate` approvals when configured; emit audit logs. | Activation endpoint enforces rule; audit logs contain approver IDs; tests cover 2-person path. |
|
||||||
|
> 2025-11-08: Policy Engine enforces pending_second_approval when dual-control toggles demand it, activation auditor emits structured `policy.activation.*` scopes, and tests cover settings/audits.
|
||||||
> Blocked: Policy Engine/Studio have not yet exposed activation workflow endpoints or approval payloads needed to enforce dual-control (`WEB-POLICY-23-002`, `POLICY-ENGINE-23-002`). Revisit once activation contract lands.
|
> Blocked: Policy Engine/Studio have not yet exposed activation workflow endpoints or approval payloads needed to enforce dual-control (`WEB-POLICY-23-002`, `POLICY-ENGINE-23-002`). Revisit once activation contract lands.
|
||||||
| AUTH-POLICY-23-003 | BLOCKED (2025-10-29) | Authority Core & Docs Guild | AUTH-POLICY-23-001 | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. | Docs updated with reviewer checklist; configuration examples validated. |
|
| AUTH-POLICY-23-003 | DONE (2025-11-08) | Authority Core & Docs Guild | AUTH-POLICY-23-001 | Update documentation and sample configs for policy roles, approval workflow, and signing requirements. | Docs updated with reviewer checklist; configuration examples validated. |
|
||||||
> Blocked pending AUTH-POLICY-23-002 dual-approval implementation so docs can capture final activation behaviour.
|
> 2025-11-08: Docs refreshed for dual-control activation (console workflow, compliance checklist, sample YAML) and linked to new Policy Engine activation options.
|
||||||
|
> 2025-11-07: Scope migration landed (AUTH-POLICY-23-001); dual-approval + documentation tasks now waiting on pairing.
|
||||||
> 2025-10-27: Added `policy-cli` defaults to Authority config/secrets, refreshed CLI/CI documentation with the new scope bundle, recorded release migration guidance, and introduced `scripts/verify-policy-scopes.py` to guard against regressions.
|
> 2025-10-27: Added `policy-cli` defaults to Authority config/secrets, refreshed CLI/CI documentation with the new scope bundle, recorded release migration guidance, and introduced `scripts/verify-policy-scopes.py` to guard against regressions.
|
||||||
|
|
||||||
## Graph & Vuln Explorer v1
|
## Graph & Vuln Explorer v1
|
||||||
@@ -127,7 +137,7 @@
|
|||||||
> 2025-11-02: Shared OpenSSL 1.1 shim feeds Authority & Signals Mongo2Go harnesses so pack scope coverage keeps running on OpenSSL 3 hosts (AUTH-PACKS-41-001).
|
> 2025-11-02: Shared OpenSSL 1.1 shim feeds Authority & Signals Mongo2Go harnesses so pack scope coverage keeps running on OpenSSL 3 hosts (AUTH-PACKS-41-001).
|
||||||
> 2025-11-04: Discovery metadata/OpenAPI advertise packs scopes, configs/offline kit templates bundle new roles, and Authority tests re-run to validate tenant gating for `packs.*`.
|
> 2025-11-04: Discovery metadata/OpenAPI advertise packs scopes, configs/offline kit templates bundle new roles, and Authority tests re-run to validate tenant gating for `packs.*`.
|
||||||
| AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Authority Core & Security Guild | AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101 | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. | Signing policies enforced; approvals require correct roles; CI token scope tests pass; audit logs recorded. |
|
| AUTH-PACKS-43-001 | BLOCKED (2025-10-27) | Authority Core & Security Guild | AUTH-PACKS-41-001, TASKRUN-42-001, ORCH-SVC-42-101 | Enforce pack signing policies, approval RBAC checks, CLI CI token scopes, and audit logging for approvals. | Signing policies enforced; approvals require correct roles; CI token scope tests pass; audit logs recorded. |
|
||||||
> Blocked: Task Runner approval APIs (`ORCH-SVC-42-101`, `TASKRUN-42-001`) still outstanding. Pack scope catalog (AUTH-PACKS-41-001) landed 2025-11-04; resume once execution/approval contracts are published.
|
> Blocked: ORCH-SVC-42-101 (Orchestrator log streaming/approvals API) still TODO. AUTH-PACKS-41-001 + TASKRUN-42-001 are DONE (2025-11-04); resume once Orchestrator publishes contracts.
|
||||||
|
|
||||||
## Authority-Backed Scopes & Tenancy (Epic 14)
|
## Authority-Backed Scopes & Tenancy (Epic 14)
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
@@ -156,8 +166,10 @@
|
|||||||
| AUTH-AIRGAP-56-002 | DONE (2025-11-04) | Authority Core & Security Guild | AUTH-AIRGAP-56-001, AIRGAP-IMP-58-001 | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. | Audit records persisted; endpoint paginates results; tests cover RBAC + filtering. |
|
| AUTH-AIRGAP-56-002 | DONE (2025-11-04) | Authority Core & Security Guild | AUTH-AIRGAP-56-001, AIRGAP-IMP-58-001 | Audit import actions with actor, tenant, bundle ID, and trace ID; expose `/authority/audit/airgap` endpoint. | Audit records persisted; endpoint paginates results; tests cover RBAC + filtering. |
|
||||||
> 2025-11-04: Airgap scope constants are wired through discovery metadata, `etc/authority.yaml.sample`, and offline kit docs; scope issuance tests executed via `dotnet test`.
|
> 2025-11-04: Airgap scope constants are wired through discovery metadata, `etc/authority.yaml.sample`, and offline kit docs; scope issuance tests executed via `dotnet test`.
|
||||||
> 2025-11-04: `/authority/audit/airgap` API persists tenant-scoped audit entries with pagination and authorization guards validated by the Authority integration suite (187 tests).
|
> 2025-11-04: `/authority/audit/airgap` API persists tenant-scoped audit entries with pagination and authorization guards validated by the Authority integration suite (187 tests).
|
||||||
| AUTH-AIRGAP-57-001 | BLOCKED (2025-11-01) | Authority Core & Security Guild, DevOps Guild | AUTH-AIRGAP-56-001, DEVOPS-AIRGAP-57-002 | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. | Awaiting clarified sealed-confirmation contract and configuration structure before implementation. |
|
| AUTH-AIRGAP-57-001 | DOING (2025-11-08) | Authority Core & Security Guild, DevOps Guild | AUTH-AIRGAP-56-001, DEVOPS-AIRGAP-57-002 | Enforce sealed-mode CI gating by refusing token issuance when declared sealed install lacks sealing confirmation. | Implement Authority-side sealed-mode checks once DevOps publishes sealed CI artefacts + contract (target 2025-11-10). |
|
||||||
> 2025-11-01: AUTH-AIRGAP-57-001 blocked pending guidance on sealed-confirmation contract and configuration expectations before gating changes (Authority Core & Security Guild, DevOps Guild).
|
> 2025-11-08: Picked up in tandem with DEVOPS-AIRGAP-57-002 — validating sealed confirmation payload + wiring Authority gating tests against ops/devops/sealed-mode-ci artefacts.
|
||||||
|
> 2025-11-08: `/token`/`/introspect` now reject mTLS-bound tokens without the recorded certificate; `authority_mtls_mismatch_total` metric + docs updated for plugin consumers.
|
||||||
|
> 2025-11-08: DevOps sealed-mode CI now emits `authority-sealed-ci.json`; ingest that contract next to unblock enforcement switch.
|
||||||
|
|
||||||
## SDKs & OpenAPI (Epic 17)
|
## SDKs & OpenAPI (Epic 17)
|
||||||
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
| ID | Status | Owner(s) | Depends on | Description | Exit Criteria |
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ internal static class CommandFactory
|
|||||||
root.Add(BuildConfigCommand(options));
|
root.Add(BuildConfigCommand(options));
|
||||||
root.Add(BuildKmsCommand(services, verboseOption, cancellationToken));
|
root.Add(BuildKmsCommand(services, verboseOption, cancellationToken));
|
||||||
root.Add(BuildVulnCommand(services, verboseOption, cancellationToken));
|
root.Add(BuildVulnCommand(services, verboseOption, cancellationToken));
|
||||||
|
root.Add(BuildCryptoCommand(services, verboseOption, cancellationToken));
|
||||||
|
|
||||||
var pluginLogger = loggerFactory.CreateLogger<CliCommandModuleLoader>();
|
var pluginLogger = loggerFactory.CreateLogger<CliCommandModuleLoader>();
|
||||||
var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger);
|
var pluginLoader = new CliCommandModuleLoader(services, options, pluginLogger);
|
||||||
@@ -385,6 +386,36 @@ internal static class CommandFactory
|
|||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Command BuildCryptoCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var crypto = new Command("crypto", "Inspect StellaOps cryptography providers.");
|
||||||
|
var providers = new Command("providers", "List registered crypto providers and keys.");
|
||||||
|
|
||||||
|
var jsonOption = new Option<bool>("--json")
|
||||||
|
{
|
||||||
|
Description = "Emit JSON output."
|
||||||
|
};
|
||||||
|
|
||||||
|
var profileOption = new Option<string?>("--profile")
|
||||||
|
{
|
||||||
|
Description = "Temporarily override the active registry profile when computing provider order."
|
||||||
|
};
|
||||||
|
|
||||||
|
providers.Add(jsonOption);
|
||||||
|
providers.Add(profileOption);
|
||||||
|
|
||||||
|
providers.SetAction((parseResult, _) =>
|
||||||
|
{
|
||||||
|
var json = parseResult.GetValue(jsonOption);
|
||||||
|
var verbose = parseResult.GetValue(verboseOption);
|
||||||
|
var profile = parseResult.GetValue(profileOption);
|
||||||
|
return CommandHandlers.HandleCryptoProvidersAsync(services, verbose, json, profile, cancellationToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
crypto.Add(providers);
|
||||||
|
return crypto;
|
||||||
|
}
|
||||||
|
|
||||||
private static Command BuildSourcesCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
|
private static Command BuildSourcesCommand(IServiceProvider services, Option<bool> verboseOption, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var sources = new Command("sources", "Interact with source ingestion workflows.");
|
var sources = new Command("sources", "Interact with source ingestion workflows.");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using StellaOps.Auth.Client;
|
using StellaOps.Auth.Client;
|
||||||
@@ -29,6 +30,7 @@ using StellaOps.Cli.Services.Models;
|
|||||||
using StellaOps.Cli.Services.Models.AdvisoryAi;
|
using StellaOps.Cli.Services.Models.AdvisoryAi;
|
||||||
using StellaOps.Cli.Telemetry;
|
using StellaOps.Cli.Telemetry;
|
||||||
using StellaOps.Cryptography;
|
using StellaOps.Cryptography;
|
||||||
|
using StellaOps.Cryptography.DependencyInjection;
|
||||||
using StellaOps.Cryptography.Kms;
|
using StellaOps.Cryptography.Kms;
|
||||||
|
|
||||||
namespace StellaOps.Cli.Commands;
|
namespace StellaOps.Cli.Commands;
|
||||||
@@ -6468,4 +6470,192 @@ internal static class CommandHandlers
|
|||||||
Environment.ExitCode = 1;
|
Environment.ExitCode = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task HandleCryptoProvidersAsync(
|
||||||
|
IServiceProvider services,
|
||||||
|
bool verbose,
|
||||||
|
bool jsonOutput,
|
||||||
|
string? profileOverride,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using var scope = services.CreateScope();
|
||||||
|
var loggerFactory = scope.ServiceProvider.GetRequiredService<ILoggerFactory>();
|
||||||
|
var logger = loggerFactory.CreateLogger("crypto-providers");
|
||||||
|
var verbosity = scope.ServiceProvider.GetRequiredService<VerbosityState>();
|
||||||
|
var previousLevel = verbosity.MinimumLevel;
|
||||||
|
verbosity.MinimumLevel = verbose ? LogLevel.Debug : LogLevel.Information;
|
||||||
|
using var activity = CliActivitySource.Instance.StartActivity("cli.crypto.providers", ActivityKind.Internal);
|
||||||
|
using var duration = CliMetrics.MeasureCommandDuration("crypto providers");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var registry = scope.ServiceProvider.GetService<ICryptoProviderRegistry>();
|
||||||
|
if (registry is null)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Crypto provider registry not available in this environment.");
|
||||||
|
AnsiConsole.MarkupLine("[yellow]Crypto subsystem is not configured in this environment.[/]");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionsMonitor = scope.ServiceProvider.GetService<IOptionsMonitor<CryptoProviderRegistryOptions>>();
|
||||||
|
var registryOptions = optionsMonitor?.CurrentValue ?? new CryptoProviderRegistryOptions();
|
||||||
|
var preferredOrder = DeterminePreferredOrder(registryOptions, profileOverride);
|
||||||
|
var providers = registry.Providers
|
||||||
|
.Select(provider => new ProviderInfo(
|
||||||
|
provider.Name,
|
||||||
|
provider.GetType().FullName ?? provider.GetType().Name,
|
||||||
|
DescribeProviderKeys(provider).ToList()))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (jsonOutput)
|
||||||
|
{
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
activeProfile = registryOptions.ActiveProfile,
|
||||||
|
preferredOrder,
|
||||||
|
providers = providers.Select(info => new
|
||||||
|
{
|
||||||
|
info.Name,
|
||||||
|
info.Type,
|
||||||
|
keys = info.Keys.Select(k => new
|
||||||
|
{
|
||||||
|
k.KeyId,
|
||||||
|
k.AlgorithmId,
|
||||||
|
Metadata = k.Metadata
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(JsonSerializer.Serialize(payload, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
}));
|
||||||
|
Environment.ExitCode = 0;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderCryptoProviders(preferredOrder, providers);
|
||||||
|
Environment.ExitCode = 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
verbosity.MinimumLevel = previousLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderCryptoProviders(
|
||||||
|
IReadOnlyList<string> preferredOrder,
|
||||||
|
IReadOnlyCollection<ProviderInfo> providers)
|
||||||
|
{
|
||||||
|
if (preferredOrder.Count > 0)
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[cyan]Preferred order:[/] {0}", Markup.Escape(string.Join(", ", preferredOrder)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnsiConsole.MarkupLine("[yellow]Preferred order is not configured; using registration order.[/]");
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = new Table().Border(TableBorder.Rounded);
|
||||||
|
table.AddColumn("Provider");
|
||||||
|
table.AddColumn("Type");
|
||||||
|
table.AddColumn("Keys");
|
||||||
|
|
||||||
|
foreach (var provider in providers)
|
||||||
|
{
|
||||||
|
var keySummary = provider.Keys.Count == 0
|
||||||
|
? "[grey]No signing keys exposed (managed externally).[/]"
|
||||||
|
: string.Join(Environment.NewLine, provider.Keys.Select(FormatDescriptor));
|
||||||
|
|
||||||
|
table.AddRow(
|
||||||
|
Markup.Escape(provider.Name),
|
||||||
|
Markup.Escape(provider.Type),
|
||||||
|
keySummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnsiConsole.Write(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<CryptoProviderKeyDescriptor> DescribeProviderKeys(ICryptoProvider provider)
|
||||||
|
{
|
||||||
|
if (provider is ICryptoProviderDiagnostics diagnostics)
|
||||||
|
{
|
||||||
|
return diagnostics.DescribeKeys().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var signingKeys = provider.GetSigningKeys();
|
||||||
|
if (signingKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return Array.Empty<CryptoProviderKeyDescriptor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptors = new List<CryptoProviderKeyDescriptor>(signingKeys.Count);
|
||||||
|
foreach (var signingKey in signingKeys)
|
||||||
|
{
|
||||||
|
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["kind"] = signingKey.Kind.ToString(),
|
||||||
|
["createdAt"] = signingKey.CreatedAt.UtcDateTime.ToString("O"),
|
||||||
|
["providerHint"] = signingKey.Reference.ProviderHint
|
||||||
|
};
|
||||||
|
|
||||||
|
if (signingKey.ExpiresAt.HasValue)
|
||||||
|
{
|
||||||
|
metadata["expiresAt"] = signingKey.ExpiresAt.Value.UtcDateTime.ToString("O");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pair in signingKey.Metadata)
|
||||||
|
{
|
||||||
|
metadata[$"meta.{pair.Key}"] = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptors.Add(new CryptoProviderKeyDescriptor(
|
||||||
|
provider.Name,
|
||||||
|
signingKey.Reference.KeyId,
|
||||||
|
signingKey.AlgorithmId,
|
||||||
|
metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> DeterminePreferredOrder(
|
||||||
|
CryptoProviderRegistryOptions? options,
|
||||||
|
string? overrideProfile)
|
||||||
|
{
|
||||||
|
if (options is null)
|
||||||
|
{
|
||||||
|
return Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(overrideProfile) &&
|
||||||
|
options.Profiles.TryGetValue(overrideProfile, out var profile) &&
|
||||||
|
profile.PreferredProviders.Count > 0)
|
||||||
|
{
|
||||||
|
return profile.PreferredProviders
|
||||||
|
.Where(static provider => !string.IsNullOrWhiteSpace(provider))
|
||||||
|
.Select(static provider => provider.Trim())
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.ResolvePreferredProviders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatDescriptor(CryptoProviderKeyDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (descriptor.Metadata.Count == 0)
|
||||||
|
{
|
||||||
|
return $"{Markup.Escape(descriptor.KeyId)} ({Markup.Escape(descriptor.AlgorithmId)})";
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataText = string.Join(
|
||||||
|
", ",
|
||||||
|
descriptor.Metadata.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||||
|
|
||||||
|
return $"{Markup.Escape(descriptor.KeyId)} ({Markup.Escape(descriptor.AlgorithmId)}){Environment.NewLine}[grey]{Markup.Escape(metadataText)}[/]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record ProviderInfo(string Name, string Type, IReadOnlyList<CryptoProviderKeyDescriptor> Keys);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using StellaOps.Auth.Abstractions;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using StellaOps.Auth.Abstractions;
|
||||||
|
using StellaOps.Configuration;
|
||||||
|
|
||||||
namespace StellaOps.Cli.Configuration;
|
namespace StellaOps.Cli.Configuration;
|
||||||
|
|
||||||
@@ -32,6 +33,9 @@ public sealed class StellaOpsCliOptions
|
|||||||
public StellaOpsCliOfflineOptions Offline { get; set; } = new();
|
public StellaOpsCliOfflineOptions Offline { get; set; } = new();
|
||||||
|
|
||||||
public StellaOpsCliPluginOptions Plugins { get; set; } = new();
|
public StellaOpsCliPluginOptions Plugins { get; set; } = new();
|
||||||
|
|
||||||
|
public StellaOpsCryptoOptions Crypto { get; set; } = new();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StellaOpsCliAuthorityOptions
|
public sealed class StellaOpsCliAuthorityOptions
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using StellaOps.Cli.Configuration;
|
|||||||
using StellaOps.Cli.Services;
|
using StellaOps.Cli.Services;
|
||||||
using StellaOps.Cli.Telemetry;
|
using StellaOps.Cli.Telemetry;
|
||||||
using StellaOps.AirGap.Policy;
|
using StellaOps.AirGap.Policy;
|
||||||
|
using StellaOps.Configuration;
|
||||||
|
|
||||||
namespace StellaOps.Cli;
|
namespace StellaOps.Cli;
|
||||||
|
|
||||||
@@ -24,10 +25,12 @@ internal static class Program
|
|||||||
var services = new ServiceCollection();
|
var services = new ServiceCollection();
|
||||||
services.AddSingleton(configuration);
|
services.AddSingleton(configuration);
|
||||||
services.AddSingleton(options);
|
services.AddSingleton(options);
|
||||||
|
services.AddOptions();
|
||||||
|
|
||||||
var verbosityState = new VerbosityState();
|
var verbosityState = new VerbosityState();
|
||||||
services.AddSingleton(verbosityState);
|
services.AddSingleton(verbosityState);
|
||||||
services.AddAirGapEgressPolicy(configuration);
|
services.AddAirGapEgressPolicy(configuration);
|
||||||
|
services.AddStellaOpsCrypto(options.Crypto);
|
||||||
|
|
||||||
services.AddLogging(builder =>
|
services.AddLogging(builder =>
|
||||||
{
|
{
|
||||||
@@ -170,4 +173,5 @@ internal static class Program
|
|||||||
|
|
||||||
return finalExit;
|
return finalExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,8 @@
|
|||||||
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
||||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||||
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Kms/StellaOps.Cryptography.Kms.csproj" />
|
||||||
|
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.CryptoPro/StellaOps.Cryptography.Plugin.CryptoPro.csproj" />
|
||||||
|
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography.Plugin.Pkcs11Gost/StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
|
||||||
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
|
<ProjectReference Include="../../AirGap/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy/StellaOps.AirGap.Policy.csproj" />
|
||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.Metrics;
|
using System.Diagnostics.Metrics;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.WebService.Diagnostics;
|
namespace StellaOps.Concelier.WebService.Diagnostics;
|
||||||
@@ -8,15 +9,28 @@ internal static class IngestionMetrics
|
|||||||
|
|
||||||
private static readonly Meter Meter = new(MeterName);
|
private static readonly Meter Meter = new(MeterName);
|
||||||
|
|
||||||
internal static readonly Counter<long> WriteCounter = Meter.CreateCounter<long>(
|
internal static readonly Counter<long> IngestionWriteCounter = Meter.CreateCounter<long>(
|
||||||
"ingestion_write_total",
|
"ingestion_write_total",
|
||||||
description: "Counts raw advisory ingestion attempts, segmented by tenant, source, and result.");
|
unit: "count",
|
||||||
|
description: "Number of advisory ingestion attempts processed by the web service.");
|
||||||
internal static readonly Counter<long> ViolationCounter = Meter.CreateCounter<long>(
|
|
||||||
"aoc_violation_total",
|
|
||||||
description: "Counts Aggregation-Only Contract violations detected during ingestion.");
|
|
||||||
|
|
||||||
internal static readonly Counter<long> VerificationCounter = Meter.CreateCounter<long>(
|
internal static readonly Counter<long> VerificationCounter = Meter.CreateCounter<long>(
|
||||||
"verify_runs_total",
|
"verify_runs_total",
|
||||||
description: "Counts AOC verification runs initiated via the API.");
|
unit: "count",
|
||||||
|
description: "Number of AOC verification requests processed by the web service.");
|
||||||
|
|
||||||
|
internal static KeyValuePair<string, object?>[] BuildWriteTags(string tenant, string source, string result) =>
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, object?>("tenant", tenant),
|
||||||
|
new KeyValuePair<string, object?>("source", source),
|
||||||
|
new KeyValuePair<string, object?>("result", result),
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static KeyValuePair<string, object?>[] BuildVerifyTags(string tenant, string result) =>
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, object?>("tenant", tenant),
|
||||||
|
new KeyValuePair<string, object?>("result", result),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using StellaOps.Concelier.RawModels;
|
using StellaOps.Concelier.RawModels;
|
||||||
@@ -34,7 +35,6 @@ internal static class AdvisoryRawRequestMapper
|
|||||||
string.IsNullOrWhiteSpace(signatureRequest.Digest) ? null : signatureRequest.Digest);
|
string.IsNullOrWhiteSpace(signatureRequest.Digest) ? null : signatureRequest.Digest);
|
||||||
|
|
||||||
var retrievedAt = upstreamRequest.RetrievedAt ?? timeProvider.GetUtcNow();
|
var retrievedAt = upstreamRequest.RetrievedAt ?? timeProvider.GetUtcNow();
|
||||||
|
|
||||||
var upstream = new RawUpstreamMetadata(
|
var upstream = new RawUpstreamMetadata(
|
||||||
upstreamRequest.UpstreamId,
|
upstreamRequest.UpstreamId,
|
||||||
string.IsNullOrWhiteSpace(upstreamRequest.DocumentVersion) ? null : upstreamRequest.DocumentVersion,
|
string.IsNullOrWhiteSpace(upstreamRequest.DocumentVersion) ? null : upstreamRequest.DocumentVersion,
|
||||||
@@ -59,6 +59,10 @@ internal static class AdvisoryRawRequestMapper
|
|||||||
var identifiers = new RawIdentifiers(
|
var identifiers = new RawIdentifiers(
|
||||||
aliases,
|
aliases,
|
||||||
identifiersRequest.Primary);
|
identifiersRequest.Primary);
|
||||||
|
var advisoryKey = NormalizeAdvisoryKey(
|
||||||
|
identifiersRequest.Primary,
|
||||||
|
aliases,
|
||||||
|
upstreamRequest.UpstreamId);
|
||||||
|
|
||||||
var linksetRequest = request.Linkset;
|
var linksetRequest = request.Linkset;
|
||||||
var linkset = new RawLinkset
|
var linkset = new RawLinkset
|
||||||
@@ -71,6 +75,8 @@ internal static class AdvisoryRawRequestMapper
|
|||||||
Notes = NormalizeDictionary(linksetRequest?.Notes)
|
Notes = NormalizeDictionary(linksetRequest?.Notes)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var links = BuildLinks(advisoryKey, aliases, upstreamRequest.UpstreamId);
|
||||||
|
|
||||||
return new AdvisoryRawDocument(
|
return new AdvisoryRawDocument(
|
||||||
tenant.Trim().ToLowerInvariant(),
|
tenant.Trim().ToLowerInvariant(),
|
||||||
source,
|
source,
|
||||||
@@ -78,8 +84,8 @@ internal static class AdvisoryRawRequestMapper
|
|||||||
content,
|
content,
|
||||||
identifiers,
|
identifiers,
|
||||||
linkset,
|
linkset,
|
||||||
AdvisoryKey: string.Empty,
|
AdvisoryKey: advisoryKey,
|
||||||
Links: ImmutableArray<RawLink>.Empty);
|
Links: links);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ImmutableArray<string> NormalizeStrings(IEnumerable<string>? values)
|
internal static ImmutableArray<string> NormalizeStrings(IEnumerable<string>? values)
|
||||||
@@ -156,4 +162,53 @@ internal static class AdvisoryRawRequestMapper
|
|||||||
using var document = JsonDocument.Parse(string.IsNullOrWhiteSpace(json) ? "{}" : json);
|
using var document = JsonDocument.Parse(string.IsNullOrWhiteSpace(json) ? "{}" : json);
|
||||||
return document.RootElement.Clone();
|
return document.RootElement.Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string NormalizeAdvisoryKey(string? primaryId, ImmutableArray<string> aliases, string upstreamId)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(primaryId))
|
||||||
|
{
|
||||||
|
return primaryId.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var alias in aliases)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(alias))
|
||||||
|
{
|
||||||
|
return alias.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(upstreamId) ? string.Empty : upstreamId.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImmutableArray<RawLink> BuildLinks(string advisoryKey, ImmutableArray<string> aliases, string upstreamId)
|
||||||
|
{
|
||||||
|
var builder = ImmutableArray.CreateBuilder<RawLink>();
|
||||||
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
void AddLink(string scheme, string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = value.Trim();
|
||||||
|
var key = $"{scheme}:{normalized}";
|
||||||
|
if (seen.Add(key))
|
||||||
|
{
|
||||||
|
builder.Add(new RawLink(scheme, normalized));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLink("PRIMARY", advisoryKey);
|
||||||
|
foreach (var alias in aliases)
|
||||||
|
{
|
||||||
|
AddLink("ALIAS", alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLink("UPSTREAM", upstreamId);
|
||||||
|
|
||||||
|
return builder.Count == 0 ? ImmutableArray<RawLink>.Empty : builder.ToImmutable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using StellaOps.Concelier.Core.Jobs;
|
|||||||
using StellaOps.Concelier.Connector.Common.Telemetry;
|
using StellaOps.Concelier.Connector.Common.Telemetry;
|
||||||
using StellaOps.Concelier.WebService.Diagnostics;
|
using StellaOps.Concelier.WebService.Diagnostics;
|
||||||
using StellaOps.Concelier.WebService.Options;
|
using StellaOps.Concelier.WebService.Options;
|
||||||
|
using StellaOps.Ingestion.Telemetry;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.WebService.Extensions;
|
namespace StellaOps.Concelier.WebService.Extensions;
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ public static class TelemetryExtensions
|
|||||||
tracing
|
tracing
|
||||||
.AddSource(JobDiagnostics.ActivitySourceName)
|
.AddSource(JobDiagnostics.ActivitySourceName)
|
||||||
.AddSource(SourceDiagnostics.ActivitySourceName)
|
.AddSource(SourceDiagnostics.ActivitySourceName)
|
||||||
|
.AddSource(IngestionTelemetry.ActivitySourceName)
|
||||||
.AddAspNetCoreInstrumentation()
|
.AddAspNetCoreInstrumentation()
|
||||||
.AddHttpClientInstrumentation();
|
.AddHttpClientInstrumentation();
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ public static class TelemetryExtensions
|
|||||||
metrics
|
metrics
|
||||||
.AddMeter(JobDiagnostics.MeterName)
|
.AddMeter(JobDiagnostics.MeterName)
|
||||||
.AddMeter(SourceDiagnostics.MeterName)
|
.AddMeter(SourceDiagnostics.MeterName)
|
||||||
.AddMeter(IngestionMetrics.MeterName)
|
.AddMeter(IngestionTelemetry.MeterName)
|
||||||
.AddMeter("StellaOps.Concelier.Connector.CertBund")
|
.AddMeter("StellaOps.Concelier.Connector.CertBund")
|
||||||
.AddMeter("StellaOps.Concelier.Connector.Nvd")
|
.AddMeter("StellaOps.Concelier.Connector.Nvd")
|
||||||
.AddMeter("StellaOps.Concelier.Connector.Vndr.Chromium")
|
.AddMeter("StellaOps.Concelier.Connector.Vndr.Chromium")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using StellaOps.Configuration;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.WebService.Options;
|
namespace StellaOps.Concelier.WebService.Options;
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ public sealed class ConcelierOptions
|
|||||||
|
|
||||||
public AdvisoryChunkOptions AdvisoryChunks { get; set; } = new();
|
public AdvisoryChunkOptions AdvisoryChunks { get; set; } = new();
|
||||||
|
|
||||||
|
public StellaOpsCryptoOptions Crypto { get; } = new();
|
||||||
|
|
||||||
public sealed class StorageOptions
|
public sealed class StorageOptions
|
||||||
{
|
{
|
||||||
public string Driver { get; set; } = "mongo";
|
public string Driver { get; set; } = "mongo";
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ builder.Services.AddOptions<ConcelierOptions>()
|
|||||||
})
|
})
|
||||||
.ValidateOnStart();
|
.ValidateOnStart();
|
||||||
|
|
||||||
|
builder.Services.AddStellaOpsCrypto(concelierOptions.Crypto);
|
||||||
|
|
||||||
builder.ConfigureConcelierTelemetry(concelierOptions);
|
builder.ConfigureConcelierTelemetry(concelierOptions);
|
||||||
|
|
||||||
builder.Services.TryAddSingleton<TimeProvider>(_ => TimeProvider.System);
|
builder.Services.TryAddSingleton<TimeProvider>(_ => TimeProvider.System);
|
||||||
@@ -387,6 +389,14 @@ var advisoryIngestEndpoint = app.MapPost("/ingest/advisory", async (
|
|||||||
return authorizationError;
|
return authorizationError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var ingestScope = logger.BeginScope(new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||||
|
{
|
||||||
|
["tenant"] = tenant,
|
||||||
|
["source.vendor"] = ingestRequest.Source.Vendor,
|
||||||
|
["upstream.upstreamId"] = ingestRequest.Upstream.UpstreamId,
|
||||||
|
["contentHash"] = ingestRequest.Upstream.ContentHash ?? "(null)"
|
||||||
|
});
|
||||||
|
|
||||||
AdvisoryRawDocument document;
|
AdvisoryRawDocument document;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -423,12 +433,12 @@ var advisoryIngestEndpoint = app.MapPost("/ingest/advisory", async (
|
|||||||
context.Response.Headers.Location = $"/advisories/raw/{Uri.EscapeDataString(result.Record.Id)}";
|
context.Response.Headers.Location = $"/advisories/raw/{Uri.EscapeDataString(result.Record.Id)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
IngestionMetrics.WriteCounter.Add(1, new[]
|
IngestionMetrics.IngestionWriteCounter.Add(
|
||||||
{
|
1,
|
||||||
new KeyValuePair<string, object?>("tenant", tenant),
|
IngestionMetrics.BuildWriteTags(
|
||||||
new KeyValuePair<string, object?>("source", result.Record.Document.Source.Vendor),
|
tenant,
|
||||||
new KeyValuePair<string, object?>("result", result.Inserted ? "inserted" : "duplicate")
|
ingestRequest.Source.Vendor ?? "(unknown)",
|
||||||
});
|
result.Inserted ? "inserted" : "duplicate"));
|
||||||
|
|
||||||
return JsonResult(response, statusCode);
|
return JsonResult(response, statusCode);
|
||||||
}
|
}
|
||||||
@@ -443,12 +453,12 @@ var advisoryIngestEndpoint = app.MapPost("/ingest/advisory", async (
|
|||||||
string.IsNullOrWhiteSpace(document.Upstream.ContentHash) ? "(empty)" : document.Upstream.ContentHash,
|
string.IsNullOrWhiteSpace(document.Upstream.ContentHash) ? "(empty)" : document.Upstream.ContentHash,
|
||||||
string.Join(',', guardException.Violations.Select(static violation => violation.ErrorCode)));
|
string.Join(',', guardException.Violations.Select(static violation => violation.ErrorCode)));
|
||||||
|
|
||||||
IngestionMetrics.ViolationCounter.Add(1, new[]
|
IngestionMetrics.IngestionWriteCounter.Add(
|
||||||
{
|
1,
|
||||||
new KeyValuePair<string, object?>("tenant", tenant),
|
IngestionMetrics.BuildWriteTags(
|
||||||
new KeyValuePair<string, object?>("source", document.Source.Vendor),
|
tenant,
|
||||||
new KeyValuePair<string, object?>("code", guardException.PrimaryErrorCode)
|
ingestRequest.Source.Vendor ?? "(unknown)",
|
||||||
});
|
"rejected"));
|
||||||
|
|
||||||
return MapAocGuardException(context, guardException);
|
return MapAocGuardException(context, guardException);
|
||||||
}
|
}
|
||||||
@@ -467,25 +477,8 @@ advisoryIngestEndpoint.RequireAocGuard<AdvisoryIngestRequest>(request =>
|
|||||||
return Array.Empty<object?>();
|
return Array.Empty<object?>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var linkset = request.Linkset ?? new AdvisoryLinksetRequest(
|
var guardDocument = AdvisoryRawRequestMapper.Map(request, "guard-tenant", TimeProvider.System);
|
||||||
Array.Empty<string>(),
|
return new object?[] { guardDocument };
|
||||||
Array.Empty<string>(),
|
|
||||||
Array.Empty<string>(),
|
|
||||||
Array.Empty<AdvisoryLinksetReferenceRequest>(),
|
|
||||||
Array.Empty<string>(),
|
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal));
|
|
||||||
|
|
||||||
var payload = new
|
|
||||||
{
|
|
||||||
tenant = "guard-tenant",
|
|
||||||
source = request.Source,
|
|
||||||
upstream = request.Upstream,
|
|
||||||
content = request.Content,
|
|
||||||
identifiers = request.Identifiers,
|
|
||||||
linkset
|
|
||||||
};
|
|
||||||
|
|
||||||
return new object?[] { payload };
|
|
||||||
}, guardOptions: advisoryIngestGuardOptions);
|
}, guardOptions: advisoryIngestGuardOptions);
|
||||||
|
|
||||||
if (authorityConfigured)
|
if (authorityConfigured)
|
||||||
@@ -796,11 +789,9 @@ var aocVerifyEndpoint = app.MapPost("/aoc/verify", async (
|
|||||||
var verificationOutcome = response.Truncated
|
var verificationOutcome = response.Truncated
|
||||||
? "truncated"
|
? "truncated"
|
||||||
: (violationResponses.Length == 0 ? "ok" : "violations");
|
: (violationResponses.Length == 0 ? "ok" : "violations");
|
||||||
IngestionMetrics.VerificationCounter.Add(1, new[]
|
IngestionMetrics.VerificationCounter.Add(
|
||||||
{
|
1,
|
||||||
new KeyValuePair<string, object?>("tenant", tenant),
|
IngestionMetrics.BuildVerifyTags(tenant, verificationOutcome));
|
||||||
new KeyValuePair<string, object?>("result", verificationOutcome)
|
|
||||||
});
|
|
||||||
|
|
||||||
return JsonResult(response);
|
return JsonResult(response);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
@@ -10,6 +9,7 @@ using System.Linq;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
using StellaOps.Cryptography;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.WebService.Services;
|
namespace StellaOps.Concelier.WebService.Services;
|
||||||
|
|
||||||
@@ -28,14 +28,18 @@ internal sealed class OpenApiDiscoveryDocumentProvider
|
|||||||
];
|
];
|
||||||
|
|
||||||
private readonly EndpointDataSource _endpointDataSource;
|
private readonly EndpointDataSource _endpointDataSource;
|
||||||
|
private readonly ICryptoHash _hash;
|
||||||
private readonly object _syncRoot = new();
|
private readonly object _syncRoot = new();
|
||||||
|
|
||||||
private string? _cachedDocumentJson;
|
private string? _cachedDocumentJson;
|
||||||
private string? _cachedEtag;
|
private string? _cachedEtag;
|
||||||
|
|
||||||
public OpenApiDiscoveryDocumentProvider(EndpointDataSource endpointDataSource)
|
public OpenApiDiscoveryDocumentProvider(
|
||||||
|
EndpointDataSource endpointDataSource,
|
||||||
|
ICryptoHash hash)
|
||||||
{
|
{
|
||||||
_endpointDataSource = endpointDataSource;
|
_endpointDataSource = endpointDataSource ?? throw new ArgumentNullException(nameof(endpointDataSource));
|
||||||
|
_hash = hash ?? throw new ArgumentNullException(nameof(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
public (string Payload, string ETag) GetDocument()
|
public (string Payload, string ETag) GetDocument()
|
||||||
@@ -58,7 +62,7 @@ internal sealed class OpenApiDiscoveryDocumentProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
var bytes = Encoding.UTF8.GetBytes(json);
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
var hash = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant();
|
var hash = _hash.ComputeHashHex(bytes);
|
||||||
var computedEtag = $"\"{hash}\"";
|
var computedEtag = $"\"{hash}\"";
|
||||||
|
|
||||||
_cachedDocumentJson = json;
|
_cachedDocumentJson = json;
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Plugin/StellaOps.Plugin.csproj" />
|
||||||
<ProjectReference Include="../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
|
||||||
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
<ProjectReference Include="../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
||||||
|
<ProjectReference Include="../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||||
|
<ProjectReference Include="../../__Libraries/StellaOps.Ingestion.Telemetry/StellaOps.Ingestion.Telemetry.csproj" />
|
||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/StellaOps.Auth.Abstractions.csproj" />
|
||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
|
||||||
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
|
<ProjectReference Include="../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
|
||||||
|
|||||||
@@ -4,12 +4,16 @@
|
|||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
> Docs alignment (2025-10-26): Endpoint expectations + scope requirements detailed in `docs/ingestion/aggregation-only-contract.md` and `docs/security/authority-scopes.md`.
|
> Docs alignment (2025-10-26): Endpoint expectations + scope requirements detailed in `docs/ingestion/aggregation-only-contract.md` and `docs/security/authority-scopes.md`.
|
||||||
> 2025-10-28: Added coverage for pagination, tenancy enforcement, and ingestion/verification metrics; verified guard handling paths end-to-end.
|
> 2025-10-28: Added coverage for pagination, tenancy enforcement, and ingestion/verification metrics; verified guard handling paths end-to-end.
|
||||||
| CONCELIER-WEB-AOC-19-002 `AOC observability` | TODO | Concelier WebService Guild, Observability Guild | CONCELIER-WEB-AOC-19-001 | Emit `ingestion_write_total`, `aoc_violation_total`, latency histograms, and tracing spans (`ingest.fetch/transform/write`, `aoc.guard`). Wire structured logging to include tenant, source vendor, upstream id, and content hash. |
|
| CONCELIER-WEB-AOC-19-002 `AOC observability` | DONE (2025-11-07) | Concelier WebService Guild, Observability Guild | CONCELIER-WEB-AOC-19-001 | Emit `ingestion_write_total`, `aoc_violation_total`, latency histograms, and tracing spans (`ingest.fetch/transform/write`, `aoc.guard`). Wire structured logging to include tenant, source vendor, upstream id, and content hash. |
|
||||||
> Docs alignment (2025-10-26): Metrics/traces/log schema in `docs/observability/observability.md`.
|
> Docs alignment (2025-10-26): Metrics/traces/log schema in `docs/observability/observability.md`.
|
||||||
| CONCELIER-WEB-AOC-19-003 `Schema/guard unit tests` | TODO | QA Guild | CONCELIER-WEB-AOC-19-001 | Add unit tests covering schema validation failures, forbidden field rejections (`ERR_AOC_001/002/006/007`), idempotent upserts, and supersedes chains using deterministic fixtures. |
|
| CONCELIER-WEB-AOC-19-003 `Schema/guard unit tests` | TODO | QA Guild | CONCELIER-WEB-AOC-19-001 | Add unit tests covering schema validation failures, forbidden field rejections (`ERR_AOC_001/002/006/007`), idempotent upserts, and supersedes chains using deterministic fixtures. |
|
||||||
> Docs alignment (2025-10-26): Guard rules + error codes documented in AOC reference §5 and CLI guide.
|
> Docs alignment (2025-10-26): Guard rules + error codes documented in AOC reference §5 and CLI guide.
|
||||||
| CONCELIER-WEB-AOC-19-004 `End-to-end ingest verification` | TODO | Concelier WebService Guild, QA Guild | CONCELIER-WEB-AOC-19-003, CONCELIER-CORE-AOC-19-002 | Create integration tests ingesting large advisory batches (cold/warm) validating linkset enrichment, metrics emission, and reproducible outputs. Capture load-test scripts + doc notes for Offline Kit dry runs. |
|
| CONCELIER-WEB-AOC-19-004 `End-to-end ingest verification` | TODO | Concelier WebService Guild, QA Guild | CONCELIER-WEB-AOC-19-003, CONCELIER-CORE-AOC-19-002 | Create integration tests ingesting large advisory batches (cold/warm) validating linkset enrichment, metrics emission, and reproducible outputs. Capture load-test scripts + doc notes for Offline Kit dry runs. |
|
||||||
> Docs alignment (2025-10-26): Offline verification workflow referenced in `docs/deploy/containers.md` §5.
|
> Docs alignment (2025-10-26): Offline verification workflow referenced in `docs/deploy/containers.md` §5.
|
||||||
|
| CONCELIER-WEB-AOC-19-005 `Chunk evidence regression` | TODO (2025-11-08) | Concelier WebService Guild, QA Guild | CONCELIER-WEB-AOC-19-002 | Fix `/advisories/{key}/chunks` seeded fixtures so AdvisoryChunksEndpoint tests stop returning 404/not-found when raw documents are pre-populated; ensure Mongo migrations no longer emit “Unable to locate advisory_raw documents” during test boot. |
|
||||||
|
| CONCELIER-WEB-AOC-19-006 `Allowlist ingest auth parity` | TODO (2025-11-08) | Concelier WebService Guild | CONCELIER-WEB-AOC-19-002 | Align WebService auth defaults with the test tokens so the allowlisted tenant can create an advisory before forbidden tenants are rejected in `AdvisoryIngestEndpoint_RejectsTenantOutsideAllowlist`. |
|
||||||
|
| CONCELIER-WEB-AOC-19-007 `AOC verify violation codes` | TODO (2025-11-08) | Concelier WebService Guild, QA Guild | CONCELIER-WEB-AOC-19-002 | Update AOC verify logic/fixtures so guard failures produce the expected `ERR_AOC_001` payload (current regression returns `ERR_AOC_004`) while keeping the mapper/guard parity exercised by the new tests. |
|
||||||
|
| CONCELIER-CRYPTO-90-001 `Crypto provider adoption` | DOING (2025-11-08) | Concelier WebService Guild, Security Guild | SEC-CRYPTO-90-003, SEC-CRYPTO-90-004 | Route hashing/signing in OpenAPI discovery, Mirror connectors, and RU advisory adapters through `ICryptoProviderRegistry` so RootPack_RU uses CryptoPro/PKCS#11 keys. Reference `docs/security/crypto-routing-audit-2025-11-07.md`. |
|
||||||
|
|
||||||
## Policy Engine v2
|
## Policy Engine v2
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
## Observability & Forensics (Epic 15)
|
## Observability & Forensics (Epic 15)
|
||||||
| ID | Status | Owner(s) | Depends on | Notes |
|
| ID | Status | Owner(s) | Depends on | Notes |
|
||||||
|----|--------|----------|------------|-------|
|
|----|--------|----------|------------|-------|
|
||||||
| CONCELIER-WEB-OBS-50-001 `Telemetry adoption` | TODO | Concelier WebService Guild | TELEMETRY-OBS-50-001, CONCELIER-OBS-50-001 | Adopt telemetry core in web service host, ensure ingest + read endpoints emit trace/log fields (`tenant_id`, `route`, `decision_effect`), and add correlation IDs to responses. |
|
| CONCELIER-WEB-OBS-50-001 `Telemetry adoption` | DONE (2025-11-07) | Concelier WebService Guild | TELEMETRY-OBS-50-001, CONCELIER-OBS-50-001 | Adopt telemetry core in web service host, ensure ingest + read endpoints emit trace/log fields (`tenant_id`, `route`, `decision_effect`), and add correlation IDs to responses. |
|
||||||
| CONCELIER-WEB-OBS-51-001 `Observability APIs` | TODO | Concelier WebService Guild | CONCELIER-WEB-OBS-50-001, WEB-OBS-51-001 | Surface ingest health metrics, queue depth, and SLO status via `/obs/concelier/health` endpoint for Console widgets, with caching and tenant partitioning. |
|
| CONCELIER-WEB-OBS-51-001 `Observability APIs` | TODO | Concelier WebService Guild | CONCELIER-WEB-OBS-50-001, WEB-OBS-51-001 | Surface ingest health metrics, queue depth, and SLO status via `/obs/concelier/health` endpoint for Console widgets, with caching and tenant partitioning. |
|
||||||
| CONCELIER-WEB-OBS-52-001 `Timeline streaming` | TODO | Concelier WebService Guild | CONCELIER-WEB-OBS-50-001, TIMELINE-OBS-52-003 | Provide SSE stream `/obs/concelier/timeline` bridging to Timeline Indexer with paging tokens, guardrails, and audit logging. |
|
| CONCELIER-WEB-OBS-52-001 `Timeline streaming` | TODO | Concelier WebService Guild | CONCELIER-WEB-OBS-50-001, TIMELINE-OBS-52-003 | Provide SSE stream `/obs/concelier/timeline` bridging to Timeline Indexer with paging tokens, guardrails, and audit logging. |
|
||||||
| CONCELIER-WEB-OBS-53-001 `Evidence locker integration` | TODO | Concelier WebService Guild, Evidence Locker Guild | CONCELIER-OBS-53-001, EVID-OBS-53-003 | Add `/evidence/advisories/*` routes invoking evidence locker snapshots, verifying tenant scopes (`evidence:read`), and returning signed manifest metadata. |
|
| CONCELIER-WEB-OBS-53-001 `Evidence locker integration` | TODO | Concelier WebService Guild, Evidence Locker Guild | CONCELIER-OBS-53-001, EVID-OBS-53-003 | Add `/evidence/advisories/*` routes invoking evidence locker snapshots, verifying tenant scopes (`evidence:read`), and returning signed manifest metadata. |
|
||||||
|
|||||||
@@ -187,6 +187,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "__Analyzers", "__Analyzers"
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.Analyzers", "__Analyzers\StellaOps.Concelier.Analyzers\StellaOps.Concelier.Analyzers.csproj", "{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Concelier.Analyzers", "__Analyzers\StellaOps.Concelier.Analyzers\StellaOps.Concelier.Analyzers.csproj", "{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Ingestion.Telemetry", "..\__Libraries\StellaOps.Ingestion.Telemetry\StellaOps.Ingestion.Telemetry.csproj", "{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -1265,6 +1267,18 @@ Global
|
|||||||
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x64.Build.0 = Release|Any CPU
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.ActiveCfg = Release|Any CPU
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.Build.0 = Release|Any CPU
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -1349,5 +1363,6 @@ Global
|
|||||||
{9006A5A2-01D8-4A70-AEA7-B7B1987C4A62} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
{9006A5A2-01D8-4A70-AEA7-B7B1987C4A62} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
||||||
{664A2577-6DA1-42DA-A213-3253017FA4BF} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
{664A2577-6DA1-42DA-A213-3253017FA4BF} = {56BCE1BF-7CBA-7CE8-203D-A88051F1D642}
|
||||||
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F} = {176B5A8A-7857-3ECD-1128-3C721BC7F5C6}
|
{39C1D44C-389F-4502-ADCF-E4AC359E8F8F} = {176B5A8A-7857-3ECD-1128-3C721BC7F5C6}
|
||||||
|
{85D215EC-DCFE-4F7F-BB07-540DCF66BE8C} = {41F15E67-7190-CF23-3BC4-77E87134CADD}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public sealed class CccsConnector : IFeedConnector
|
|||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Uri CanonicalBaseUri = new("https://www.cyber.gc.ca", UriKind.Absolute);
|
||||||
private const string DtoSchemaVersion = "cccs.dto.v1";
|
private const string DtoSchemaVersion = "cccs.dto.v1";
|
||||||
|
|
||||||
private readonly CccsFeedClient _feedClient;
|
private readonly CccsFeedClient _feedClient;
|
||||||
@@ -484,23 +485,36 @@ public sealed class CccsConnector : IFeedConnector
|
|||||||
|
|
||||||
private static string BuildDocumentUri(CccsFeedItem item, CccsFeedEndpoint feed)
|
private static string BuildDocumentUri(CccsFeedItem item, CccsFeedEndpoint feed)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(item.Url))
|
var candidate = item.Url?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(candidate))
|
||||||
{
|
{
|
||||||
if (Uri.TryCreate(item.Url, UriKind.Absolute, out var absolute))
|
if (Uri.TryCreate(candidate, UriKind.Absolute, out var absolute))
|
||||||
|
{
|
||||||
|
if (IsHttpScheme(absolute.Scheme))
|
||||||
{
|
{
|
||||||
return absolute.ToString();
|
return absolute.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseUri = new Uri("https://www.cyber.gc.ca", UriKind.Absolute);
|
candidate = absolute.PathAndQuery;
|
||||||
if (Uri.TryCreate(baseUri, item.Url, out var combined))
|
if (!string.IsNullOrEmpty(absolute.Fragment))
|
||||||
|
{
|
||||||
|
candidate += absolute.Fragment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(candidate) && Uri.TryCreate(CanonicalBaseUri, candidate, out var combined))
|
||||||
{
|
{
|
||||||
return combined.ToString();
|
return combined.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"https://www.cyber.gc.ca/api/cccs/threats/{feed.Language}/{item.Nid}";
|
return new Uri(CanonicalBaseUri, $"/api/cccs/threats/{feed.Language}/{item.Nid}").ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsHttpScheme(string? scheme)
|
||||||
|
=> string.Equals(scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private static CccsRawAdvisoryDocument CreateRawDocument(CccsFeedItem item, CccsFeedEndpoint feed, IReadOnlyDictionary<int, string> taxonomy)
|
private static CccsRawAdvisoryDocument CreateRawDocument(CccsFeedItem item, CccsFeedEndpoint feed, IReadOnlyDictionary<int, string> taxonomy)
|
||||||
{
|
{
|
||||||
var language = string.IsNullOrWhiteSpace(item.Language) ? feed.Language : item.Language!.Trim();
|
var language = string.IsNullOrWhiteSpace(item.Language) ? feed.Language : item.Language!.Trim();
|
||||||
|
|||||||
@@ -126,8 +126,13 @@ public sealed class CccsFeedEndpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
var language = Uri.GetQueryParameterValueOrDefault("lang", Language);
|
var language = Uri.GetQueryParameterValueOrDefault("lang", Language);
|
||||||
var builder = $"https://www.cyber.gc.ca/api/cccs/taxonomy/v1/get?lang={language}&vocabulary=cccs_alert_type";
|
var taxonomyBuilder = new UriBuilder(Uri)
|
||||||
return new Uri(builder, UriKind.Absolute);
|
{
|
||||||
|
Path = "/api/cccs/taxonomy/v1/get",
|
||||||
|
Query = $"lang={language}&vocabulary=cccs_alert_type"
|
||||||
|
};
|
||||||
|
|
||||||
|
return taxonomyBuilder.Uri;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,9 +353,11 @@ public sealed class CccsHtmlParser
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Uri.TryCreate(href, UriKind.Absolute, out var absolute))
|
var candidate = href.Trim();
|
||||||
|
var hasAbsolute = Uri.TryCreate(candidate, UriKind.Absolute, out var absolute);
|
||||||
|
if (!hasAbsolute || string.Equals(absolute.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (baseUri is null || !Uri.TryCreate(baseUri, href, out absolute))
|
if (baseUri is null || !Uri.TryCreate(baseUri, candidate, out absolute))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,12 +319,19 @@ public sealed class KisaDetailParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
var headerRow = labelCell.ParentElement as IHtmlTableRowElement;
|
var headerRow = labelCell.ParentElement as IHtmlTableRowElement;
|
||||||
var columnIndex = labelCell.CellIndex;
|
var columnIndex = headerRow is null
|
||||||
|
? -1
|
||||||
|
: Array.FindIndex(headerRow.Cells.ToArray(), cell => ReferenceEquals(cell, labelCell));
|
||||||
if (headerRow is null)
|
if (headerRow is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (columnIndex < 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var rows = ownerTable.Rows.ToArray();
|
var rows = ownerTable.Rows.ToArray();
|
||||||
var headerIndex = Array.FindIndex(rows, row => ReferenceEquals(row, headerRow));
|
var headerIndex = Array.FindIndex(rows, row => ReferenceEquals(row, headerRow));
|
||||||
if (headerIndex < 0)
|
if (headerIndex < 0)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using System.Collections.Immutable;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@@ -21,6 +20,7 @@ using StellaOps.Concelier.Storage.Mongo.Advisories;
|
|||||||
using StellaOps.Concelier.Storage.Mongo.Documents;
|
using StellaOps.Concelier.Storage.Mongo.Documents;
|
||||||
using StellaOps.Concelier.Storage.Mongo.Dtos;
|
using StellaOps.Concelier.Storage.Mongo.Dtos;
|
||||||
using StellaOps.Plugin;
|
using StellaOps.Plugin;
|
||||||
|
using StellaOps.Cryptography;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.Connector.Ru.Bdu;
|
namespace StellaOps.Concelier.Connector.Ru.Bdu;
|
||||||
|
|
||||||
@@ -46,6 +46,7 @@ public sealed class RuBduConnector : IFeedConnector
|
|||||||
|
|
||||||
private readonly string _cacheDirectory;
|
private readonly string _cacheDirectory;
|
||||||
private readonly string _archiveCachePath;
|
private readonly string _archiveCachePath;
|
||||||
|
private readonly ICryptoHash _hash;
|
||||||
|
|
||||||
public RuBduConnector(
|
public RuBduConnector(
|
||||||
SourceFetchService fetchService,
|
SourceFetchService fetchService,
|
||||||
@@ -57,7 +58,8 @@ public sealed class RuBduConnector : IFeedConnector
|
|||||||
IOptions<RuBduOptions> options,
|
IOptions<RuBduOptions> options,
|
||||||
RuBduDiagnostics diagnostics,
|
RuBduDiagnostics diagnostics,
|
||||||
TimeProvider? timeProvider,
|
TimeProvider? timeProvider,
|
||||||
ILogger<RuBduConnector> logger)
|
ILogger<RuBduConnector> logger,
|
||||||
|
ICryptoHash cryptoHash)
|
||||||
{
|
{
|
||||||
_fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
|
_fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
|
||||||
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
|
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
|
||||||
@@ -70,6 +72,7 @@ public sealed class RuBduConnector : IFeedConnector
|
|||||||
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_hash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
||||||
_cacheDirectory = ResolveCacheDirectory(_options.CacheDirectory);
|
_cacheDirectory = ResolveCacheDirectory(_options.CacheDirectory);
|
||||||
_archiveCachePath = Path.Combine(_cacheDirectory, "vulxml.zip");
|
_archiveCachePath = Path.Combine(_cacheDirectory, "vulxml.zip");
|
||||||
EnsureCacheDirectory();
|
EnsureCacheDirectory();
|
||||||
@@ -398,7 +401,7 @@ public sealed class RuBduConnector : IFeedConnector
|
|||||||
}
|
}
|
||||||
|
|
||||||
var payload = JsonSerializer.SerializeToUtf8Bytes(dto, SerializerOptions);
|
var payload = JsonSerializer.SerializeToUtf8Bytes(dto, SerializerOptions);
|
||||||
var sha = Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant();
|
var sha = _hash.ComputeHashHex(payload);
|
||||||
var documentUri = BuildDocumentUri(dto.Identifier);
|
var documentUri = BuildDocumentUri(dto.Identifier);
|
||||||
|
|
||||||
var existing = await _documentStore.FindBySourceAndUriAsync(SourceName, documentUri, cancellationToken).ConfigureAwait(false);
|
var existing = await _documentStore.FindBySourceAndUriAsync(SourceName, documentUri, cancellationToken).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||||
|
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -4,7 +4,6 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@@ -21,6 +20,7 @@ using StellaOps.Concelier.Storage.Mongo.Advisories;
|
|||||||
using StellaOps.Concelier.Storage.Mongo.Documents;
|
using StellaOps.Concelier.Storage.Mongo.Documents;
|
||||||
using StellaOps.Concelier.Storage.Mongo.Dtos;
|
using StellaOps.Concelier.Storage.Mongo.Dtos;
|
||||||
using StellaOps.Plugin;
|
using StellaOps.Plugin;
|
||||||
|
using StellaOps.Cryptography;
|
||||||
|
|
||||||
namespace StellaOps.Concelier.Connector.Ru.Nkcki;
|
namespace StellaOps.Concelier.Connector.Ru.Nkcki;
|
||||||
|
|
||||||
@@ -58,6 +58,7 @@ public sealed class RuNkckiConnector : IFeedConnector
|
|||||||
private readonly RuNkckiDiagnostics _diagnostics;
|
private readonly RuNkckiDiagnostics _diagnostics;
|
||||||
private readonly ILogger<RuNkckiConnector> _logger;
|
private readonly ILogger<RuNkckiConnector> _logger;
|
||||||
private readonly string _cacheDirectory;
|
private readonly string _cacheDirectory;
|
||||||
|
private readonly ICryptoHash _hash;
|
||||||
|
|
||||||
private readonly HtmlParser _htmlParser = new();
|
private readonly HtmlParser _htmlParser = new();
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ public sealed class RuNkckiConnector : IFeedConnector
|
|||||||
IOptions<RuNkckiOptions> options,
|
IOptions<RuNkckiOptions> options,
|
||||||
RuNkckiDiagnostics diagnostics,
|
RuNkckiDiagnostics diagnostics,
|
||||||
TimeProvider? timeProvider,
|
TimeProvider? timeProvider,
|
||||||
ILogger<RuNkckiConnector> logger)
|
ILogger<RuNkckiConnector> logger,
|
||||||
|
ICryptoHash cryptoHash)
|
||||||
{
|
{
|
||||||
_fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
|
_fetchService = fetchService ?? throw new ArgumentNullException(nameof(fetchService));
|
||||||
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
|
_rawDocumentStorage = rawDocumentStorage ?? throw new ArgumentNullException(nameof(rawDocumentStorage));
|
||||||
@@ -84,6 +86,7 @@ public sealed class RuNkckiConnector : IFeedConnector
|
|||||||
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
||||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
|
_hash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
||||||
_cacheDirectory = ResolveCacheDirectory(_options.CacheDirectory);
|
_cacheDirectory = ResolveCacheDirectory(_options.CacheDirectory);
|
||||||
EnsureCacheDirectory();
|
EnsureCacheDirectory();
|
||||||
}
|
}
|
||||||
@@ -597,7 +600,7 @@ public sealed class RuNkckiConnector : IFeedConnector
|
|||||||
}
|
}
|
||||||
|
|
||||||
var payload = JsonSerializer.SerializeToUtf8Bytes(dto, SerializerOptions);
|
var payload = JsonSerializer.SerializeToUtf8Bytes(dto, SerializerOptions);
|
||||||
var sha = Convert.ToHexString(SHA256.HashData(payload)).ToLowerInvariant();
|
var sha = _hash.ComputeHashHex(payload);
|
||||||
var documentUri = BuildDocumentUri(dto);
|
var documentUri = BuildDocumentUri(dto);
|
||||||
|
|
||||||
var existing = await _documentStore.FindBySourceAndUriAsync(SourceName, documentUri, cancellationToken).ConfigureAwait(false);
|
var existing = await _documentStore.FindBySourceAndUriAsync(SourceName, documentUri, cancellationToken).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||||
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
<ProjectReference Include="../StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||||
|
<ProjectReference Include="../../../__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user