Add determinism tests for verdict artifact generation and update SHA256 sums script

- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering.
- Created helper methods for generating sample verdict inputs and computing canonical hashes.
- Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics.
- Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
This commit is contained in:
StellaOps Bot
2025-12-24 02:17:34 +02:00
parent e59921374e
commit 7503c19b8f
390 changed files with 37389 additions and 5380 deletions

View File

@@ -1,8 +1,8 @@
# AirGap Docs Index
- Time anchors & staleness: `time-anchor-scaffold.md`, `staleness-and-time.md`, `time-config-sample.json`, `time-api.md`, `time-anchor-verification-gap.md`.
- Importer scaffolds: `importer-scaffold.md`, `bundle-repositories.md`.
- Controller/diagnostics: `controller-scaffold.md`, `sealed-startup-diagnostics.md`.
- Time anchors & staleness: `staleness-and-time.md`, `time-config-sample.json`, `time-api.md`, `time-anchor-verification-gap.md`.
- Import pipeline: `importer.md`, `bundle-repositories.md`.
- Controller/diagnostics: `controller.md`, `sealed-startup-diagnostics.md`.
- Portable evidence flows: `portable-evidence.md`.
Use these as the front door for AirGap module work; update alongside code changes.

View File

@@ -1 +0,0 @@
# Placeholder hashes; replace with real asset sums when inputs arrive

View File

@@ -296,7 +296,7 @@ src/Authority/
| Document | Sprint | Updates |
|----------|--------|---------|
| `docs/airgap/importer-scaffold.md` | 0338 | Add monotonicity, quarantine sections |
| `docs/airgap/importer.md` | 0338 | Monotonicity + quarantine reference |
| `docs/airgap/runbooks/quarantine-investigation.md` | 0338 | New runbook |
| `docs/modules/cli/commands/offline.md` | 0339 | New command reference |
| `docs/modules/cli/guides/airgap.md` | 0339 | Update with CLI examples |
@@ -336,4 +336,4 @@ src/Authority/
- [14-Dec-2025 Offline and Air-Gap Technical Reference](../product-advisories/14-Dec-2025%20-%20Offline%20and%20Air-Gap%20Technical%20Reference.md)
- [Air-Gap Mode Playbook](./airgap-mode.md)
- [Offline Kit Documentation](../24_OFFLINE_KIT.md)
- [Importer Scaffold](./importer-scaffold.md)
- [Importer](./importer.md)

View File

