# WireGuard gate (SP1) — manual runbook The host scripts (`docker/wg/clusev-wg.sh`, `clusev-wg-gate.service`, the `install.sh` additions) cannot be exercised by the PHPUnit suite — the test runner is the app container, with no host WireGuard/firewall/kernel access. This runbook is their real verification. Run on a **throwaway Debian-13 VM** with the Clusev prod stack up. **Keep an SSH session open the whole time** — it is the escape hatch. Tick each step against its expected result. ## Pre - [ ] `clusev wg help` prints usage; `clusev wg status` shows wg0 not active, gate off. - [ ] `wireguard-tools` + `qrencode` present (installed by `install.sh`): `command -v wg qrencode`. ## setup - [ ] `sudo clusev wg setup`, enter a subnet that overlaps the VM's LAN (e.g. the VM's own /24) → **rejected**, re-prompts. - [ ] Re-run, accept the default `10.99.0.0/24` → **accepted**; server keys + first peer created; client config + QR printed; `systemctl is-active wg-quick@wg0` → `active`. - [ ] `/etc/wireguard/wg0.conf` and `/etc/clusev/wg.env` exist, mode `0600`. - [ ] `sudo clusev wg setup` again → it **warns** that wg0.conf exists and does NOT silently clobber it (aborts unless you type `reconfigure`). ## tunnel (gate still OFF) - [ ] Import the printed client (QR) into the WireGuard app; connect. - [ ] Over the tunnel: `http://` reaches the panel. - [ ] Public `http://` still reachable (gate is off). ## gate up - [ ] `sudo clusev wg up` → prints the escape reminder; refuses (non-zero) if wg0 is down. - [ ] Public `http://` 80/443 now **refused / times out**; the tunnel still serves the panel. - [ ] **SSH still works** (port 22 untouched). - [ ] `clusev wg status` shows the peer + gate on (marker `/etc/clusev/wg-gate.enabled` present, chain `CLUSEV-WG-GATE` present). - [ ] Add a manual rule: `sudo iptables -I DOCKER-USER -s 203.0.113.0/24 -j RETURN`. Run `clusev wg down` then `clusev wg up` → the manual rule **survives** (only `CLUSEV-WG-GATE` and its jump are touched). Remove it afterwards. ## persistence - [ ] Reboot → wg0 + the gate come back (public still refused, tunnel still serves). - [ ] Restart Docker: `sudo systemctl restart docker` → the gate is re-applied (Docker flushes `DOCKER-USER`, the `clusev-wg-gate.service` re-runs via `WantedBy=docker.service`). Public still refused. - [ ] Force wg0 to fail on boot (`sudo systemctl disable wg-quick@wg0` + reboot) → the gate is **NOT applied** (`ConditionPathExists=/sys/class/net/wg0` fails the unit), the panel is publicly reachable (NOT bricked); SSH + `clusev wg down` recover. Re-enable wg0 afterwards. ## peers + down - [ ] `clusev wg add-peer client-2` → new config + QR; the split-vs-full-tunnel note is shown. - [ ] `clusev wg remove-peer client-2` → peer gone from `wg show wg0` AND from `/etc/wireguard/wg0.conf`. - [ ] A peer name with a space or odd character round-trips (add then remove) without corrupting wg0.conf. - [ ] `clusev wg down` → public open again; marker + `CLUSEV-WG-GATE` chain gone (`clusev wg status`). ## Known SP1 limitations (by design — not bugs) - Peer-IP allocation + the subnet collision check assume the default **/24** (`next_free_ip` scans `.2–.254`; the collision check matches the first three octets). Non-/24 subnets work for the tunnel but allocation is /24-shaped. Wider prefixes are SP2 territory. - The collision check is a heuristic (first-three-octets match against host routes/addresses), not a formal CIDR-overlap proof — it catches the common LAN clash, which is the point. - Peers are stored only in `wg0.conf` (no DB). SP2 adds the dashboard status page + peer models.