docs(wireguard): document the VPN console and the invariants behind it
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>feat/portal-design
parent
d1302e17bc
commit
cad245c5df
|
|
@ -56,6 +56,50 @@ and skipping every `hosts.wg_ip` already taken. `addPeer()`/`removePeer()` run
|
|||
`wg set` and then `wg-quick save wg0` — without the save, peers are lost on the
|
||||
next restart.
|
||||
|
||||
## The console (/admin/vpn)
|
||||
|
||||
Create, block and remove accesses, and see who is actually connected. Requires
|
||||
the `vpn.manage` capability (Owner/Admin) — it also hides the nav entry, because
|
||||
a VPN access reaches the management network.
|
||||
|
||||
The web container has no wg0, so the page never talks to WireGuard. `SyncVpnPeers`
|
||||
runs on the provisioning queue (every minute from the scheduler, plus a throttled
|
||||
nudge from the page's five-second poll) and copies handshakes, traffic counters
|
||||
and source endpoints into `vpn_peers`. The page reads the database.
|
||||
|
||||
Two fields carry the state, and keeping them apart is what makes the page honest:
|
||||
|
||||
- `enabled` — the operator's intent
|
||||
- `present` — what the hub actually reports
|
||||
|
||||
They disagree only while a change is in flight, which the UI shows as
|
||||
"wird angewendet". A sync never writes `enabled`.
|
||||
|
||||
Peers the host pipeline registers are adopted into the same list and named after
|
||||
their host, so "who is on the VPN" has no blind spots.
|
||||
|
||||
Things that are deliberate, and worth knowing before changing them:
|
||||
|
||||
- **Deleting is a soft delete.** The row survives as a tombstone until the hub
|
||||
confirms the peer is gone. A hard delete would let the next sync see the
|
||||
still-present peer and adopt it back as a live access — silently restoring
|
||||
what was just revoked. The tombstone also keeps the key and tunnel address
|
||||
reserved meanwhile.
|
||||
- **Every hub mutation takes `Cache::lock('wireguard:hub')`** — ApplyVpnPeer,
|
||||
RemoveWireguardPeer and ConfigureWireguard. Without it a sync could read the
|
||||
interface around a mutation and then write what it saw.
|
||||
- **Address allocation takes `Cache::lock('wireguard:allocate')`.** Addresses
|
||||
come from one subnet but live in two tables (`hosts.wg_ip`,
|
||||
`vpn_peers.allowed_ip`), so no unique index can catch the other side's insert.
|
||||
- **ApplyVpnPeer reads the current row**, not the state captured at dispatch: a
|
||||
retried job must not undo a newer decision. A missing row can only mean the
|
||||
access is gone, so a stale payload may remove but never enable.
|
||||
- **A host's key cannot be re-used** for an operator access: `wg set` would
|
||||
rewrite that host's allowed-ip and cut its management tunnel.
|
||||
- Generated keypairs come from libsodium in PHP, not `wg genkey` — this
|
||||
container has no interface, and it keeps generation testable. The private key
|
||||
is shown once and never stored.
|
||||
|
||||
## Verifying it actually works
|
||||
|
||||
Mocked tests cannot prove a tunnel. Against the running stack:
|
||||
|
|
|
|||
Loading…
Reference in New Issue