@@ -1,54 +0,0 @@
# AirGap Controller Scaffold (Draft) - PREP-AIRGAP-CTL-56-001/002/57-001/57-002/58-001
Status: Draft (2025-11-20)
Owners: AirGap Controller Guild / Observability Guild / AirGap Time Guild / DevOps Guild
Scope: Define the baseline project skeleton, APIs, telemetry, and staleness fields needed to unblock controller tasks 56-001 through 58-001.
## 1) Project layout
- Project: `src/AirGap/StellaOps.AirGap.Controller` (net10.0, minimal API host).
- Tests: `tests/AirGap/StellaOps.AirGap.Controller.Tests` with xunit + deterministic time provider.
- Shared contracts: DTOs under `Endpoints/Contracts`, domain state under `Domain/AirGapState.cs`.
- Persistence: in-memory state store only (no external DB dependency). Postgres-backed persistence will follow in a later sprint.
- Tests: run entirely in-memory; no Mongo/OpenSSL shims required.
## 2) State model
- In-memory state record per tenant: `id` (const `singleton`), `tenant_id`, `sealed` (bool), `policy_hash`, `time_anchor` (nullable), `last_transition_at` (UTC), `staleness_budget_seconds` (int?, optional per bundle), `notes`.
- In-memory cache with monotonic timestamp to avoid stale reads; cache invalidated on transitions.
- Persistence roadmap: swap in a Postgres-backed store with equivalent singleton and tenant scoping; Mongo wiring has been removed.
## 3) Endpoints (56-002 baseline)
- `GET /system/airgap/status` -> returns current state + staleness summary:
- `{sealed, policy_hash, time_anchor:{source, anchored_at, drift_seconds}, staleness:{age_seconds, warning_seconds, breach_seconds, seconds_remaining}, last_transition_at}`.
- `POST /system/airgap/seal` -> body `{policy_hash, time_anchor?, staleness_budget_seconds?}`; requires Authority scopes `airgap:seal` + `effective:write`.
- `POST /system/airgap/unseal` -> requires `airgap:seal`.
- Validation: reject seal if missing `policy_hash` or time anchor when platform requires sealed mode.
## 4) Telemetry (57-002)
- Structured logs: `airgap.sealed`, `airgap.unsealed`, `airgap.status.read` with tenant_id, policy_hash, time_anchor_source, drift_seconds.
- Metrics (Prometheus/OpenTelemetry): counters `airgap_seal_total`, `airgap_unseal_total`, `airgap_startup_blocked_total`; gauges `airgap_time_anchor_age_seconds`, `airgap_staleness_budget_seconds`.
- Timeline events (Observability stream): `airgap.sealed`, `airgap.unsealed` with correlation_id.
### Startup diagnostics wiring (57-001)
- Config section `AirGap:Startup` now drives sealed-mode startup validation:
- `TenantId` (default `default`).
- `EgressAllowlist` (array; required when sealed).
- `Trust:RootJsonPath`, `Trust:SnapshotJsonPath`, `Trust:TimestampJsonPath` (all required when sealed; parsed via TUF validator).
- `Rotation:ActiveKeys`, `Rotation:PendingKeys`, `Rotation:ApproverIds` (base64-encoded keys; dual approval enforced when pending keys exist).
- Failures raise `sealed-startup-blocked:<reason>` and increment `airgap_startup_blocked_total{reason}`.
## 5) Staleness & time (58-001)
- Staleness computation: `drift_seconds = now_utc - time_anchor.anchored_at`; `seconds_remaining = max(0, staleness_budget_seconds - drift_seconds)`.
- Time anchors accept Roughtime or RFC3161 token parsed via AirGap Time component (imported service).
- Status response includes drift and remaining budget; sealed mode refuses to run if budget exceeded.
## 6) Determinism & offline rules
- No external network calls; time source injected `IClock` seeded in tests.
- All timestamps RFC3339 UTC; responses sorted properties (serializer config).
## 7) Open decisions
- Final scopes list (Authority) for status read vs seal/unseal.
- Whether to require dual authorization for `seal` (two-man rule) in sealed environments.
- Retention/rotation policy for `airgap_state` audit trail (append-only vs mutation).
## 8) Handoff
This document satisfies PREP-AIRGAP-CTL-56-001 through 58-001. Update once Authority scopes and time-anchor token format are finalized; then promote to v1 schema doc and wire tests accordingly.

100
docs/airgap/controller.md Normal file
View File

