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):

Libraries (no HTTP surface):

Endpoints

Controller (StellaOps.AirGap.Controller, route group /system/airgap)

MethodRouteRequired scopePolicyDescription
GET/system/airgap/statusairgap:status:readAirGap.StatusReadReturns current sealing state, time anchor, drift, and per-content staleness.
POST/system/airgap/sealairgap:sealAirGap.SealSeals the tenant; body is SealRequest (policyHash required; optional timeAnchor, stalenessBudget, contentBudgets).
POST/system/airgap/unsealairgap:sealAirGap.SealUnseals the tenant (no body).
POST/system/airgap/verifyairgap:status:readAirGap.VerifyVerifies 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:verify scope. The canonical catalog (StellaOps.Auth.Abstractions/StellaOpsScopes.cs) defines exactly three AirGap scopes — airgap:seal (:188), airgap:import (:193), airgap:status:read (:198) — and the AirGap.Verify policy requires StellaOpsScopes.AirgapStatusRead (Controller/Program.cs:59-69): verification is a read-only integrity check (ReplayVerificationService compares 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 to attest:read. airgap:status:read is already granted to the console and CLI clients, so /verify needs no additional grant. The AirGap.Import policy (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)

MethodRouteRequired scopePolicyDescription
GET/api/v1/time/statusairgap:status:readAirGapTime.StatusReadReturns TimeStatusDto time-anchor status for the tenant.
POST/api/v1/time/anchorairgap:sealAirGapTime.AnchorWriteIngests 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):

Note: the YAML egressPolicy.mode (allowlist/denylist) is the configuration-template shape; the runtime EgressPolicyOptions.Mode enum is Unsealed/Sealed with 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:

Bundle manifest (v2) additions

BundleManifestV2 (src/AirGap/__Libraries/StellaOps.AirGap.Bundle/Models/BundleManifestV2.cs) carries schemaVersion ("2.0.0") plus:

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):

The Importer emits offline-kit metrics via meter StellaOps.AirGap.Importer (OfflineKitMetrics): import totals plus attestation-verify and Rekor inclusion latency/retry/success.

Dependencies

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.

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:

Bundle Export and Import (PROPOSED)

Status: Not implemented as written. The stella groundtruth bundle export / stella groundtruth bundle import subcommands below do not exist in the CLI. The only groundtruth export command implemented today is stella groundtruth validate export (exports a validation report, not an evidence bundle — see src/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:

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.