Files
git.stella-ops.org/docs/product-advisories/14-Dec-2025 - Offline and Air-Gap Technical Reference.md
2025-12-14 21:29:44 +02:00

12 KiB
Raw Blame History

Offline and Air-Gap Technical Reference

Source Advisories:

  • 01-Dec-2025 - DSSESigned Offline Scanner Updates
  • 07-Dec-2025 - Reliable AirGap Verification Workflows

Last Updated: 2025-12-14


0. AIR-GAP PRE-SEED CHECKLIST (GOLDEN INPUTS)

Before you can verify or ingest anything offline, the air-gap must be pre-seeded with:

  • Root of trust
    • Vendor/org public keys (and chains if using Fulcio-like PKI)
    • Pinned transparency log root(s) for the offline log/mirror
  • Policy bundle
    • Verification policies (Cosign/in-toto rules, allow/deny lists)
    • Lattice rules for VEX merge/precedence
    • Toolchain manifest with hash-pinned binaries (cosign/oras/jq/scanner, etc.)
  • Evidence bundle
    • SBOMs (CycloneDX/SPDX), DSSE-wrapped attestations (provenance/VEX/SLSA)
    • Optional: frozen vendor feeds/VEX snapshots (as content-addressed inputs)
  • Offline log snapshot
    • Signed checkpoint/tree head and entry pack (leaves + proofs) for every receipt you rely on

Ship bundles on signed, write-once media (or equivalent operational controls) and keep chain-of-custody receipts alongside the bundle manifest.

1. OFFLINE UPDATE BUNDLE STRUCTURE

1.1 Directory Layout

/bundle-2025-12-14/
  manifest.json              # version, created_at, entries[], sha256s
  payload.tar.zst            # actual DB/indices/feeds
  payload.tar.zst.sha256
  statement.dsse.json        # DSSE-wrapped statement over payload hash
  rekor-receipt.json         # Rekor v2 inclusion/verification material

1.2 Manifest Schema

manifest.json:

{
  "version": "string",
  "created_at": "UTC ISO-8601",
  "entries": [{"name": "string", "sha256": "string", "size": int}],
  "payload_sha256": "string"
}

1.3 DSSE Predicate Schema

statement.dsse.json payload:

{
  "payloadType": "application/vnd.in-toto+json",
  "payload": {
    "subject": {
      "name": "stella-ops-offline-kit-<DATE>.tgz",
      "digest": {"sha256": "string"}
    },
    "predicateType": "https://stella-ops.org/attestations/offline-update/1",
    "predicate": {
      "offline_manifest_sha256": "string",
      "feeds": [{"name": "string", "snapshot_date": "UTC ISO-8601", "archive_digest": "string"}],
      "builder": "string",
      "created_at": "UTC ISO-8601",
      "oukit_channel": "edge|stable|fips-profile"
    }
  }
}

1.4 Rekor Receipt Schema

rekor-receipt.json:

{
  "uuid": "string",
  "logIndex": int,
  "rootHash": "string",
  "hashes": ["string"],
  "checkpoint": "string"
}

2. VERIFICATION SEQUENCE

2.1 Offline Kit Import Steps