@@ -0,0 +1,100 @@
# AirGap Controller
The AirGap Controller is the tenant-scoped state keeper for sealed-mode operation. It records whether an installation is sealed, what policy hash is active, which time anchor is in force, and what staleness budgets apply.
For workflow context, start at `docs/airgap/overview.md` and `docs/airgap/airgap-mode.md`.
## Responsibilities
- Maintain the current AirGap state per tenant (sealed/unsealed, policy hash, time anchor, staleness budgets).
- Provide a deterministic, auditable status snapshot for operators and automation.
- Enforce sealed/unsealed transitions via Authority scopes.
- Emit telemetry signals suitable for dashboards and forensics timelines.
Non-goals:
- Bundle signature validation and import staging (owned by the importer; see `docs/airgap/importer.md`).
- Cryptographic signing (Signer/Attestor).
## API
Base route group: `/system/airgap` (requires authorization).
### `GET /system/airgap/status`
Required scope: `airgap:status:read`
Response: `AirGapStatusResponse` (current state + staleness evaluation).
Notes:
- Tenant routing uses `x-tenant-id` (defaults to `default` if absent).
- `driftSeconds` and `secondsRemaining` are derived from the active time anchor and staleness budget evaluation.
- `contentStaleness` contains per-category staleness evaluations (clients should treat keys as case-insensitive).
### `POST /system/airgap/seal`
Required scope: `airgap:seal`
Body: `SealRequest`
- `policyHash` (required): binds the sealed state to a specific policy revision.
- `timeAnchor` (optional): time anchor record (from the AirGap Time service).
- `stalenessBudget` (optional): default staleness budget.
- `contentBudgets` (optional): per-category staleness budgets (e.g., `advisories`, `vex`, `scanner`).
Behavior:
- Rejects requests missing `policyHash` (`400 { \"error\": \"policy_hash_required\" }`).
- Records the sealed state and returns an updated status snapshot.
### `POST /system/airgap/unseal`
Required scope: `airgap:seal`
Behavior:
- Clears the sealed state and returns an updated status snapshot.
- Staleness is returned as `Unknown` after unseal (clients should treat this as "not applicable").
### `POST /system/airgap/verify`
Required scope: `airgap:verify`
Purpose: verify replay / bundle verification requests against the currently active AirGap state.
## State model (per tenant)
Canonical fields captured by the controller (see `src/AirGap/StellaOps.AirGap.Controller`):
- `tenantId`
- `sealed`
- `policyHash` (nullable)
- `timeAnchor` (`TimeAnchor`, may be `Unknown`)
- `stalenessBudget` (`StalenessBudget`)
- `contentBudgets` (`Dictionary<string, StalenessBudget>`)
- `driftBaselineSeconds` (baseline used to keep drift evaluation stable across transitions)
- `lastTransitionAt` (UTC)
Determinism requirements:
- Use UTC timestamps only.
- Use ordinal comparisons for keys and stable serialization settings for JSON responses.
- Never infer state from wall-clock behavior other than the injected `TimeProvider`.
## Telemetry
The controller emits:
- Structured logs: `airgap.status.read`, `airgap.sealed`, `airgap.unsealed`, `airgap.verify` (include `tenant_id`, `policy_hash`, and drift/staleness).
- Metrics: `airgap_seal_total`, `airgap_unseal_total`, `airgap_status_read_total`, and gauges for drift/budget/remaining seconds.
- Timeline events (optional): `airgap.sealed`, `airgap.unsealed`, `airgap.staleness.warning`, `airgap.staleness.breach`.
## References
- `docs/airgap/overview.md`
- `docs/airgap/sealed-startup-diagnostics.md`
- `docs/airgap/staleness-and-time.md`
- `docs/airgap/time-api.md`
- `docs/airgap/importer.md`

View File

