AirGap
Status: Implemented Source: src/AirGap/ Owner: Platform Team
Note: This is the module dossier with architecture and implementation details. For operational guides and workflows, see docs/modules/airgap/guides/.
Purpose
AirGap manages sealed knowledge snapshot export and import for offline/air-gapped deployments. Provides time-anchored snapshots with staleness policies, deterministic bundle creation, and secure import validation for complete offline operation.
Components
Services (HTTP host):
StellaOps.AirGap.Controller- Sealing/staleness orchestration host. Minimal-API endpoints under route group/system/airgap(/status,/seal,/unseal,/verify); see Endpoints below.StellaOps.AirGap.Time- HTTP host (Microsoft.NET.Sdk.Web) exposing the time-anchor surface under/api/v1/time(GET /status,POST /anchor) viaTimeStatusController, plus time-anchor verification (Roughtime/RFC3161) and staleness math.
Libraries (no HTTP surface):
StellaOps.AirGap.Importer- Bundle/replay/TUF validation, version monotonicity, quarantine, evidence reconciliation, and offline verification policy. Referenced by both hosts.StellaOps.AirGap.Policy- Egress policy for sealed mode (EgressPolicyMode:Unsealed= 0 advisory,Sealed= 1 enforced) plus Roslyn analyzers (HttpClientUsageAnalyzer).StellaOps.AirGap.Bundle- Bundle manifest (v2) models, builder, extractors, snapshot writer/reader, and import targets (ConcelierAdvisoryImportTarget,ExcititorVexImportTarget,PolicyRegistryImportTarget).StellaOps.AirGap.Sync- Offline job-log HLC merge for disconnected ordering (__Libraries/StellaOps.AirGap.Sync).StellaOps.AirGap.Persistence- PostgreSQL persistence: embedded SQL migration (001_initial_schema.sql) forstate/bundle_versions/bundle_version_history, applied byAirGapStartupMigrationHost. Runtime stores use raw Npgsql; EF Core v10 is used for the scaffolded model/compiled-model workflow below.
Endpoints
Controller (StellaOps.AirGap.Controller, route group /system/airgap)
| Method | Route | Required scope | Policy | Description |
|---|---|---|---|---|
GET | /system/airgap/status | airgap:status:read | AirGap.StatusRead | Returns current sealing state, time anchor, drift, and per-content staleness. |
POST | /system/airgap/seal | airgap:seal | AirGap.Seal | Seals the tenant; body is SealRequest (policyHash required; optional timeAnchor, stalenessBudget, contentBudgets). |
POST | /system/airgap/unseal | airgap:seal | AirGap.Seal | Unseals the tenant (no body). |
POST | /system/airgap/verify | airgap:status:read | AirGap.Verify | Verifies bundle/replay evidence against the sealed policy hash; body is VerifyRequest. |
Responses use AirGapStatusResponse (fields: tenantId, sealed, policyHash, timeAnchor, staleness, driftSeconds, driftBaselineSeconds, secondsRemaining, contentStaleness, lastTransitionAt, evaluatedAt); /verify returns VerifyResponse (valid, reason). Tenant is resolved from the stellaops:tenant/tid claim and/or the x-tenant-id header; mismatches return 400/403.
Scope note. There is no
airgap:verifyscope. The canonical catalog (StellaOps.Auth.Abstractions/StellaOpsScopes.cs) defines exactly three AirGap scopes —airgap:seal(:188),airgap:import(:193),airgap:status:read(:198) — and theAirGap.Verifypolicy requiresStellaOpsScopes.AirgapStatusRead(Controller/Program.cs:59-69): verification is a read-only integrity check (ReplayVerificationServicecompares supplied replay evidence against the sealed policy hash and mutates no state), so it shares the air-gap read scope, mirroring how the Attestor verify surface collapses toattest:read.airgap:status:readis already granted to the console and CLI clients, so/verifyneeds no additional grant. TheAirGap.Importpolicy (airgap:import) is registered in the Controller but no Controller endpoint currently maps it; the import scope is consumed by the bundle import path.
Time service (StellaOps.AirGap.Time, route prefix /api/v1/time)
| Method | Route | Required scope | Policy | Description |
|---|---|---|---|---|
GET | /api/v1/time/status | airgap:status:read | AirGapTime.StatusRead | Returns TimeStatusDto time-anchor status for the tenant. |
POST | /api/v1/time/anchor | airgap:seal | AirGapTime.AnchorWrite | Ingests a time anchor (SetAnchorRequest: hexToken, format, optional warning/breach seconds and trust-root override). |
ReplayVerificationService accepts a ReplayDepth of HashOnly, FullRecompute (default), or PolicyFreeze.
Configuration
See etc/airgap.yaml.sample for configuration options.
Key settings (per etc/airgap.yaml.sample):
- Staleness policy (
staleness.maxAgeHours,staleness.warnAgeHours,staleness.staleAction(warn|block),staleness.requireTimeAnchor) - Per-content staleness budgets — default keys are
advisories,vex,policy(eachwarningSeconds/breachSeconds); these mirrorAirGapOptions.ContentBudgets - Time anchor (
timeAnchor.defaultSource(roughtime|rfc3161|local), Roughtime/RFC3161 servers,maxClockDriftSeconds) - Trust store (
trustStore.rootBundlePath,allowedAlgorithms, rotation) - Egress policy (
egressPolicy.mode,allowedHosts/deniedHosts,allowLocalhost) - Import (
import.quarantineDirectory,verifySignature,verifyMerkleRoot,enforceMonotonicity) - PostgreSQL connection (
Postgres:AirGap:ConnectionStringorConnectionStrings:Default; schema:airgap, override viaPostgres:AirGap:SchemaName)
Note: the YAML
egressPolicy.mode(allowlist/denylist) is the configuration-template shape; the runtimeEgressPolicyOptions.Modeenum isUnsealed/Sealedwith explicit allow rules (src/AirGap/StellaOps.AirGap.Policy).
EF Core Persistence Workflow
AirGap persistence now uses EF Core v10 models generated from the module migration schema.
Scaffold baseline context/models:
dotnet ef dbcontext scaffold \
"Host=...;Port=...;Database=...;Username=...;Password=..." \
Npgsql.EntityFrameworkCore.PostgreSQL \
--project src/AirGap/__Libraries/StellaOps.AirGap.Persistence/StellaOps.AirGap.Persistence.csproj \
--startup-project src/AirGap/__Libraries/StellaOps.AirGap.Persistence/StellaOps.AirGap.Persistence.csproj \
--schema airgap \
--table state \
--table bundle_versions \
--table bundle_version_history \
--context-dir EfCore/Context \
--context AirGapDbContext \
--output-dir EfCore/Models \
--namespace StellaOps.AirGap.Persistence.EfCore.Models \
--context-namespace StellaOps.AirGap.Persistence.EfCore.Context \
--use-database-names
Regenerate compiled model artifacts after model updates:
dotnet ef dbcontext optimize \
--project src/AirGap/__Libraries/StellaOps.AirGap.Persistence/StellaOps.AirGap.Persistence.csproj \
--startup-project src/AirGap/__Libraries/StellaOps.AirGap.Persistence/StellaOps.AirGap.Persistence.csproj \
--context AirGapDbContext \
--output-dir EfCore/CompiledModels \
--namespace StellaOps.AirGap.Persistence.EfCore.CompiledModels
Runtime behavior:
- The static compiled model is used explicitly for the default
airgapschema path. - Non-default schemas (for integration fixtures) use runtime model construction to preserve schema isolation.
Bundle manifest (v2) additions
BundleManifestV2 (src/AirGap/__Libraries/StellaOps.AirGap.Bundle/Models/BundleManifestV2.cs) carries schemaVersion ("2.0.0") plus:
canonicalManifestHash: sha256 of canonical JSON for deterministic verification.subject: sha256 (+ optional sha512) digest of the bundle target.timestamps: RFC3161/eIDAS timestamp entries with TSA chain/OCSP/CRL refs.rekorProofs: entry body/inclusion proof paths plus signed entry timestamp for offline verification.- Inline artifacts (no
path) are capped at 4 MiB (BundleSizeValidator.MaxInlineBlobSize = 4 * 1024 * 1024); larger artifacts are externalized underartifacts/.
KnowledgeSnapshotManifest snapshot bundles now emit schema "2.1.0" when created by SnapshotBundleWriter. Schema 2.1.0 adds ociImages[] for fully offline container-image serving: OCI manifests, configs, layers, and referrer blobs are stored under oci/blobs/sha256/<hex> with oci/oci-layout and oci/index.json. Layer/referrer digests are verified before packing, duplicate blobs are written once, and the reader includes the OCI subtree in Merkle-root verification. OciImageLayoutVerifier provides the import-side fail-closed layout check. The deployment agent consumes an extracted oci/ directory through Agent:LocalRegistry:OciLayoutBundlePath; the agent registry repeats the blob-hash and index.json checks inline before importing into its local IOciContentStore.
Telemetry
The Controller emits metrics and traces via meter/source StellaOps.AirGap.Controller (AirGapTelemetry):
- Counters:
airgap_seal_total,airgap_unseal_total,airgap_startup_blocked_total(tagged bytenant). - Observable gauges:
airgap_time_anchor_age_seconds,airgap_staleness_budget_seconds(tagged bytenant). - Trace spans (ActivitySource
StellaOps.AirGap.Controller):airgap.status.read,airgap.startup.validation.
The Importer emits offline-kit metrics via meter StellaOps.AirGap.Importer (OfflineKitMetrics): import totals plus attestation-verify and Rekor inclusion latency/retry/success.
Dependencies
- PostgreSQL (schema:
airgap) - Authority — scope-based authorization (
StellaOps.Auth.Abstractions/StellaOps.Auth.ServerIntegration); scopesairgap:status:read,airgap:seal,airgap:import. - Router messaging (
StellaOps.Router.Transport.Messaging,StellaOps.Router.AspNet) — host integration for both services. - Data-module cores consumed by the bundle import targets: Concelier (
StellaOps.Concelier.Core/.RawModels, advisories) and Excititor (StellaOps.Excititor.Core, VEX); Policy registry is targeted viaPolicyRegistryImportTarget. StellaOps.Canonical.Json/StellaOps.Determinism.Abstractions— deterministic bundle hashing.
Earlier “ExportCenter / Mirror / VexHub / SbomService” wiring is not present as direct project references in
src/AirGap/; bundle ingestion targets the Concelier/Excititor cores listed above.
Related Documentation
- Operational guides:
./guides/ - Offline Kit:
../../OFFLINE_KIT.md - Mirror:
../mirror/ - ExportCenter:
../export-center/ - AirGap Controller API reference:
../airgap-controller/api-reference.md - Promotion Rekor tile runbook:
./guides/promotion-rekor-tile-verification.md
Evidence Bundles for Air-Gapped Verification
The AirGap module supports golden corpus evidence bundles for offline verification of patch provenance. These bundles enable auditors to verify security patch status without network access.
Bundle Contents
Evidence bundles follow the OCI format and contain:
- Pre/post binaries with debug symbols
- Canonical SBOM for each binary
- DSSE delta-sig predicate proving patch status
- Build provenance (if available from buildinfo)
- RFC 3161 timestamps for each signed artifact
- Validation run results and KPIs
Bundle Export and Import (PROPOSED)
Status: Not implemented as written. The
stella groundtruth bundle export/stella groundtruth bundle importsubcommands below do not exist in the CLI. The onlygroundtruthexport command implemented today isstella groundtruth validate export(exports a validation report, not an evidence bundle — seesrc/Cli/Commands/GroundTruth/GroundTruthValidateCommands.cs). The snippets are retained as a forward design sketch.
# PROPOSED — not yet implemented
stella groundtruth bundle export \
--packages openssl,zlib,glibc \
--distros debian,fedora \
--output symbol-bundle.tar.gz \
--sign-with cosign
# PROPOSED — not yet implemented
stella groundtruth bundle import \
--input symbol-bundle.tar.gz \
--verify-signature \
--trusted-keys /etc/stellaops/trusted-keys.pub \
--output verification-report.md
Standalone Verifier
For air-gapped environments without the full Stella Ops stack, use the standalone verifier (src/Tools/StellaOps.Verifier, binary stella-verifier). It exposes verify and info subcommands:
stella-verifier verify \
--bundle evidence-bundle.oci.tar \
--trusted-keys trusted-keys.pub \
--trust-profile eu-eidas.trustprofile.json \
--format json \
--output report.json
Verify options: --bundle/-b (required), --trusted-keys/-k, --trust-profile/-p, --output/-o, --format/-f (Markdown default, or json), and per-check toggles --verify-signatures, --verify-timestamps, --verify-digests, --verify-pairs (all default true), plus --quiet/-q and --verbose/-v. (The report format is selected by --format; --output only sets the path.)
Exit codes:
0: All verifications passed1: One or more verifications failed2: Invalid input or configuration error
Related Documentation
Current Status
Implemented. Two HTTP hosts are shipped: the Controller (/system/airgap/{status,seal,unseal,verify}) for sealing/staleness orchestration and the Time service (/api/v1/time/{status,anchor}) for time-anchor management. The Importer library handles secure ingestion (bundle/TUF/replay validation, version monotonicity, quarantine, evidence reconciliation) and the Bundle library builds/reads v2 manifests with import targets into the Concelier (advisories) and Excititor (VEX) cores and the Policy registry. Staleness policies enforce time-bound validity; egress is gated by the Policy library in sealed mode.
