Files
git.stella-ops.org/docs/airgap/controller-scaffold.md
StellaOps Bot ea970ead2a
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
up
2025-11-27 07:46:56 +02:00

72 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 store by default; Mongo store activates when `AirGap:Mongo:ConnectionString` is set.
- Tests: Mongo2Go-backed store tests live under `tests/AirGap`; see `tests/AirGap/README.md` for OpenSSL shim note.
## 2) State model
- Persistent document `airgap_state` (Mongo):
- `id` (const `singleton`), `tenant_id`, `sealed` (bool), `policy_hash`, `time_anchor` (nullable), `last_transition_at` (UTC), `staleness_budget_seconds` (int?, optional per bundle), `notes`.
- Index on `{tenant_id}`; unique on `singleton` within tenant.
- In-memory cache with monotonic timestamp to avoid stale reads; cache invalidated on transitions.
### Mongo wiring (optin)
- Config section:
```json
"AirGap": {
"Mongo": {
"ConnectionString": "mongodb://localhost:27017",
"Database": "stellaops_airgap",
"Collection": "airgap_state"
}
}
```
- The DI extension `AddAirGapController` chooses Mongo when `ConnectionString` is present; otherwise falls back to in-memory.
- Collection index: unique on `{tenant_id, id}` to enforce singleton per tenant.
## 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.