@@ -1,45 +0,0 @@
# AirGap Importer Scaffold (prep for AIRGAP-IMP-56-001/56-002/58-002)
## Scope for prep
- Provide minimal project and test scaffolds so downstream implementation can wire DSSE, TUF, Merkle validation, and audit logging without redoing structure.
- Capture trust-root inputs required (bundle path, signing keys, allowed algorithms, validity window).
## What landed (2025-11-20)
- New project: `src/AirGap/StellaOps.AirGap.Importer/StellaOps.AirGap.Importer.csproj` (net10.0, deterministic-only dependencies).
- Planning layer: `BundleImportPlanner` emits deterministic plan steps and early validation reasons (`bundle-path-required`, `trust-roots-required`, `invalid-trust-window`).
- Contracts: `TrustRootConfig` record carries root bundle path, trusted key fingerprints, allowed algorithms, and optional validity window.
- Validation shape: `BundleValidationResult` centralises success/failure reasons for replay/capture.
- Tests: `tests/AirGap/StellaOps.AirGap.Importer.Tests` validate planner behavior without external feeds.
## Updates (2025-11-20)
- Added DSSE verifier (RSA-PSS/SHA256) with PAE encoding + trusted key fingerprint checks.
- Added TUF metadata validator (root/snapshot/timestamp) with hash consistency guard.
- Added deterministic Merkle root calculator for bundle object staging.
- Expanded tests for DSSE, TUF, Merkle helpers.
- Added trust store + root rotation policy (dual approval) and import validator that coordinates DSSE/TUF/Merkle/rotation checks.
## Updates (2025-12-15)
- Added monotonicity enforcement primitives under `src/AirGap/StellaOps.AirGap.Importer/Versioning/` (`BundleVersion`, `IVersionMonotonicityChecker`, `IBundleVersionStore`).
- Added file-based quarantine service under `src/AirGap/StellaOps.AirGap.Importer/Quarantine/` (`IQuarantineService`, `FileSystemQuarantineService`, `QuarantineOptions`).
- Updated `ImportValidator` to include monotonicity checks, force-activate support (requires reason), and quarantine on validation failures.
- Added Postgres-backed bundle version tracking in `src/AirGap/StellaOps.AirGap.Storage.Postgres/Repositories/PostgresBundleVersionStore.cs` and registration via `src/AirGap/StellaOps.AirGap.Storage.Postgres/ServiceCollectionExtensions.cs`.
- Updated tests in `tests/AirGap/StellaOps.AirGap.Importer.Tests` to cover versioning/quarantine and the new import validator behavior.
## Next implementation hooks
- Replace placeholder plan with actual DSSE + TUF verifiers; keep step ordering stable.
- Feed trust roots from sealed-mode config and Evidence Locker bundles (once available) before allowing imports.
- Record audit trail for each plan step (success/failure) and a Merkle root of staged content.
## Determinism/air-gap posture
- No network dependencies; BCL + `Microsoft.Extensions.*` only.
- Tests use cached local NuGet feed (`local-nugets/`).
- Plan steps are ordered list; do not reorder without bumping downstream replay expectations.
## How to consume
```bash
# run tests offline once feed is hydrated
DOTNET_NOLOGO=1 dotnet test tests/AirGap/StellaOps.AirGap.Importer.Tests/StellaOps.AirGap.Importer.Tests.csproj --no-build
```
## Owners
- AirGap Importer Guild / Security Guild (per sprint 0510).

77
docs/airgap/importer.md Normal file
View File

@@ -0,0 +1,77 @@
# AirGap Importer
The AirGap Importer verifies and ingests offline bundles (mirror, bootstrap, evidence kits) into a sealed or constrained deployment. It fails closed by default: imports are rejected when verification fails, and failures are diagnosable offline.
This document describes importer behavior and its key building blocks. For bundle formats and operational workflow, see `docs/airgap/offline-bundle-format.md`, `docs/airgap/mirror-bundles.md`, and `docs/airgap/operations.md`.
## Responsibilities
- Verify bundle integrity and authenticity (DSSE signatures; optional TUF metadata where applicable).
- Enforce monotonicity (prevent version rollback unless explicitly force-activated with a recorded reason).
- Stage verified content into deterministic layouts (catalog + item repository + object-store paths).
- Quarantine failed bundles for forensic analysis with deterministic logs and metadata.
- Emit an audit trail for every dry-run and import attempt (success or failure).
## Verification pipeline (conceptual)
1. **Plan**: build an ordered list of validation/ingest steps for the bundle (`BundleImportPlanner`).
2. **Validate signatures**: verify DSSE envelopes and trusted key fingerprints.
3. **Validate metadata** (when present): verify TUF root/snapshot/timestamp consistency against trust roots.
4. **Compute deterministic roots**: compute a Merkle root over staged bundle items (stable ordering).
5. **Check monotonicity**: ensure the incoming bundle version is newer than the currently active version.
6. **Quarantine on failure**: preserve the bundle + verification log and emit a stable failure reason code.
7. **Commit**: write catalog/item entries and activation record; emit audit/timeline events.
The step order must remain stable; if steps change, treat it as a contract change and update CLI/UI guidance.
## Quarantine
When verification fails, the importer quarantines the bundle with enough information to debug offline.
Typical structure:
- `/updates/quarantine/<tenantId>/<timestamp>-<reason>-<id>/`
- `bundle.tar.zst` (original)
- `manifest.json` (if extracted)
- `verification.log` (deterministic, no machine-specific paths)
- `failure-reason.txt` (human-readable)
- `quarantine.json` (structured metadata: tenant, reason, timestamps, sizes, hashes)
Operational expectations:
- Quarantine is bounded: enforce per-tenant quota + TTL cleanup.
- Listing is deterministic: sort by `quarantined_at` then `quarantine_id` (ordinal).
## Version monotonicity
Rollback resistance is enforced via:
- A per-tenant version store (`IBundleVersionStore`) backed by Postgres in production.
- A monotonicity checker (`IVersionMonotonicityChecker`) that compares incoming bundle versions to the active version.
- Optional force-activate path requiring a human reason, stored alongside the activation record.
## Storage model
The importer writes deterministic metadata that other components can query:
- **Bundle catalog**: (tenant, bundle_id, digest, imported_at_utc, content paths).
- **Bundle items**: (tenant, bundle_id, path, digest, size).
For the logical schema and deterministic ordering rules, see `docs/airgap/bundle-repositories.md`.
## Telemetry and auditing
Minimum signals:
- Counters: imports attempted/succeeded/failed, dry-runs, quarantines created, monotonicity failures, force-activations.
- Structured logs with stable reason codes (e.g., `dsse_signature_invalid`, `tuf_root_invalid`, `merkle_mismatch`, `version_rollback_blocked`).
- Audit emission: include tenant, bundle_id, digest, operator identity, and whether sealed mode was active.
## References
- `docs/airgap/offline-bundle-format.md`
- `docs/airgap/mirror-bundles.md`
- `docs/airgap/bundle-repositories.md`
- `docs/airgap/operations.md`
- `docs/airgap/controller.md`

