ADR-0015: VPN egress steering — node-level tunnel + Cilium Egress Gateway

The in-cluster Gluetun-pod gateway (ADR-0013) cannot be steered: a pod's tun0 is
invisible to the node datapath, and hostNetwork Gluetun hijacks the node's own
default route (breaks k3s/etcd). The tunnel itself works (PIA egress validated),
but not the steering.

New decision (ADR-0015): move the tunnel to a HOST OpenVPN systemd service on
egress-gateway node(s) (Ansible-managed, split-tunnel so it doesn't touch the node
default route), steer egress-class=vpn pods with a CiliumEgressGatewayPolicy (native
eBPF, no client-pod changes), and enforce the kill-switch with a host nftables rule
(fail-closed independent of Cilium). PIA/OpenVPN-UDP provider decision (ADR-0013)
stands; the .ovpn transfers over; PF re-solved host-side (Phase 6).

Amended for consistency: ADR-0013 (client+steering superseded), decisions/README,
docs/00, docs/01, docs/03, docs/07, agents/task-board.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 4.8 committed 2026-08-24 00:46:18 -05:00
1 parent 0ba06ec25b
commit 7598cff945
8 files changed
+130 -38

No files matched your search

+3 -3
View File
@@ -50,8 +50,8 @@ 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** (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.2p | **Prereq:** provide **PIA username/password + `.ovpn`**; store in **Ansible vault** on the egress-gateway node(s) (ADR-0015) | WS-SEC | R-NET-05 | — | — | todo |
| T-3.2 | **Node-level PIA/OpenVPN tunnel** (host systemd, Ansible, split-tunnel) on egress-gateway node(s) + **CiliumEgressGatewayPolicy** steering `egress-class=vpn` + **host nftables kill-switch** + host PF helper for qBittorrent (ADR-0015) | 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, **OpenVPN-UDP** + native PF (ADR-0013 amended 2026-08-22; WireGuard not supported for PIA in Gluetun, supersedes 0012) |
| OQ-3 | VPN provider/protocol + egress steering | — | ✅ resolved: **PIA over OpenVPN-UDP** (ADR-0013); steering = **node-level host tunnel + Cilium Egress Gateway + host nftables kill-switch** (ADR-0015, supersedes the in-cluster-Gluetun steering — a Gluetun pod's tun0 can't be steered by Cilium egress gateway) |
| 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 |