diff --git a/agents/task-board.md b/agents/task-board.md index 06486da..eee5685 100644 --- a/agents/task-board.md +++ b/agents/task-board.md @@ -51,7 +51,7 @@ Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workst | T-3.0 | L3 fault-injection harness — **in-cluster Chainsaw Job** (CI-triggered, scoped test SA) using `script`/`try`/`finally` (force tunnel down, kill node, inject bad image, trigger restore) | WS-TEST | R-TEST-05, R-SEC-02 | T-0.4 | — | todo | | T-3.1 | Egress classes; Cilium Egress Gateway wiring (steer `vpn` pods → gateway) | WS-NET | R-NET-04/05 | T-2.2 | — | todo | | T-3.2p | **Prereq:** provide **PIA username/password**; store SOPS-encrypted (PIA purchased) | WS-SEC | R-NET-05 | — | — | todo | -| T-3.2 | **Single shared in-cluster PIA/Gluetun gateway** (WireGuard, active/standby) + built-in kill-switch + native PF (`qbittorrent-natmap` port-sync) (ADR-0013) | WS-NET | R-NET-05/06 | T-3.1, T-3.2p | — | todo | +| T-3.2 | **Single shared in-cluster PIA/Gluetun gateway** (OpenVPN-UDP, active/standby) + built-in kill-switch + native PF (`qbittorrent-natmap` port-sync) (ADR-0013) | WS-NET | R-NET-05/06 | T-3.1, T-3.2p | — | todo | | ~~T-3.3~~ | ~~Edge-router multi-WAN split~~ — **out of scope** (single WAN, dumb SOHO edge; OQ-1) | WS-NET | R-NET-04 | — | — | won't-do | | T-3.4 | Prove: VPN IP egress, **kill-switch fail-closed (central)**, egress-class flip via git | WS-NET | R-NET-05/06/07 | T-3.0, T-3.2 | — | todo | @@ -102,6 +102,6 @@ Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workst |---|---|---|---| | OQ-1 | WAN count + edge router | — | ✅ resolved: 1 WAN, dumb SOHO edge; multi-WAN out of scope; VPN + segmentation in-cluster | | OQ-2 | Bulk-media home | — | ✅ resolved: media outside cluster, backing TBD later (not blocking) | -| OQ-3 | VPN provider/protocol | — | ✅ resolved: **PIA** via Gluetun, single shared in-cluster tunnel, **WireGuard** + native PF (ADR-0013, supersedes 0012) | +| OQ-3 | VPN provider/protocol | — | ✅ resolved: **PIA** via Gluetun, single shared in-cluster tunnel, **OpenVPN-UDP** + native PF (ADR-0013 amended 2026-08-22; WireGuard not supported for PIA in Gluetun, supersedes 0012) | | OQ-4 | DNS-01 challenge access | — | ✅ resolved: scoped **Cloudflare API token** (Zone·DNS·Edit), SOPS-encrypted; drives cert-manager + ExternalDNS. Operator provides token (T-2.4p) | | **OQ-BKP** | **Off-cluster DR backup target** (S3/NFS endpoint) | T-4.3 | ⛔ **OPEN** — needed by Phase 4, not Phase 2 | diff --git a/decisions/README.md b/decisions/README.md index 5b09983..57695b4 100644 --- a/decisions/README.md +++ b/decisions/README.md @@ -54,5 +54,5 @@ serves. **Do not edit an accepted decision in place** — supersede it with a ne | [0010](adr-0010-conformance-testing.md) | Conformance testing: automated, reproducible, behavior-based | Accepted | R-TEST-01..06 | | [0011](adr-0011-test-tooling-kyverno-chainsaw.md) | Conformance test tooling: Kyverno + Chainsaw | Accepted | R-TEST-01..05 | | [0012](adr-0012-vpn-egress-single-expressvpn-gateway.md) | VPN egress: single shared in-cluster ExpressVPN gateway | ~~Superseded by 0013~~ | R-NET-05/06/07 | -| [0013](adr-0013-vpn-provider-pia-gluetun-wireguard.md) | VPN provider: PIA via Gluetun (WireGuard + port forwarding) | Accepted | R-NET-05/06/07 | +| [0013](adr-0013-vpn-provider-pia-gluetun-wireguard.md) | VPN provider: PIA via Gluetun (OpenVPN-UDP + port forwarding) | Accepted (amended 2026-08-22) | R-NET-05/06/07 | | [0014](adr-0014-idp-keycloak.md) | Identity Provider: Keycloak (SSO via OIDC + forward-auth) | Accepted | R-IDP-01..06 | diff --git a/decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md b/decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md index aad1a7f..0b0fe01 100644 --- a/decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md +++ b/decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md @@ -1,9 +1,24 @@ -# ADR-0013 — VPN provider: PIA via Gluetun (WireGuard + native port forwarding) -- Status: Accepted +# ADR-0013 — VPN provider: PIA via Gluetun (OpenVPN-UDP + native port forwarding) +- Status: Accepted (amended 2026-08-22 — protocol WireGuard → OpenVPN-UDP) - Date: 2026-08-15 - Serves: R-NET-05, R-NET-06, R-NET-07 - Supersedes: [ADR-0012](adr-0012-vpn-egress-single-expressvpn-gateway.md) +> **Amendment — 2026-08-22 (protocol: WireGuard → OpenVPN-UDP).** The original decision specified +> PIA over **WireGuard**. This is **not implementable with Gluetun**: Gluetun does not support PIA +> over WireGuard — upstream states native support "cannot be added… slow work in progress," and it +> was confirmed live rejecting `private internet access` as a WireGuard provider on v3.40 / v3.41 / +> latest. **PIA in Gluetun is OpenVPN-only.** The decision is amended to **PIA over OpenVPN-UDP via +> Gluetun**; everything else in this ADR stands (single shared gateway, central kill-switch / +> fail-closed, native port forwarding, egress steering, active/standby). OpenVPN-UDP satisfies +> R-NET's "non-TCP, UDP or better" requirement, and Gluetun's HTTP control API (health + +> forwarded-port lookup) is what the shared-gateway topology relies on — a file-based PF handoff +> (the WireGuard-container alternative) would pull toward the sidecar model ADR-0008 rejected. +> **WireGuard fallback (documented, not primary):** if OpenVPN-UDP throughput ever proves a +> bottleneck, `thrnz/docker-wireguard-pia` provides PIA-over-WireGuard as a drop-in gateway — a +> contained swap, since the egress-steering design is protocol-agnostic. The filename retains the +> `-wireguard` slug as a stable historical identifier. + ## Context ADR-0012 kept the operator's required model — a single shared in-cluster VPN gateway with rules enforced outside the service — but named **ExpressVPN** as the provider, which forced a custom @@ -13,13 +28,16 @@ which *is* first-class in Gluetun. The egress **model is unchanged**; only the p protocol change — so ADR-0012 is superseded. ## Decision -The single shared in-cluster VPN gateway runs **Gluetun** configured for **PIA** over **WireGuard**, +The single shared in-cluster VPN gateway runs **Gluetun** configured for **PIA** over **OpenVPN-UDP**, with Gluetun's **native automated port forwarding** for qBittorrent: - **Provider/client:** PIA via the **Gluetun** container as the gateway VPN client (Gluetun provides the built-in **kill-switch/firewall**, health checks, and auto-reconnect). -- **Protocol:** **WireGuard** (UDP) — satisfies "non-TCP, UDP or better" and is a strict upgrade over - ExpressVPN's OpenVPN-UDP. +- **Protocol:** **OpenVPN-UDP** — satisfies "non-TCP, UDP or better." (WireGuard was the original + choice but is not supported for PIA in Gluetun — see the amendment above.) Unlike the ExpressVPN + plan this needs **no bespoke OpenVPN build**: PIA is a first-class Gluetun provider, so it is + stock env-var config (`VPN_TYPE=openvpn`), and Gluetun exposes the tunnel state + forwarded port + over its HTTP control API. - **Credentials:** just the **PIA username/password**, stored **SOPS-encrypted** ([ADR-0007](adr-0007-secrets-sops-age.md)). No manual `.ovpn` extraction (the ExpressVPN prerequisite is gone). - **Port forwarding:** Gluetun native (`VPN_PORT_FORWARDING=on`, provider `private internet @@ -32,8 +50,13 @@ with Gluetun's **native automated port forwarding** for qBittorrent: ## Alternatives considered - **ExpressVPN (ADR-0012)** — dropped: not Gluetun-native, needed a custom OpenVPN workaround; and the operator's subscription is lapsing. -- **ProtonVPN** — equally good fit (Gluetun-native NAT-PMP PF, WireGuard); viable if PIA - disappoints, but the operator has purchased PIA. +- **PIA over WireGuard via a dedicated container (`thrnz/docker-wireguard-pia`)** — the only way to + keep **WireGuard** with PIA, since Gluetun cannot. Rejected as primary: it exposes the forwarded + port as a shared **file** (`/pia-shared/port.dat`) rather than an HTTP API, which fits a same-netns + sidecar (the model ADR-0008 rejected) better than a shared gateway, plus script-based health and a + smaller single-maintainer image. Retained as the WireGuard **fallback** (see amendment). +- **ProtonVPN** — equally good fit (Gluetun-native NAT-PMP PF, and Gluetun **does** support ProtonVPN + over WireGuard); viable if PIA disappoints, but the operator has purchased PIA. - **AirVPN** — WireGuard works in Gluetun but automated PF is not integrated (static port only); less hands-off. - **Mullvad** — no port forwarding since 2023 → degraded qBittorrent seeding; rejected for this diff --git a/docs/00-vision-and-scope.md b/docs/00-vision-and-scope.md index b88f32f..7b1c258 100644 --- a/docs/00-vision-and-scope.md +++ b/docs/00-vision-and-scope.md @@ -209,7 +209,7 @@ implementation. metadata DB still lives on replicated Longhorn. The **off-cluster DR backup target** (for app config, Nextcloud, Gitea, etc.) remains to be chosen at Phase 4. See [`04`](04-storage-and-state.md). - **OQ-3 (VPN)** — **RESOLVED**: **PIA** via Gluetun, cluster-provisioned, **single shared - in-cluster tunnel** over **WireGuard** (non-TCP), native port forwarding, rules enforced outside + in-cluster tunnel** over **OpenVPN-UDP** (non-TCP), native port forwarding, rules enforced outside the service (ADR-0013, supersedes ADR-0012's ExpressVPN choice). See [`03`](03-networking-isolation-and-egress.md). diff --git a/docs/01-architecture-overview.md b/docs/01-architecture-overview.md index 47d769c..1c819b2 100644 --- a/docs/01-architecture-overview.md +++ b/docs/01-architecture-overview.md @@ -15,7 +15,7 @@ choice here without a superseding ADR.** | VM workloads (IaaS) | **KubeVirt** (+ CDI) | R-PLAT-03 | [ADR-0005](../decisions/adr-0005-vm-workloads-kubevirt.md) | [06](06-vm-workloads-kubevirt.md) | | TLS | **cert-manager** + Let's Encrypt (DNS-01) | R-TLS-01..03 | [ADR-0006](../decisions/adr-0006-tls-cert-manager-letsencrypt.md) | [08](08-tls-and-certificates.md) | | Secrets | **SOPS + age** (Flux-native) | R-SEC-01 | [ADR-0007](../decisions/adr-0007-secrets-sops-age.md) | [07](07-security-and-secrets.md) | -| VPN egress | **Single shared in-cluster PIA/Gluetun gateway** (WireGuard + native PF) + Cilium Egress Gateway | R-NET-05/06/07 | [ADR-0013](../decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md) (supersedes 0012/0008) | [03](03-networking-isolation-and-egress.md) | +| VPN egress | **Single shared in-cluster PIA/Gluetun gateway** (OpenVPN-UDP + native PF) + Cilium Egress Gateway | R-NET-05/06/07 | [ADR-0013](../decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md) (supersedes 0012/0008) | [03](03-networking-isolation-and-egress.md) | | Automated updates | **Flux image-update-automation** (images) + **Renovate** (charts) | R-UPD-01..03 | [ADR-0009](../decisions/adr-0009-automated-image-updates.md) | [11](11-automated-updates.md) | | DNS records | **ExternalDNS** (public via Cloudflare) + **PiHole** (internal) | R-DNS-01..03 | — | [09](09-dns-and-ingress.md) | | Ingress (north-south) | **Gateway API** implemented by Cilium | R-DNS-01, R-TLS-01 | [ADR-0002](../decisions/adr-0002-cni-cilium.md) | [09](09-dns-and-ingress.md) | diff --git a/docs/03-networking-isolation-and-egress.md b/docs/03-networking-isolation-and-egress.md index 8925c52..970776c 100644 --- a/docs/03-networking-isolation-and-egress.md +++ b/docs/03-networking-isolation-and-egress.md @@ -93,7 +93,8 @@ shared VPN connection** — not a per-service sidecar — so that **network rule the service**: - **One in-cluster VPN egress gateway** (an HA-capable Deployment) runs **Gluetun** configured for - **PIA over WireGuard** (UDP — "non-TCP or better"). The cluster provisions and controls the tunnel + **PIA over OpenVPN-UDP** ("non-TCP or better"; WireGuard is not supported for PIA in Gluetun — + ADR-0013 amendment). The cluster provisions and controls the tunnel (nothing on the edge router). Gluetun provides the built-in **kill-switch/firewall**, health checks, and auto-reconnect. - **`vpn`-class pods are steered through the gateway** (Cilium Egress Gateway / routing); they have @@ -138,7 +139,7 @@ Exposure is a declared property: `internal` (LAN-only) vs `public` (port-forward ## Resolved - **OQ-1** — one commercial WAN, no multi-ISP plans; dumb SOHO edge (no VLAN/VPN/services). All segmentation + VPN in-cluster; edge does NAT + port-forward only. Multi-ISP routing out of scope. -- **OQ-3** — **PIA** via Gluetun, single shared in-cluster tunnel over **WireGuard**, native port +- **OQ-3** — **PIA** via Gluetun, single shared in-cluster tunnel over **OpenVPN-UDP**, native port forwarding, rules enforced outside the service (ADR-0013, supersedes ADR-0012's ExpressVPN choice). ## Open questions diff --git a/docs/07-security-and-secrets.md b/docs/07-security-and-secrets.md index c6d6e95..e2281aa 100644 --- a/docs/07-security-and-secrets.md +++ b/docs/07-security-and-secrets.md @@ -47,7 +47,7 @@ Defense in depth with declarative, auditable controls: [AGENTS.md §5](../AGENTS.md)). ### Secret categories to manage -- VPN credentials (PIA username/password; WireGuard) for the egress model (ADR-0013). +- VPN credentials (PIA username/password; OpenVPN-UDP) for the egress model (ADR-0013). - Keycloak admin credential and per-service **OIDC client secrets** ([15](15-identity-and-access.md)). - DNS provider API token (Cloudflare) for ACME DNS-01 + ExternalDNS. - SMTP credentials for Alertmanager email.