View File

@@ -29,4 +29,4 @@ Display a top-of-console banner when `sealed=true`:
- `docs/airgap/airgap-mode.md` — deeper policy shapes per mode.
- `docs/airgap/bundle-repositories.md` — mirror/bootstrap bundle structure.
- `docs/airgap/staleness-and-time.md` — time anchors and staleness checks.
- `docs/airgap/controller-scaffold.md` / `importer-scaffold.md` — implementation scaffolds.
- `docs/airgap/controller.md` / `docs/airgap/importer.md` — controller + importer references.

View File

@@ -1,20 +0,0 @@
# Controller Scaffold Prep — PREP-AIRGAP-CTL-56-001 / 56-002
Status: Draft (2025-11-20)
Owners: AirGap Controller Guild · DevOps Guild
Scope: Provide the controller scaffold + status API contract so AIRGAP-CTL-56-001/56-002 can proceed.
## Deliverables included
- Service scaffold described in `docs/airgap/controller-scaffold.md` (project layout, DI wiring, config keys, auth scopes).
- Baseline status/seal endpoints sketch:
- `GET /system/airgap/status``{sealed, policy_hash?, staleness_seconds?, time_anchor_id?, bundle_id?}`
- `POST /system/airgap/seal` (body: `{policy_hash, reason}`) → returns new state; requires `airgap:seal` scope.
- Determinism & offline posture: no external calls; state persisted via `airgap_state` store; timestamps UTC; subject ordering deterministic.
## Next steps for implementation
- Generate controller project under `src/AirGap/StellaOps.AirGap.Controller` per scaffold.
- Wire Authority scope checks (`airgap:seal`, `airgap:status:read`).
- Add sealed-mode guard middleware and timeline events per `docs/airgap/sealed-startup-diagnostics.md` once integrated.
## Handoff
Use this prep doc to satisfy PREP-AIRGAP-CTL-56-001 and PREP-AIRGAP-CTL-56-002. Update if scope changes; otherwise move tasks to DONE.

View File

