diff --git a/docs/superpowers/runbooks/wireguard-gate-runbook.md b/docs/superpowers/runbooks/wireguard-gate-runbook.md new file mode 100644 index 0000000..dc0507d --- /dev/null +++ b/docs/superpowers/runbooks/wireguard-gate-runbook.md @@ -0,0 +1,58 @@ +# 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.