# WireGuard Tunnel — Encrypted Node Transit ## Why cAIc cluster traffic is plaintext today: | Traffic | Protocol | Plaintext risk | |---------|----------|----------------| | AMQP (coordinator ↔ worker agent) | TCP :5672 | Registration, ping/pong, swap commands | | Inference (coordinator → worker llama-server) | HTTP :8081 | Every token generated | | LLM RPC layer offload (coordinator llama-server → worker) | TCP :50052 | Internal llama.cpp protocol | WireGuard encrypts all three at the network layer with zero application changes. The cAIc app keeps using `http://` URLs — it's just talking to a virtual IP whose traffic is automatically encrypted before it hits the wire. ## Topology ``` ┌───────────────────────┐ WireGuard tunnel ┌───────────────────────┐ │ Coordinator (ultron) │◄═══════════════════════════►│ Worker (jarvis) │ │ 10.0.2.1 │ UDP :51820 │ 10.0.2.2 │ │ LAN 192.168.50.108 │ │ LAN 192.168.50.210 │ │ │════════════════════════════►│ │ │ │ UDP :51820 │ Worker (corsair) │ │ │ │ 10.0.2.3 │ │ │ │ LAN (DHCP) │ └───────────────────────┘ └───────────────────────┘ ``` All nodes connect directly to the coordinator's WireGuard endpoint (star topology). Workers do not need to talk to each other. ## Prerequisites ```bash # Debian / Ubuntu sudo apt install wireguard # Windows / WSL2 — install WireGuard from https://www.wireguard.com/install/ # The wg.exe binary is used inside WSL2; the Windows GUI manages the tunnel config ``` ## Key Generation Run once per node. Save the private key securely; public keys go into peer configs on the other end. ```bash wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key chmod 600 /etc/wireguard/private.key ``` **Recorded keys for this deployment:** | Node | Private key | Public key | |------|-------------|------------| | ultron | (node private) | `ultron_pubkey=` | | jarvis | (node private) | `jarvis_pubkey=` | | corsair | (node private) | `corsair_pubkey=` | ## Per-Node Configs ### Coordinator — `/etc/wireguard/wg0.conf` on ultron ```ini [Interface] Address = 10.0.2.1/24 ListenPort = 51820 PrivateKey = # Enable IP forwarding so workers can route through coordinator if needed # sudo sysctl -w net.ipv4.ip_forward=1 # sudo sysctl -w net.ipv6.conf.all.forwarding=1 # Worker: jarvis [Peer] PublicKey = AllowedIPs = 10.0.2.2/32 # If jarvis is off-site, put its public IP / DDNS hostname here: # Endpoint = jarvis.example.com:51820 # If jarvis is LAN-only, set PersistentKeepalive = 25 to maintain NAT binding: # PersistentKeepalive = 25 # Worker: corsair [Peer] PublicKey = AllowedIPs = 10.0.2.3/32 ``` ### Worker — `/etc/wireguard/wg0.conf` on jarvis (Linux) ```ini [Interface] Address = 10.0.2.2/24 ListenPort = 51820 PrivateKey = # Coordinator [Peer] PublicKey = AllowedIPs = 10.0.2.0/24 # LAN-only: just point at the LAN IP Endpoint = 192.168.50.108:51820 # Off-site: use DDNS or static IP: # Endpoint = ultron.example.com:51820 PersistentKeepalive = 25 ``` ### Worker — Windows / WSL2 on corsair Create a WireGuard tunnel in the Windows GUI app with the same config as jarvis above (Address=10.0.2.3/24). WSL2 inside Windows can reach the tunnel IP via the Windows host. If llama-server runs inside WSL2 on corsair, the Windows host's WireGuard tunnel IP `10.0.2.3` is reachable from the WSL2 instance as well — just bind llama-server to `0.0.0.0` (already the default) and configure the Windows firewall to allow inbound on :8081 from the coordinator's WireGuard IP. ## Starting the Tunnel ```bash # Start immediately sudo systemctl start wg-quick@wg0 # Enable on boot sudo systemctl enable wg-quick@wg0 # Check status sudo wg show ``` Expected output on each node: ``` interface: wg0 public key: <...> private key: (hidden) listening port: 51820 peer: endpoint: 192.168.50.108:51820 allowed ips: 10.0.2.0/24 latest handshake: 5 seconds ago ← healthy transfer: 1.2 KiB received, 3.4 KiB sent ``` If `latest handshake` is missing, check firewall rules (UDP :51820 must be open on all nodes). ## Verification ```bash # From any node, ping another node's WireGuard IP ping -c 3 10.0.2.1 # coordinator ping -c 3 10.0.2.2 # jarvis ping -c 3 10.0.2.3 # corsair # Verify cAIc inference through the tunnel curl http://10.0.2.2:8081/v1/models # jarvis llama-server curl http://10.0.2.3:8081/v1/models # corsair llama-server ``` ## Updating cAIc to Use the Tunnel Once WireGuard is running, point each service at the tunnel IP instead of the LAN IP. ### Worker node agent config — `/etc/caic-node-agent.conf` ```ini [agent] node_name = jarvis node_ip = 10.0.2.2 # was 192.168.50.210 node_type = worker capabilities = llm amqp_url = amqp://caic:password@10.0.2.1:5672/caic # was 192.168.50.108 llama_port = 8081 models_dir = /var/lib/caic/models active_model = qwen2.5-7b-instruct-Q5_K_M.gguf ``` ### Coordinator config — environment variables ```bash # On the coordinator node, override the worker-facing addresses # (LLAMA_SERVER_BASE stays as localhost / LAN IP since inference # to the coordinator's own llama-server stays on-machine) export CAIC_AMQP_URL="amqp://caic:password@10.0.2.1:5672/caic" ``` No other cAIc code changes are needed. The app already reads `CAIC_AMQP_URL` from the environment (`config.py:27`) and the node agent reads `node_ip` from its INI file. Inference requests routed to remote workers via `triage.py` use the IP the worker registered — so setting `node_ip = 10.0.2.2` in the worker's agent config is all it takes. ## Cross-Site Deployment Checklist When placing a worker outside the LAN: 1. **Firewall:** Open UDP :51820 on the remote site. On the coordinator side, make sure UDP :51820 is reachable from the internet (port forward / firewall rule at the coordinator's router). 2. **DDNS:** If the coordinator's public IP is dynamic, set up a DDNS hostname and use it in the worker's `Endpoint = ultron.example.com:51820`. 3. **PersistentKeepalive:** Set `PersistentKeepalive = 25` on the worker side to keep NAT bindings alive. 4. **No double encryption:** WireGuard encrypts everything on the WireGuard interface. The cAIc app continues to use `http://` — it never touches raw TLS. This is correct and intended. 5. **Split tunnelling (optional):** The worker's `AllowedIPs = 10.0.2.0/24` ensures only cluster traffic goes through the tunnel. All other internet traffic from the worker uses its normal gateway.