Merge Phase-3 VPN egress gateway (author-first)
This commit is contained in:
5 files changed
+179
-15
No files matched your search
@@ -2,30 +2,24 @@
|
|||||||
# TEMPLATE — NOT APPLIED, NOT ENCRYPTED (.yaml.example is ignored by kustomize
|
# TEMPLATE — NOT APPLIED, NOT ENCRYPTED (.yaml.example is ignored by kustomize
|
||||||
# and never auto-encrypted by SOPS). Reference scaffold only.
|
# and never auto-encrypted by SOPS). Reference scaffold only.
|
||||||
#
|
#
|
||||||
# (a) Copy to: (TO BE DECIDED AT PHASE 3 — VPN component not authored yet)
|
# (a) Copy to: platform/vpn-egress/pia-credentials.sops.yaml
|
||||||
# (b) kustomization: DO NOT WIRE YET.
|
# (b) kustomization: uncomment `- pia-credentials.sops.yaml` in platform/vpn-egress/kustomization.yaml
|
||||||
# (c) Encrypt: sops -e -i <target>.sops.yaml (once the target path exists)
|
# (c) Encrypt: sops -e -i platform/vpn-egress/pia-credentials.sops.yaml
|
||||||
# (d) Consumed by: Phase-3 Gluetun VPN egress gateway. The consuming
|
# (d) Consumed by: the Phase-3 Gluetun VPN egress gateway (platform/vpn-egress/gluetun-gateway.yaml),
|
||||||
# component AND its namespace DO NOT EXIST yet — this template
|
# which maps username->OPENVPN_USER, password->OPENVPN_PASSWORD. [Phase 3]
|
||||||
# is provisioned upfront. [Phase 3]
|
|
||||||
#
|
|
||||||
# !! NAMESPACE UNKNOWN — the metadata.namespace below is a placeholder. Set it to
|
|
||||||
# the VPN gateway's namespace when that component is authored at Phase 3. !!
|
|
||||||
#
|
#
|
||||||
# VALUE NOTES:
|
# VALUE NOTES:
|
||||||
# - username: PIA (Private Internet Access) user, form pNNNNNNN.
|
# - username: PIA (Private Internet Access) user, form pNNNNNNN.
|
||||||
# - password: PIA password.
|
# - password: PIA password.
|
||||||
# - Gluetun typically reads OPENVPN_USER / OPENVPN_PASSWORD (OpenVPN) or
|
# - Protocol is WireGuard (VPN_TYPE=wireguard, UDP — satisfies "not TCP"). PIA on WireGuard STILL
|
||||||
# WireGuard credentials. Confirm the chosen protocol when wiring — the
|
# authenticates with the username/password below (Gluetun derives the WireGuard config from PIA's
|
||||||
# requirement says NOT TCP. The env var names / key names the Gluetun
|
# API at runtime), so these two keys are correct as-is — no extra WireGuard key material needed.
|
||||||
# deployment expects may differ from username/password below; adjust to match
|
|
||||||
# the chosen protocol before encrypting.
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: pia-credentials
|
name: pia-credentials
|
||||||
namespace: REPLACE-ME-PHASE-3-VPN-NAMESPACE
|
namespace: vpn-egress
|
||||||
type: Opaque
|
type: Opaque
|
||||||
stringData:
|
stringData:
|
||||||
username: REPLACE-ME
|
username: REPLACE-ME
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# platform/vpn-egress — shared VPN egress gateway (Phase 3, ADR-0013)
|
||||||
|
|
||||||
|
Single shared **Gluetun** gateway: **PIA over WireGuard**, built-in kill-switch, native port
|
||||||
|
forwarding. `vpn`-class services (sonarr, radarr, jackett, qbittorrent) egress **only** through this
|
||||||
|
tunnel; `internet`-class services go out the normal WAN. Serves **R-NET-05/06/07**.
|
||||||
|
|
||||||
|
> **STATUS: authored, NOT deployed.** Not wired into `platform/kustomization.yaml` yet. Deploy needs
|
||||||
|
> (a) the `pia-credentials` SOPS secret, and (b) validation of the egress-steering mechanism below,
|
||||||
|
> which cannot be tested without a live PIA tunnel.
|
||||||
|
|
||||||
|
## Pieces
|
||||||
|
- `gluetun-gateway.yaml` — the Gluetun Deployment (`replicas: 1`, `Recreate`) + the `gluetun-control`
|
||||||
|
Service (control API :8000 for forwarded-port lookup + tunnel-state health). Kill-switch = `FIREWALL=on`.
|
||||||
|
- `pia-credentials` (SOPS) — PIA username/password only; Gluetun derives the WireGuard config at runtime.
|
||||||
|
|
||||||
|
## Operator hand-off (when deploying Phase 3)
|
||||||
|
1. Provision `pia-credentials` (see `bootstrap/sops/secret-templates/pia-credentials.yaml.example`,
|
||||||
|
namespace `vpn-egress`, keys `username`/`password`) → `sops -e -i` → uncomment it in `kustomization.yaml`.
|
||||||
|
2. Set cluster-vars: `PIA_REGION` (a **port-forwarding-capable** PIA region — e.g. `Netherlands`,
|
||||||
|
`CA Toronto`, `Switzerland`; NOT a US region, which lack PF) and `CLUSTER_POD_CIDR` (k3s default
|
||||||
|
`10.42.0.0/16`).
|
||||||
|
3. Enable `- vpn-egress` in `platform/kustomization.yaml` (Phase 3).
|
||||||
|
|
||||||
|
## Egress steering — the DESIGN DECISION (needs live validation)
|
||||||
|
The hard part: routing `vpn`-class pods through this ONE shared tunnel. Cilium Egress Gateway
|
||||||
|
(`CiliumEgressGatewayPolicy`) SNATs matched pods out of a **gateway node's interface** — it does not
|
||||||
|
natively route through a *pod's* tunnel. Options considered:
|
||||||
|
|
||||||
|
1. **Cilium Egress Gateway → the Gluetun node's tunnel interface (recommended).** Pin Gluetun to a
|
||||||
|
node; a `CiliumEgressGatewayPolicy` selects pods labeled `homelab.keithjackson.dev/egress-class=vpn`
|
||||||
|
and egresses them via that node's WireGuard interface (`wg0`). If the tunnel drops, `wg0` is gone →
|
||||||
|
traffic has no egress path → **fail-closed** (the kill-switch behavior R-NET-06 needs). This is the
|
||||||
|
most Cilium-native fit but requires Gluetun's tunnel interface to be visible to the node datapath
|
||||||
|
(hostNetwork Gluetun, or a shared netns) — the detail to VALIDATE.
|
||||||
|
2. **Gateway-pod routing:** client pods default-route to the Gluetun pod IP (init-container +
|
||||||
|
NET_ADMIN, or a Multus secondary interface); Gluetun forwards+NATs via the tunnel. Works without
|
||||||
|
Cilium egress gateway but is more moving parts and pod-IP-fragile.
|
||||||
|
3. **Per-pod Gluetun sidecar** — explicitly REJECTED by ADR-0013 (single shared gateway preferred).
|
||||||
|
|
||||||
|
**Plan:** validate option 1 against a live PIA tunnel (confirm `wg0` on the Gluetun node + a
|
||||||
|
`CiliumEgressGatewayPolicy` steering `egress-class=vpn` pods, then run the `egress-class` (R-NET-05/07)
|
||||||
|
and `vpn-killswitch` (R-NET-06) conformance tests). Fall back to option 2 if the interface isn't
|
||||||
|
addressable. The `CiliumEgressGatewayPolicy` is intentionally NOT committed until validated, to avoid
|
||||||
|
shipping an untested datapath policy.
|
||||||
|
|
||||||
|
## Port forwarding (qBittorrent) — follow-up
|
||||||
|
PIA assigns a forwarded port that can change on reconnect. A small **port-sync helper**
|
||||||
|
(`qbittorrent-natmap`) reads the current port from the `gluetun-control` API (:8000
|
||||||
|
`/v1/openvpn/portforwarded`) and writes it into qBittorrent, keeping it hands-off (R-UPD). Authored
|
||||||
|
alongside the qBittorrent app (Phase 6 apps / or here when qBittorrent lands).
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
# Gluetun — the single shared in-cluster VPN egress gateway (ADR-0013): PIA over WireGuard, with
|
||||||
|
# Gluetun's built-in kill-switch/firewall (fail-closed), health checks, auto-reconnect, and native
|
||||||
|
# port forwarding for qBittorrent. `vpn`-class pods are steered through this gateway (see README.md
|
||||||
|
# "Egress steering" — the Cilium-native mechanism, pending live validation with PIA creds).
|
||||||
|
#
|
||||||
|
# Credentials: the `pia-credentials` SOPS Secret (keys: username, password). Gluetun uses PIA's
|
||||||
|
# username/password (via OPENVPN_USER/OPENVPN_PASSWORD — legacy env names it uses for PIA on BOTH
|
||||||
|
# protocols) to authenticate to PIA's API and derive the WireGuard config at runtime — so WireGuard
|
||||||
|
# needs no extra key material (ADR-0013).
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: gluetun
|
||||||
|
namespace: vpn-egress
|
||||||
|
labels:
|
||||||
|
app: gluetun
|
||||||
|
spec:
|
||||||
|
replicas: 1 # ONE shared tunnel (ADR-0013). Active/standby is a later enhancement.
|
||||||
|
strategy:
|
||||||
|
type: Recreate # a tunnel is a singleton — never run two overlapping during a rollout
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: gluetun
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gluetun
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: gluetun
|
||||||
|
image: qmcgaw/gluetun:v3.40.0 # pinned; Renovate bumps
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add: ["NET_ADMIN"] # required for the tunnel + firewall
|
||||||
|
env:
|
||||||
|
- name: VPN_SERVICE_PROVIDER
|
||||||
|
value: "private internet access"
|
||||||
|
- name: VPN_TYPE
|
||||||
|
value: "wireguard" # UDP — satisfies "protocol != TCP" (R-NET); upgrade over OpenVPN
|
||||||
|
# PIA credentials from the SOPS secret (Gluetun's PIA creds env, used for WireGuard too):
|
||||||
|
- name: OPENVPN_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: pia-credentials
|
||||||
|
key: username
|
||||||
|
- name: OPENVPN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: pia-credentials
|
||||||
|
key: password
|
||||||
|
- name: SERVER_REGIONS
|
||||||
|
value: "${PIA_REGION}" # cluster-vars: a PF-CAPABLE PIA region (ADR-0013 PF caveat)
|
||||||
|
- name: VPN_PORT_FORWARDING
|
||||||
|
value: "on" # PIA native port forwarding for qBittorrent
|
||||||
|
- name: FIREWALL
|
||||||
|
value: "on" # KILL-SWITCH: block all egress except the tunnel (fail-closed, R-NET-06)
|
||||||
|
- name: FIREWALL_OUTBOUND_SUBNETS
|
||||||
|
value: "${CLUSTER_POD_CIDR}" # allow steered client pods (pod CIDR) to route out via the tunnel
|
||||||
|
- name: DOT
|
||||||
|
value: "off" # DNS via cluster CoreDNS, not Gluetun's DoT
|
||||||
|
ports:
|
||||||
|
- name: control
|
||||||
|
containerPort: 8000 # Gluetun control API (health + forwarded-port lookup)
|
||||||
|
livenessProbe:
|
||||||
|
httpGet: { path: /v1/openvpn/status, port: 8000 } # Gluetun control API health
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 30
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
memory: 160Mi
|
||||||
|
volumeMounts:
|
||||||
|
- name: tun
|
||||||
|
mountPath: /dev/net/tun
|
||||||
|
volumes:
|
||||||
|
- name: tun
|
||||||
|
hostPath:
|
||||||
|
path: /dev/net/tun
|
||||||
|
type: CharDevice
|
||||||
|
---
|
||||||
|
# Control API Service — the port-sync helper (qbittorrent-natmap, README) reads the PIA-assigned
|
||||||
|
# forwarded port from here; also used for tunnel-state health/alerting (ADR-0013 "alert on tunnel state").
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gluetun-control
|
||||||
|
namespace: vpn-egress
|
||||||
|
labels:
|
||||||
|
app: gluetun
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: gluetun
|
||||||
|
ports:
|
||||||
|
- name: control
|
||||||
|
port: 8000
|
||||||
|
targetPort: 8000
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- gluetun-gateway.yaml
|
||||||
|
# SOPS-encrypted PIA credentials (keys: username, password). Operator flow:
|
||||||
|
# copy bootstrap/sops/secret-templates/pia-credentials.yaml.example here as pia-credentials.sops.yaml
|
||||||
|
# (namespace: vpn-egress), fill + `sops -e -i`, then uncomment. See bootstrap/sops/CREDENTIALS.md.
|
||||||
|
# - pia-credentials.sops.yaml
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# vpn-egress — the single shared VPN egress gateway namespace (ADR-0013, docs/03).
|
||||||
|
# Platform INFRA, not an application namespace: the Gluetun gateway needs NET_ADMIN + /dev/net/tun,
|
||||||
|
# so this ns is deliberately NOT labeled `app-namespace=true` (Kyverno restrict-host-namespaces /
|
||||||
|
# require-*-label policies scope to app namespaces only — see platform/kyverno).
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: vpn-egress
|
||||||
|
labels:
|
||||||
|
homelab.keithjackson.dev/app-namespace: "false"
|
||||||
Reference in new issue
Block a user