# Air-Gap Satellite Deployment — step-ca + Offline PKI
## Overview
Tier A customers in government, defense, critical infrastructure, or regulated industries may operate in fully air-gapped environments with zero internet connectivity. KuiperDesk supports this with:
1. **step-ca** (Smallstep Certificate Authority) for all TLS — no Let's Encrypt dependency
2. **Contract-length certificates** — cert lifetime = contract duration + 3 months grace
3. **Pre-loaded Harbor cache** — all container images baked into the install media
4. **Offline agent enrollment** — enrollment keys + CA bundle shipped with install media
## How Air-Gap Install Works
### Phase 1: Preparation (Online, at Arivaran or VAR)
```
Arivaran Hub (online) Install Media (USB/ISO)
┌──────────────────────┐ ┌──────────────────────────┐
│ Dashboard: Generate │ │ │
│ air-gap bundle for │ ───export───→ │ airgap-bundle-<tenant>/ │
│ tenant X, contract │ │ images/ │
│ 3 years │ │ aa-hash-svc.tar │
│ │ │ aa-twin-svc.tar │
│ Creates: │ │ aa-enroll-svc.tar │
│ - Satellite inter CA │ │ aa-dash.tar │
│ (3yr + 3mo = 39mo) │ │ cnpg-citus.tar │
│ - Enrollment keys │ │ seaweedfs.tar │
│ - step-ca root+inter │ │ step-ca.tar │
│ - Agent MSI │ │ keycloak.tar │
│ - Helm chart │ │ nginx.tar │
│ - Bootstrap script │ │ charts/ │
│ │ │ satellite-0.1.0.tgz │
│ │ │ pki/ │
│ │ │ root-ca.crt │
│ │ │ satellite-inter.crt │
│ │ │ satellite-inter.key │
│ │ │ step-ca-config.json │
│ │ │ enrollment/ │
│ │ │ keys.json │
│ │ │ agent.msi │
│ │ │ bootstrap.sh │
│ │ │ values-airgap.yaml │
│ │ │ README.md │
└──────────────────────┘ └──────────────────────────┘
```
### Phase 2: Deployment (Offline, at Customer Site)
```bash
# 1. Insert install media, mount
mount /dev/sdb1 /mnt/kuiperdesk
# 2. Run bootstrap (detects air-gap: no internet, uses local media)
/mnt/kuiperdesk/bootstrap.sh --airgap --media /mnt/kuiperdesk
# What happens:
# a) Installs RKE2 from local RPMs/debs (bundled on media)
# b) Loads container images from .tar files into containerd
# c) Installs step-ca as the cluster CA (replaces cert-manager + Let's Encrypt)
# d) Imports satellite intermediate CA (pre-signed by hub)
# e) Helm installs satellite chart with values-airgap.yaml
# f) No hub registration (offline) — satellite runs fully autonomous
```
### Phase 3: Operation (Offline)
The satellite operates fully autonomously:
- **TLS certs** issued by local step-ca (auto-renews within the cluster)
- **Agent enrollment** uses pre-generated keys from install media
- **Backups** stored on local S3 (SeaweedFS/CubeFS)
- **No hub sync** — all data stays local
- **Dashboard** accessible at the satellite's local IP/domain
### Phase 4: Renewal (Before Contract Expiry)
90 days before certificate expiry:
1. Dashboard shows "Certificate renewal required" banner
2. Customer contacts Arivaran/VAR for renewal bundle
3. New bundle generated with extended cert lifetime
4. Applied via USB/sneakernet (same bootstrap process, `--renew` flag)
## step-ca Configuration
### Why step-ca (Not cert-manager + Let's Encrypt)
| Feature | cert-manager + LE | step-ca |
|---------|-------------------|---------|
| Internet required | Yes (ACME HTTP-01/DNS-01) | **No** |
| Cert lifetime | 90 days max | **Custom (contract-length)** |
| CA control | Let's Encrypt controls | **Customer/Arivaran controls** |
| ACME support | Yes | Yes (local ACME server) |
| FedRAMP | LE not FedRAMP-authorized CA | **Own PKI = full control** |
| Air-gap | Impossible | **Native** |
### step-ca Deployment on Satellite
```yaml
# In Helm chart values-airgap.yaml
pki:
provider: step-ca # instead of cert-manager + letsencrypt
stepCA:
enabled: true
image: "registry.arivaran.ai/proxy-dockerhub/smallstep/step-ca:0.28.0"
# Pre-configured: root CA + intermediate loaded from install media
rootCert: /pki/root-ca.crt
intermediateCert: /pki/satellite-inter.crt
intermediateKey: /pki/satellite-inter.key
# ACME provisioner — local services request certs via standard ACME
acme:
enabled: true
endpoint: "https://step-ca.kd-system.svc:9000/acme/acme/directory"
# Certificate lifetimes
defaultTTL: "2160h" # 90 days (service certs)
maxTTL: "{{ .Values.pki.contractMonths }}mo" # contract length
ingress:
tls:
enabled: true
clusterIssuer: step-ca-issuer # cert-manager StepIssuer (not LE)
```
### Certificate Lifetimes
| Cert Type | Online Satellite | Air-Gap Satellite |
|-----------|-----------------|-------------------|
| Satellite intermediate CA | 5 years | **Contract + 3 months** |
| Service TLS (inter-service) | 90 days (auto-renew) | 90 days (auto-renew via local step-ca) |
| Agent mTLS | 1 year (auto-renew) | **Contract + 3 months** (no renewal without media) |
| Ingress TLS (dashboard) | 90 days (LE) | **Contract + 3 months** (step-ca) |
| Keycloak signing keys | 1 year | **Contract + 3 months** |
**Why contract + 3 months for air-gap:**
- Service certs (90-day) auto-renew within the cluster via step-ca — no issue
- But the **intermediate CA** and **agent mTLS certs** can't be renewed without hub contact
- Setting them to contract + 3 months ensures everything works through the contract period
- The 3-month grace gives time for renewal even if the contract renewal is delayed
## Agent Configuration for Air-Gap
### CA Trust Bundle
The agent needs to trust the satellite's step-ca root instead of Let's Encrypt:
```json
// prod_config.json on air-gap agents
{
"mode": "prod",
"grpc_hash_check_endpoint": "api.satellite.local:443",
"grpc_twin_endpoint": "api.satellite.local:443",
"cloud_upload_urls": ["https://api.satellite.local/kuiperdesk-blocks"],
"ca_bundle": "C:\\Program Files\\Arivaran\\pki\\root-ca.crt",
"tenant_id": "uuid",
"endpoint_id": "uuid"
}
```
**Agent code change required** (CloudComm.cpp):
```cpp
// If ca_bundle is set, use it instead of system CA store
if (!cfg.caBundlePath.empty()) {
curl_easy_setopt(curl, CURLOPT_CAINFO, cfg.caBundlePath.c_str());
}
```
### MSI Filename for Air-Gap
```
aaagent_{version}_{KEY}_{CA_FINGERPRINT}_{base64url(apiHost)}.msi
```
The `CA_FINGERPRINT` field tells the installer DLL to:
1. Fetch the CA cert from `https://{apiHost}/api/v1/ca/{fingerprint}`
2. Verify the fingerprint matches
3. Install to Windows Machine cert store (Local Machine → Trusted Root)
4. **For air-gap**: CA cert is bundled ON the install media (no fetch needed)
### Air-Gap MSI Installation
```powershell
# Air-gap: CA cert pre-installed from media, KEY from media, no download
msiexec /i D:\kuiperdesk\enrollment\agent.msi `
URL=api.satellite.local `
KEY=kdenr_<from-media> `
AIRGAPFP=<ca-fingerprint> `
AIRGAPCA=D:\kuiperdesk\pki\root-ca.crt `
/qn /l*v C:\temp\install.log
```
The installer DLL:
1. Reads `AIRGAPCA` path → installs cert to machine store (no download)
2. Reads `KEY` → enrollment (against local satellite, not hub)
3. Agent starts, trusts the satellite's TLS via installed CA
### Browser Trust
For users accessing the dashboard in a browser on air-gapped machines:
**Option A: GPO/MDM deployment (enterprise)**
```powershell
# Deploy root CA to all domain machines via Group Policy
certutil -addstore "Root" \\fileserver\kuiperdesk\root-ca.crt
```
**Option B: Manual trust (per-machine)**
```powershell
# Admin runs on each machine
Import-Certificate -FilePath "D:\kuiperdesk\pki\root-ca.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
```
**Option C: Firefox (uses own store, ignores Windows)**
```
# Deploy to Firefox via policies.json
{
"policies": {
"Certificates": {
"Install": ["D:\\kuiperdesk\\pki\\root-ca.crt"]
}
}
}
```
## Air-Gap Bundle Generation (Hub-Side)
```bash
# Dashboard: Settings → Satellite → Generate Air-Gap Bundle
# Or CLI:
kuiperdesk-cli airgap-bundle \
--tenant-id <uuid> \
--contract-months 36 \
--platform bare-metal \
--output /tmp/airgap-bundle-tenant-x/
```
The bundle generator:
1. Pulls all required container images from Harbor → saves as .tar
2. Generates step-ca root + intermediate keypair
3. Signs satellite intermediate CA with hub CA (lifetime = contract + 3mo)
4. Generates enrollment keys (batch of 100, pre-hashed)
5. Builds agent MSI with embedded CA fingerprint
6. Packages Helm chart + values-airgap.yaml
7. Generates bootstrap.sh (offline-capable)
8. Creates checksum manifest (SHA-256 for integrity)
9. Optionally: creates bootable ISO
## Hub ↔ Air-Gap Satellite Sync (Sneakernet)
For customers who periodically connect (e.g., monthly data export):
```
Air-Gap Satellite USB Drive
┌──────────────┐ ┌──────────────┐
│ Export: │ ───copy to USB──→ │ export/ │
│ - Metrics │ │ metrics.json│
│ - Audit log │ │ audit.jsonl │
│ - Billing │ │ billing.json│
│ │ │ │
│ Import: │ ←───copy from USB─── │ import/ │
│ - Agent MSI │ │ agent.msi │
│ - Policy │ │ policy.json│
│ - CA renewal │ │ renewal.pem│
└──────────────┘ └──────────────┘
```
## Compliance Mapping
| Framework | Control | How Air-Gap Satisfies |
|-----------|---------|----------------------|
| **FedRAMP High** | SC-7 | Complete network boundary — zero external connections |
| **FedRAMP High** | SC-8 | All data encrypted in transit (step-ca TLS) and at rest (LUKS) |
| **FedRAMP High** | SC-12 | FIPS-validated crypto (step-ca supports FIPS mode) |
| **FedRAMP High** | CM-3 | Configuration changes only via install media (auditable) |
| **ITAR** | — | Data never leaves customer facility |
| **CJIS** | 5.10.1.2 | Encryption in transit via customer-controlled CA |
| **IL4/IL5** | — | No internet dependency, all crypto under government control |
## Revision History
| Date | Change | Author |
|------|--------|--------|
| 2026-03-22 | Initial — step-ca air-gap, contract-length certs, bundle generation, sneakernet sync | Claire Dubois (Security), Viktor Kowalski (Agent Security), Navid Ahmadi (Compliance) |