Add docs/adr/ with a chronological ADR set reconstructed from the project history (no orchestrator, one storage format, derived-state sync, SQL-over-SSH contract, WireGuard beneath SSH, IaC boundaries, non-root image, runtime data paths, bounded lake scans), an index README, and a template. Distinguish repo-local ADRs from ecosystem-wide ASRs. Expand AGENTS.md with a decision-records section and a runtime/operations guide (service URLs, rebuild workflow, CPU budget knobs). Reference the ADRs from README, CONTRIBUTING, and the affected design docs, and add an open question for T1 partition pruning.
5.5 KiB
05 — Network & security
Zero trust, fully air-gapped, everything provisioned before wheels-up.
Decision: ADR-0005 — WireGuard beneath SSH.
Trust model
- Nothing joins the swarm at runtime. Every device receives its identity (key pair + certificate) during ground provisioning, signed by the fleet's offline CA. There is no trust-on-first-use, no runtime enrollment endpoint, no exception path.
- Network position grants nothing. Being inside the mesh does not authorize anything; every connection authenticates mutually.
- Two layers of cryptography, by design:
- WireGuard mesh overlay — every drone holds the public keys of every fleet member (distributed at provisioning). All swarm traffic runs inside these tunnels; a device without a provisioned key cannot even complete a handshake.
- Authenticated channels inside the overlay — the bulk-sync and peer-query channel is SSH with ed25519 keys and pinned
known_hosts(no first-use prompts, no runtime key acceptance); every permitted operation is a separate key pair bound to a forced command, so a stolen key authorizes exactly one narrow action. Services that speak TLS (MinIO, ground APIs) present per-service certificates from the fleet PKI (mTLS). Compromising the network layer alone still yields nothing readable — and compromising one credential yields one operation, not a shell.
graph TB
subgraph ground [Ground provisioning, before deployment]
CA["offline fleet CA"]
PROV["provisioning station"]
CA -->|"sign device + service certs"| PROV
end
subgraph mesh [In-flight ad-hoc mesh]
A["drone A<br/>wg key + certs"]
B["drone B<br/>wg key + certs"]
C["drone C<br/>wg key + certs"]
A <-->|"WireGuard + mTLS"| B
B <-->|"WireGuard + mTLS"| C
A <-->|"WireGuard + mTLS"| C
end
PROV -->|"per-device identity, peer list"| A
PROV --> B
PROV --> C
Radio reality
- Transport is assumed to be ad-hoc Wi-Fi class (2.4/5 GHz), with channel hopping expected. The platform treats the link as lossy, variable, and adversarial — all sync mechanisms already tolerate partitions (04).
- RSSI is data. Per-peer signal strength is captured into
telemetrylike any other sensor — it feeds relative-positioning estimates and post-flight link-quality analysis, and it is free. - Electromagnetic environment sensing (whatever the radio can observe) is likewise recorded — cheap in flight, valuable in replay.
Why not SSH alone?
SSH and WireGuard solve different problems. SSH is the authorization layer for narrow, point-to-point operations (bulk sync, read-only SQL). WireGuard is the membership and transport encryption layer for everything on the mesh, including traffic SSH cannot carry.
| Concern | SSH alone | WireGuard + SSH (this design) |
|---|---|---|
| Pose broadcast (UDP, ~5 Hz, compact frame) | Cannot carry subnet broadcast; would need a separate app protocol | Encrypted inside the mesh overlay; any peer on the tunnel can receive |
| Broadcast confidentiality without WG | Payloads traverse the radio in cleartext — any receiver in range reads swarm positions | Only provisioned fleet members complete a handshake; outsiders see noise |
| Lossy ad-hoc links | TCP head-of-line blocking; sessions stall and reconnect on packet loss | UDP transport survives loss and roaming; SSH sessions over WG are more stable than SSH directly on Wi-Fi |
| Network membership | Any host that reaches the port gets an SSH banner — a probe surface | Devices without a provisioned key get no handshake response; the port is effectively silent |
Rejected alternative: encrypt each broadcast frame in application code with a fleet-wide AEAD key. That reinvents a crypto layer the OS already provides, still leaves TCP head-of-line blocking for peer queries, and does not answer the membership question (who is allowed on the mesh at all).
Keep both layers: WireGuard for who is on the network and whether the wire is readable; SSH forced commands for what a peer is allowed to do once connected (04).
Key lifecycle
| Phase | Action |
|---|---|
| Fleet build | Offline CA generates device identities; keys injected at provisioning, never transmitted over any network |
| Mission prep | Peer lists and certificates refreshed as part of the fleet release bundle (11) |
| Rotation | Standard cadence between missions; emergency rotation = new fleet release |
| Loss of a drone | Its keys are revoked in the next release; mesh peers drop the revoked identity at the next mission load. Data-at-rest exposure is bounded by NVMe encryption (below) |
Data at rest
- On-board NVMe is encrypted (LUKS) with keys held in the device's secure element / TPM where the hardware provides one; the disk alone is unreadable.
- The ground warehouse applies standard at-rest encryption plus role-based access; it is the single most valuable asset in the system (every flight ever flown).
Attack surface, deliberately shortened
- The dev/debug plane does not exist in production builds (02) — not firewalled, absent.
- No inbound listeners except the authenticated mesh services; no management SSH in production radio profiles (bench access is wired, at the dock).
- Images are scanned (Trivy) and signed in CI; drones verify signatures against the pinned digests in the fleet release manifest before running anything (11).