16 KiB
Offline Sync Flow
Overview
The Offline Sync Flow describes how StellaOps supports air-gapped and disconnected environments through the Offline Kit. This flow covers advisory bundle generation, secure transfer, verification, and import in environments with no external network connectivity.
Business Value: Enable full vulnerability scanning and policy evaluation capabilities in highly secure, air-gapped environments while maintaining audit trails and cryptographic verification.
Actors
| Actor | Type | Role |
|---|---|---|
| Online Admin | Human | Generates and exports offline bundles |
| Offline Admin | Human | Imports and verifies bundles |
| Mirror | Service | Creates advisory snapshots |
| EvidenceLocker | Service | Seals bundles for transfer |
| AirGap Importer | Service | Validates and imports bundles |
| Signer | Service | Signs bundle manifests |
Prerequisites
Online Environment
- Access to vulnerability feeds (NVD, GHSA, etc.)
- Signing keys configured
- Bundle generation scheduled
Offline Environment
- AirGap services deployed
- Trust anchors configured (public keys)
- Secure transfer mechanism available
Bundle Types
| Bundle Type | Contents | Frequency |
|---|---|---|
| Advisory Bundle | CVE data, CVSS scores, affected versions | Daily/Weekly |
| VEX Bundle | VEX statements from trusted issuers | Daily |
| Policy Bundle | Policy sets, rules, exceptions | On-demand |
| Trust Bundle | Signing keys, certificates, CRLs | Monthly |
| Time Anchor | Roughtime proofs, NTP alternatives | Daily |
Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Offline Sync Flow │
└─────────────────────────────────────────────────────────────────────────────────┘
ONLINE ENVIRONMENT OFFLINE ENVIRONMENT
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ │ │ │
│ ┌────────┐ ┌────────┐ ┌────┐│ │┌────┐ ┌─────────┐ ┌────────┐│
│ │ Mirror │ │Evidence│ │Sign││ ││Verif│ │ AirGap │ │Concelier││
│ │ │ │ Locker │ │ ││ ││ │ │Importer │ │ ││
│ └───┬────┘ └───┬────┘ └──┬─┘│ │└──┬──┘ └────┬────┘ └───┬────┘│
│ │ │ │ │ │ │ │ │ │
│ │ Snapshot │ │ │ │ │ │ │ │
│ │ advisories│ │ │ │ │ │ │ │
│ │───┐ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │<──┘ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ Create │ │ │ │ │ │ │ │
│ │ bundle │ │ │ │ │ │ │ │
│ │──────────>│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ Seal │ │ │ │ │ │ │
│ │ │──────────> │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ Signed │ │ │ │ │ │ │
│ │ │ bundle │ │ │ │ │ │ │
│ │ │<────────── │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ [Export to ] │ │ │ │ │ │
│ │ [removable media ] │ =========> [Import from│ │ │
│ │ │ │ │ │ │removable]│ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ Verify │ │ │
│ │ │ │ │ │ │ signature│ │ │
│ │ │ │ │ │ │───┐ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │<──┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ Verify │ │ │
│ │ │ │ │ │ │ Merkle │ │ │
│ │ │ │ │ │ │───┐ │ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │<──┘ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ Import │ │ │
│ │ │ │ │ │ │──────────> │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ Unpack │ │
│ │ │ │ │ │ │ │ advisories│ │
│ │ │ │ │ │ │ │───┐ │ │
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │<──┘ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ Merge to │ │
│ │ │ │ │ │ │ │ Concelier │ │
│ │ │ │ │ │ │ │──────────>│ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
└────────────────────────────────┘ └────────────────────────────────┘
Step-by-Step
1. Advisory Snapshot (Online)
Mirror service creates point-in-time snapshot:
{
"snapshot_id": "snap-20241229",
"created_at": "2024-12-29T00:00:00Z",
"sources": [
{"name": "nvd", "last_sync": "2024-12-29T00:00:00Z", "count": 245678},
{"name": "ghsa", "last_sync": "2024-12-28T23:45:00Z", "count": 45678},
{"name": "osv", "last_sync": "2024-12-28T23:30:00Z", "count": 89012}
],
"delta_from": "snap-20241228",
"advisories": {
"new": 127,
"updated": 456,
"unchanged": 245095
}
}
2. Bundle Generation (Online)
EvidenceLocker creates sealed bundle:
{
"bundle_id": "offline-adv-20241229",
"bundle_type": "advisory",
"created_at": "2024-12-29T01:00:00Z",
"contents": {
"advisories": {
"full_count": 245678,
"delta_count": 583,
"format": "ndjson.gz"
},
"metadata": {
"sources": ["nvd", "ghsa", "osv"],
"schema_version": "1.0.0"
}
},
"integrity": {
"merkle_root": "sha256:abc123...",
"file_count": 15,
"total_size": "125 MB"
}
}
3. Bundle Signing (Online)
Signer creates detached signature:
{
"bundle_id": "offline-adv-20241229",
"signature": {
"algorithm": "ecdsa-p256",
"keyid": "sha256:offline-signing-key",
"sig": "base64:signature...",
"timestamp": "2024-12-29T01:00:00Z"
},
"certificate_chain": [
"base64:signing-cert...",
"base64:intermediate-ca...",
"base64:root-ca..."
],
"timestamping": {
"tsa_url": "https://timestamp.stellaops.io",
"timestamp_token": "base64:tst..."
}
}
4. Export Package
Final export package structure:
offline-kit-20241229/
├── manifest.json # Bundle manifest
├── manifest.sig # Detached signature
├── advisories/
│ ├── nvd-full.ndjson.gz
│ ├── nvd-delta.ndjson.gz
│ ├── ghsa-full.ndjson.gz
│ └── osv-full.ndjson.gz
├── vex/
│ └── vex-statements.ndjson.gz
├── policies/
│ └── policy-sets.json
├── trust/
│ ├── root-ca.pem
│ └── signing-keys.json
├── merkle/
│ └── tree.json
├── verify.sh # Verification script
└── README.md
5. Secure Transfer
Transfer via approved mechanism:
- USB drive (encrypted)
- Optical media (write-once)
- Data diode (one-way network)
- Secure courier
6. Import Verification (Offline)
AirGap Importer verifies bundle:
# Run verification
stellaops-airgap verify /media/usb/offline-kit-20241229/
# Verification steps:
✓ Manifest signature valid
✓ Certificate chain verified (trust anchor: sha256:root-ca)
✓ Timestamp verified (within 7 day window)
✓ Merkle root matches: sha256:abc123...
✓ All 15 files verified against Merkle tree
✓ No tamper detected
Bundle verified successfully. Ready for import.
7. Time Anchor Verification (Offline)
Verify bundle freshness without network time:
{
"time_verification": {
"bundle_timestamp": "2024-12-29T01:00:00Z",
"tsa_timestamp": "2024-12-29T01:00:05Z",
"local_time_anchor": "2024-12-29T10:00:00Z",
"max_age_policy": "7d",
"age": "9h",
"status": "FRESH"
}
}
8. Advisory Import (Offline)
AirGap Importer loads advisories into Concelier:
{
"import_id": "import-20241229-001",
"bundle_id": "offline-adv-20241229",
"started_at": "2024-12-29T10:30:00Z",
"completed_at": "2024-12-29T10:35:00Z",
"results": {
"advisories_imported": 583,
"advisories_updated": 456,
"advisories_new": 127,
"conflicts_resolved": 0,
"errors": 0
},
"state": {
"previous_snapshot": "snap-20241228",
"current_snapshot": "snap-20241229"
}
}
Bundle Freshness Policies
Strict (High Security)
freshness_policy:
mode: strict
max_age:
advisory: 24h
vex: 24h
policy: 7d
trust: 30d
require_tsa: true
reject_stale: true
Standard
freshness_policy:
mode: standard
max_age:
advisory: 7d
vex: 7d
policy: 30d
trust: 90d
require_tsa: false
warn_stale: true
Permissive (Disconnected Operations)
freshness_policy:
mode: permissive
max_age:
advisory: 30d
vex: 30d
policy: 90d
trust: 365d
require_tsa: false
warn_stale: true
allow_manual_override: true
Data Contracts
Bundle Manifest Schema
interface OfflineBundle {
bundle_id: string;
bundle_type: 'advisory' | 'vex' | 'policy' | 'trust' | 'time_anchor';
version: string;
created_at: string;
created_by: string;
contents: {
files: Array<{
path: string;
size: number;
sha256: string;
}>;
metadata: Record<string, unknown>;
};
integrity: {
merkle_root: string;
algorithm: 'sha256';
tree_path: string;
};
signature: {
keyid: string;
algorithm: string;
sig: string;
};
freshness: {
timestamp: string;
tsa_timestamp?: string;
valid_until?: string;
};
}
Import Result Schema
interface ImportResult {
import_id: string;
bundle_id: string;
status: 'success' | 'partial' | 'failed';
started_at: string;
completed_at: string;
results: {
records_imported: number;
records_updated: number;
records_new: number;
conflicts: number;
errors: number;
};
verification: {
signature_valid: boolean;
merkle_verified: boolean;
freshness_check: 'FRESH' | 'STALE' | 'EXPIRED';
};
audit_log_entry: string;
}
Scheduling Strategies
Daily Sync
offline_sync:
advisory_bundle:
schedule: "0 1 * * *" # Daily at 1 AM
type: delta
retention: 7
vex_bundle:
schedule: "0 2 * * *" # Daily at 2 AM
type: delta
retention: 7
Weekly Full + Daily Delta
offline_sync:
advisory_bundle:
full:
schedule: "0 0 * * SUN" # Weekly full on Sunday
retention: 4
delta:
schedule: "0 1 * * MON-SAT" # Daily delta Mon-Sat
retention: 7
Error Handling
| Error | Recovery |
|---|---|
| Signature invalid | Reject bundle, alert admin |
| Merkle verification failed | Reject bundle, request retransfer |
| Bundle too old | Warn user, require override |
| Import conflict | Log conflict, apply latest |
| Disk space insufficient | Cleanup old imports, retry |
Observability
Metrics (Online)
| Metric | Type | Labels |
|---|---|---|
offline_bundle_created_total |
Counter | type |
offline_bundle_size_bytes |
Histogram | type |
offline_bundle_advisory_count |
Gauge | bundle_id |
Metrics (Offline)
| Metric | Type | Labels |
|---|---|---|
offline_import_total |
Counter | status, type |
offline_bundle_age_hours |
Gauge | bundle_id |
offline_advisory_freshness_hours |
Gauge | - |
Key Log Events
| Event | Level | Fields |
|---|---|---|
offline.bundle.created |
INFO | bundle_id, type, size |
offline.bundle.verified |
INFO | bundle_id, verifier |
offline.import.started |
INFO | import_id, bundle_id |
offline.import.complete |
INFO | import_id, records |
offline.freshness.warning |
WARN | bundle_id, age |
Related Flows
- Evidence Bundle Export Flow - Similar sealing mechanics
- Advisory Drift Re-scan Flow - Advisory consumption
- Scan Submission Flow - Uses imported advisories