1. Validate Cosign signature of tarball
2. Validate offline-manifest.json with JWS signature
3. Verify file digests for all entries (including /attestations/*)
4. Verify DSSE:
   - Call StellaOps.Attestor.Verify with:
     - offline-update.dsse.json
     - offline-update.rekor.json
     - local Rekor log snapshot/segment
   - Ensure payload digest matches kit tarball + manifest digests
5. Only after all checks pass:
   - Swap Scanner's feed pointer to new snapshot
   - Emit audit event (kit filename, tarball digest, DSSE digest, Rekor UUID + log index)

2.2 Activation Acceptance Rules

  • Trust root: pinned publisher public keys (out-of-band rotation)
  • Monotonicity: only activate if manifest.version > current.version
  • Rollback/testing: allow an explicit force-activate path for emergency validation, but record it as a non-monotonic override in state + audit logs
  • Atomic switch: unpack → validate → symlink flip (db/staging/db/active/)
  • Quarantine on failure: move to updates/quarantine/ with reason code

3. OFFLINE DIRECTORY LAYOUT

/evidence/
  keys/
    roots/                 # root/intermediate certs, PGP pubkeys
    identities/            # per-vendor public keys
    tlog-root/             # hashed/pinned tlog root(s)
  policy/
    verify-policy.yaml
    lattice-rules.yaml
  sboms/                   # *.cdx.json, *.spdx.json
  attestations/            # *.intoto.jsonl.dsig (DSSE)
  tlog/
    checkpoint.sig         # signed tree head
    entries/               # *.jsonl (Merkle leaves) + proofs
  tools/
    cosign-<ver> (sha256)
    oras-<ver>   (sha256)
    jq-<ver>     (sha256)
    scanner-<ver> (sha256)

4. OFFLINE VERIFICATION POLICY SCHEMA

keys:
  - ./evidence/keys/identities/vendor_A.pub
  - ./evidence/keys/identities/your_authority.pub
tlog:
  mode: "offline"
  checkpoint: "./evidence/tlog/checkpoint.sig"
  entry_pack: "./evidence/tlog/entries"
attestations:
  required:
    - type: slsa-provenance
    - type: cyclonedx-sbom
  optional:
    - type: vex
constraints:
  subjects:
    alg: "sha256"
  certs:
    allowed_issuers:
      - "https://fulcio.offline"
    allow_expired_if_timepinned: true

4.1 Offline Keyring Usage (Cosign / in-toto)

Cosign-style verification must not require any online CA, Rekor fetch, or DNS lookups. Use pinned keys and (when applicable) an offline Rekor mirror snapshot.

# Verify a DSSE attestation using a locally pinned key (no network assumptions)
COSIGN_EXPERIMENTAL=1 cosign verify-attestation \
  --key ./evidence/keys/identities/vendor_A.pub \
  --policy ./evidence/policy/verify-policy.yaml \
  <artifact-digest-or-ref>
# in-toto offline verification (layout + local keys)
in-toto-verify \
  --layout ./evidence/attestations/layout.root.json \
  --layout-keys ./evidence/keys/identities/vendor_A.pub \
  --products <artifact-file>

5. DETERMINISTIC EVIDENCE RECONCILIATION ALGORITHM

1. Index artifacts by immutable digest
2. For each artifact digest:
   - Collect SBOM nodes from canonical SBOM files
   - Collect attestations (provenance, VEX, SLSA, signatures)
   - Validate each attestation (sig + tlog inclusion proof)
3. Normalize all docs (stable sort, strip non-essential timestamps, lowercase URIs)
4. Apply lattice rules (precedence: vendor > maintainer > 3rd-party)
5. Emit `evidence-graph.json` (stable node/edge order) + `evidence-graph.sha256` + DSSE signature

6. OFFLINE FLOW OPERATIONAL STEPS

1. Import bundle (mount WORM media read-only)
2. Verify tools (hash + signature) before execution
3. Verify tlog checkpoint
4. Verify each inclusion proof
5. Verify attestations (keyring + policy)
6. Ingest SBOMs (canonicalize + hash)
7. Reconcile (apply lattice rules → evidence graph)
8. Record run: write `run.manifest` with input/policy/tool/output hashes; DSSE-sign with Authority key

7. SCANNER CONFIG SURFACE

7.1 Offline Kit Configuration

scanner:
  offlineKit:
    requireDsse: true        # fail import if DSSE/Rekor verification fails
    rekorOfflineMode: true   # use local snapshots only
    attestationVerifier: https://attestor.internal
    trustAnchors:
      - anchorId: "UUID"
        purlPattern: "pkg:npm/*"
        allowedKeyids: ["key1", "key2"]

7.2 DSSE/Rekor Failure Handling

DSSE/Rekor fail, Cosign + manifest OK:

  • Keep old feeds active
  • Mark import as failed; surface ProblemDetails error via API/UI
  • Log structured fields: rekorUuid, attestationDigest, offlineKitHash, failureReason

Config flag to soften during rollout:

  • When requireDsse=false: treat DSSE/Rekor failure as warning; allow import with alerts

8. SBOM INGESTION DETERMINISTIC FLOW

# 1. Normalize SBOMs to canonical form
jq -S . sboms/app.cdx.json > sboms/_canon/app.cdx.json

# 2. Validate schemas (vendored validators)

# 3. Hash-pin canonical files and record in manifest.lock
sha256sum sboms/_canon/*.json > manifest.lock

# 4. Import to DB with idempotent keys: (artifactDigest, sbomHash)

9. OFFLINE REKOR MIRROR VERIFICATION

9.1 File-Ledger Pattern

  • Keep tlog/checkpoint.sig (signed tree head) + tlog/entries/*.jsonl (leaves + proofs)

9.2 Verification Steps

1. Recompute Merkle root from entries
2. Check matches `checkpoint.sig` (after verifying signature with tlog root key)
3. For each attestation:
   - Verify UUID/digest appears in entry pack
   - Verify inclusion proof resolves

10. METRICS & OBSERVABILITY

10.1 Offline Kit Metrics (Prometheus)

offlinekit_import_total{status="success|failed_dsse|failed_rekor|failed_cosign"}
offlinekit_attestation_verify_latency_seconds (histogram)
attestor_rekor_success_total
attestor_rekor_retry_total
rekor_inclusion_latency

10.2 Structured Logging Fields

rekorUuid
attestationDigest
offlineKitHash
failureReason
kitFilename
tarballDigest
dsseStatementDigest
rekorLogIndex

11. ERROR HANDLING

11.1 Import Failure Modes

Failure Type Action Audit Event
Cosign signature invalid Reject, quarantine IMPORT_FAILED_COSIGN
Manifest signature invalid Reject, quarantine IMPORT_FAILED_MANIFEST
DSSE verification failed Reject (if requireDsse=true) IMPORT_FAILED_DSSE
Rekor inclusion failed Reject (if requireDsse=true) IMPORT_FAILED_REKOR
Digest mismatch Reject, quarantine IMPORT_FAILED_DIGEST
Version not monotonic Reject IMPORT_FAILED_VERSION

11.2 Reason Codes (structured logs/metrics)

Use stable, machine-readable reason codes in logs/metrics and in ProblemDetails payloads:

  • HASH_MISMATCH
  • SIG_FAIL_COSIGN
  • SIG_FAIL_MANIFEST
  • DSSE_VERIFY_FAIL
  • REKOR_VERIFY_FAIL
  • SELFTEST_FAIL
  • VERSION_NON_MONOTONIC
  • POLICY_DENY

11.3 Quarantine Structure

/updates/quarantine/<timestamp>-<reason>/
  bundle.tar.zst
  manifest.json
  verification.log
  failure-reason.txt

12. CLI COMMANDS

12.1 Offline Kit Import

stellaops offline import \
  --bundle ./bundle-2025-12-14.tar.zst \
  --verify-dsse \
  --verify-rekor \
  --trust-root /evidence/keys/roots/stella-root.pub
# Emergency testing only (records a non-monotonic override in the audit trail)
stellaops offline import \
  --bundle ./bundle-2025-12-07.tar.zst \
  --verify-dsse \
  --verify-rekor \
  --trust-root /evidence/keys/roots/stella-root.pub \
  --force-activate

12.2 Offline Kit Status

stellaops offline status
# Output:
# Active kit: bundle-2025-12-14
# Kit digest: sha256:abc123...
# Activated at: 2025-12-14T10:00:00Z
# DSSE verified: true
# Rekor verified: true

12.3 Offline Verification

stellaops verify offline \
  --evidence-dir /evidence \
  --artifact sha256:def456... \
  --policy verify-policy.yaml

13. AUDIT TRAIL

13.1 Audit Event Schema

{
  "eventId": "uuid",
  "eventType": "OFFLINE_KIT_IMPORTED",
  "timestamp": "2025-12-14T10:00:00Z",
  "actor": "system",
  "details": {
    "kitFilename": "bundle-2025-12-14.tar.zst",
    "tarballDigest": "sha256:...",
    "dsseStatementDigest": "sha256:...",
    "rekorUuid": "...",
    "rekorLogIndex": 12345,
    "previousKitVersion": "bundle-2025-12-07",
    "newKitVersion": "bundle-2025-12-14"
  },
  "result": "success"
}

13.2 Audit Log Storage

CREATE TABLE offline_kit_audit (
  event_id UUID PRIMARY KEY,
  event_type TEXT NOT NULL,
  timestamp TIMESTAMPTZ NOT NULL,
  actor TEXT NOT NULL,
  details JSONB NOT NULL,
  result TEXT NOT NULL
);

CREATE INDEX idx_offline_kit_audit_ts ON offline_kit_audit(timestamp DESC);
CREATE INDEX idx_offline_kit_audit_type ON offline_kit_audit(event_type);

14. SECURITY CONSIDERATIONS

14.1 Key Management

  • Trust roots: pinned via out-of-band distribution
  • Key rotation: maintain version history in trust store
  • Revocation: maintain revoked_keys list in trust anchors

14.2 Integrity Guarantees

  • All bundles content-addressed
  • Manifest integrity via signature
  • DSSE envelope integrity via signature
  • Rekor inclusion proof integrity via Merkle tree

14.3 Air-Gap Boundaries

Allowed:

  • Local file system reads (read-only mount)
  • Local tool execution (verified binaries)
  • Local database writes (staged)

Forbidden:

  • Network egress
  • DNS lookups
  • NTP synchronization (use frozen clock)
  • External API calls

Document Version: 1.0 Target Platform: .NET 10, PostgreSQL ≥16, Angular v17