Operator turned off ExpressVPN auto-renew and purchased PIA. The single-shared- gateway model is retained; provider/client/protocol change to PIA via Gluetun over WireGuard with native port forwarding (qbittorrent-natmap port-sync). Simpler than ExpressVPN: no custom OpenVPN config, just PIA user/pass SOPS-encrypted; Gluetun provides the built-in kill-switch. Updated: ADR-0013 (new) + ADR-0012 superseded + index; docs 00,01,03,07; task board (T-3.2p/T-3.2, OQ-3). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
3.9 KiB
3.9 KiB
ADR-0012 — VPN egress: single shared in-cluster ExpressVPN gateway
- Status: Superseded by ADR-0013 (2026-08-15)
- Date: 2026-08-15
- Serves: R-NET-05, R-NET-06, R-NET-07
- Supersedes: ADR-0008
Superseded. The single-shared-gateway model here is retained, but the operator moved off ExpressVPN to PIA (Gluetun-native, WireGuard, port forwarding). Only the provider/client/ protocol changed — see ADR-0013. Retained for history.
Context
ADR-0008 offered two VPN-egress patterns (Gluetun sidecar per pod as default, central gateway as alternative) and left the provider open (OQ-3). The operator has now specified (OQ-3):
- Provider is ExpressVPN; the cluster must provision and control the connection (nothing on the edge router — the edge is a dumb SOHO device with no VPN, OQ-1).
- Protocol must not be TCP; UDP or better is required, data must be protected.
- A single shared VPN connection is preferred over per-service sidecars, so network rules are enforced outside the service.
This changes the decision, so ADR-0008 is superseded.
Decision
Adopt a single, shared, in-cluster VPN egress gateway as the mandated model (ADR-0008's "Pattern B" becomes the only pattern; the per-service Gluetun sidecar default is dropped):
- One VPN egress gateway (HA-capable Deployment, run active/standby) holds a single ExpressVPN tunnel using a non-TCP protocol: OpenVPN UDP (via ExpressVPN's manual OpenVPN-UDP config), or Lightway-UDP if a containerizable client proves viable. The cluster — not the edge — provisions and controls the tunnel.
vpn-class pods are steered through the gateway (Cilium Egress Gateway / routing) and have no independent egress path. The kill-switch (fail-closed) and egress allow rules are enforced at the gateway + namespace/policy level — outside each service. A service is merely labeledegress: vpn; it cannot leak on its own even if misconfigured.- Credentials (ExpressVPN manual OpenVPN config + manual-config username/password) are stored SOPS-encrypted (ADR-0007).
- The
egress: internet | vpnservice contract is unchanged (R-NET-07).
Alternatives considered
- ADR-0008 Pattern A (Gluetun sidecar per pod) — rejected per OQ-3: puts VPN inside each service (rules enforced in the service), multiplies tunnels, and isn't the operator's preferred single- connection model.
- VPN on the edge router — impossible: the SOHO edge has no VPN capability (OQ-1); the cluster must own the tunnel.
- WireGuard — ExpressVPN does not offer WireGuard; OpenVPN UDP (or Lightway-UDP) is the non-TCP path.
- OpenVPN TCP — rejected: operator requires non-TCP.
Consequences
- Rules enforced outside the service (operator's goal); one tunnel to manage; simpler per-service workloads (no sidecars).
- Single shared egress IP for all
vpnservices and a single failure domain → mitigate with active/standby gateway and tunnel-state alerting (10). - ExpressVPN is not a first-class containerized provider: expect a custom OpenVPN-UDP setup (e.g. gluetun custom-provider mode or a purpose-built OpenVPN client), and the operator must obtain the manual OpenVPN config + manual credentials. Tunnel + kill-switch must be validated in dev/test (fault-injection test, R-TEST-05).
- Exact pod→gateway steering (Cilium Egress Gateway vs routing/SNAT) is a WS-NET implementation detail; the kill-switch acceptance test is the gate (14).
- If ExpressVPN containerization proves impractical, revisit provider choice via a superseding ADR (escalation to the operator — agents may not change this decision themselves).