- 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.
3.6 KiB
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 todefaultif absent). driftSecondsandsecondsRemainingare derived from the active time anchor and staleness budget evaluation.contentStalenesscontains 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
Unknownafter 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):
tenantIdsealedpolicyHash(nullable)timeAnchor(TimeAnchor, may beUnknown)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(includetenant_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.mddocs/airgap/sealed-startup-diagnostics.mddocs/airgap/staleness-and-time.mddocs/airgap/time-api.mddocs/airgap/importer.md