@@ -1,25 +0,0 @@
# Staleness & Drift Prep — PREP-AIRGAP-CTL-58-001-BLOCKED-ON-57-002
Status: Draft (2025-11-20)
Owners: AirGap Controller Guild · AirGap Time Guild
Scope: Capture the staleness/drift requirements for controller status once seal/unseal telemetry (57-002) is available.
## Inputs
- Time anchor ingestion from Time service (Roughtime/RFC3161) via `time_anchor_id`, `drift_seconds`, `staleness_budget_seconds`.
- Bundle metadata from importer (bundle_id, manifest hash, generated_at).
## Proposed status enrichments
- Add fields to `GET /system/airgap/status`:
- `staleness_seconds_remaining`
- `bundle_id`
- `time_anchor_id`
- `drift_seconds`
- Compute `staleness_seconds_remaining = staleness_budget_seconds - drift_seconds` (floor at 0).
- Determinism: calculations purely from stored numbers; no wall-clock calls beyond persisted anchor timestamps.
## Observability
- Metrics: `airgap_staleness_seconds{tenant}` (gauge), `airgap_drift_seconds{tenant}`.
- Timeline events emitted when budgets breached: `airgap.staleness.threshold`.
## Handoff
Use this prep note to satisfy PREP-AIRGAP-CTL-58-001. After integrating sealed-startup telemetry and time anchor verification, implement the above fields and metrics, then mark the implementation task DOING.

View File

@@ -1,41 +0,0 @@
# AirGap Time Anchor Scaffold (prep for AIRGAP-TIME-57-001)
## Scope for prep
- Provide a deterministic parsing surface for signed time tokens (Roughtime, RFC3161) so staleness calculations and telemetry wiring can start without full crypto yet.
## What landed (2025-11-20)
- New project: `src/AirGap/StellaOps.AirGap.Time/StellaOps.AirGap.Time.csproj` (net10.0), BCL-only.
- Model: `TimeAnchor` canonical record (anchor time, source, format, signature fingerprint placeholder, token digest).
- Parser: `TimeTokenParser` with deterministic SHA-256 digest derivation and structured success/failure reasons.
- Result envelope: `TimeAnchorValidationResult` and `TimeTokenFormat` enum.
- Tests: `tests/AirGap/StellaOps.AirGap.Time.Tests` cover empty-token failure and digest production for Roughtime tokens.
## Updates (2025-11-20)
- Added staleness calculator (`StalenessCalculator`) and budgets/evaluation models to derive warning/breach states deterministically.
- Added `TimeAnchorLoader` to ingest hex-encoded tokens from fixtures; sample tokens placed under `src/AirGap/StellaOps.AirGap.Time/fixtures/`.
- Added `TimeStatusService` + `InMemoryTimeAnchorStore` for per-tenant anchor/budget status + staleness; tests in `TimeStatusServiceTests`.
- Added verification pipeline (`TimeVerificationService`) with stub Roughtime/RFC3161 verifiers requiring trust roots; loader now verifies using trust roots.
- Added API surface `/api/v1/time/status` (plus POST `/api/v1/time/anchor`) via `TimeStatusController` and web host wiring.
- Added sealed startup hook (`StartupValidationExtensions`) to block app start when anchor missing/stale; uses budgets and returns structured reasons.
- Upgraded Roughtime verifier to real Ed25519 signature check + RFC3161 verifier using SignedCms; failures now return `roughtime-*` / `rfc3161-*` reasons.
- Added config binding (`AirGap:*`) for tenant and staleness budgets; startup validation pulls from config.
- Added config sample at `docs/airgap/time-config-sample.json` for sealed-mode deployments.
- Documented endpoints and payloads at `docs/airgap/time-api.md`.
- Health check: `/healthz/ready` reports degraded/healthy based on staleness; consumers should scrape for sealed-mode readiness.
## Next implementation hooks
- Plug real Roughtime and RFC3161 decoders, verifying against trust roots supplied via sealed-mode config.
- Persist `TimeAnchor` rows under controller/importer once schema is final; emit telemetry counters/alerts.
- Replace placeholder signature fingerprint with actual signer fingerprint post-verification.
## Determinism/air-gap posture
- Parser avoids wall-clock; anchor time derived deterministically from token digest until real parser is wired.
- No network calls; uses cached NuGet (`local-nugets/`) for tests.
## How to consume
```bash
DOTNET_NOLOGO=1 dotnet test tests/AirGap/StellaOps.AirGap.Time.Tests/StellaOps.AirGap.Time.Tests.csproj --no-build
```
## Owners
- AirGap Time Guild (per sprint 0510).