Merge feature/adr-0015-node-vpn-egress into main
This commit is contained in:
8 files changed
+130
-38
No files matched your search
@@ -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 |
|
||||
+2
-1
@@ -54,5 +54,6 @@ 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 (OpenVPN-UDP + port forwarding) | Accepted (amended 2026-08-22) | R-NET-05/06/07 |
|
||||
| [0013](adr-0013-vpn-provider-pia-gluetun-wireguard.md) | VPN provider: PIA over OpenVPN-UDP + port forwarding | Accepted — provider/protocol only (client+steering superseded by 0015) | R-NET-05/06/07 |
|
||||
| [0014](adr-0014-idp-keycloak.md) | Identity Provider: Keycloak (SSO via OIDC + forward-auth) | Accepted | R-IDP-01..06 |
|
||||
| [0015](adr-0015-vpn-egress-steering-node-tunnel-cilium.md) | VPN egress steering: node-level tunnel + Cilium Egress Gateway (host kill-switch) | Accepted | R-NET-05/06/07 |
|
||||
@@ -1,5 +1,6 @@
|
||||
# ADR-0013 — VPN provider: PIA via Gluetun (OpenVPN-UDP + native port forwarding)
|
||||
- Status: Accepted (amended 2026-08-22 — protocol WireGuard → OpenVPN-UDP)
|
||||
- Status: Accepted — provider/protocol only (amended 2026-08-22 protocol→OpenVPN-UDP; Gluetun client + egress
|
||||
steering superseded by [ADR-0015](adr-0015-vpn-egress-steering-node-tunnel-cilium.md) 2026-08-23)
|
||||
- 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)
|
||||
@@ -19,6 +20,15 @@
|
||||
> contained swap, since the egress-steering design is protocol-agnostic. The filename retains the
|
||||
> `-wireguard` slug as a stable historical identifier.
|
||||
|
||||
> **Superseded in part — 2026-08-23 by [ADR-0015](adr-0015-vpn-egress-steering-node-tunnel-cilium.md).** The
|
||||
> **provider + protocol** here (PIA over OpenVPN-UDP, native port forwarding) STANDS. What is superseded: the
|
||||
> **client + placement** (a single in-cluster **Gluetun** pod) and the deferred **steering** mechanism. Live
|
||||
> validation confirmed the in-cluster Gluetun tunnel works (PIA egress) but **cannot be steered by Cilium
|
||||
> Egress Gateway** — Gluetun's `tun0` is in the pod netns, and `hostNetwork` Gluetun hijacks the node's own
|
||||
> default route (breaks k3s/etcd). ADR-0015 moves the tunnel to a **host OpenVPN service on egress-gateway
|
||||
> node(s)**, steered by **Cilium Egress Gateway** with a **host nftables kill-switch**. The same PIA `.ovpn`
|
||||
> transfers over.
|
||||
|
||||
## 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
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# ADR-0015 — VPN egress steering: node-level tunnel + Cilium Egress Gateway (host kill-switch)
|
||||
- Status: Accepted
|
||||
- Date: 2026-08-23
|
||||
- Serves: R-NET-05, R-NET-06, R-NET-07
|
||||
- Supersedes (egress-steering + tunnel placement of): [ADR-0013](adr-0013-vpn-provider-pia-gluetun-wireguard.md)
|
||||
— which now stands only for the **provider/protocol** (PIA over OpenVPN-UDP + port forwarding). By
|
||||
extension this also closes the steering question left open since [ADR-0008](adr-0008-vpn-egress-model.md).
|
||||
|
||||
## Context
|
||||
ADR-0013 placed the VPN in a **single shared in-cluster Gluetun pod** and *deferred* the egress-steering
|
||||
mechanism (how `egress-class=vpn` pods actually route through it), naming "Cilium Egress Gateway → the
|
||||
gateway's tunnel interface" as the recommended-but-unvalidated option.
|
||||
|
||||
Implementation (2026-08) proved that option **non-viable**, and validated the tunnel itself:
|
||||
- The in-cluster Gluetun tunnel **works** — PIA Stockholm egress confirmed, stable.
|
||||
- But `CiliumEgressGatewayPolicy` SNATs matched pods out of a **node** interface; Gluetun-in-a-pod's `tun0`
|
||||
lives in the *pod* netns, invisible to the node datapath. Running Gluetun `hostNetwork` to put `tun0` on
|
||||
the node **breaks the node**: Gluetun owns its netns routing and on connect redirects `0.0.0.0/0` via the
|
||||
tunnel — in host netns that hijacks the **node's own default route**, cutting k3s/etcd/kubelet off. Gluetun
|
||||
has no "create `tun0` but don't touch the default route" mode.
|
||||
- The accepted community alternative — the **pod-gateway** pattern (VXLAN overlay + admission-webhook-injected
|
||||
client routing + gateway NAT, e.g. `angelnu/pod-gateway`) — works but is heavy, mutates client pods, and
|
||||
side-steps Cilium's datapath.
|
||||
|
||||
The clean fix is to move the tunnel to where routing is fully controllable — the **node** — and use Cilium's
|
||||
native egress gateway to steer.
|
||||
|
||||
## Decision
|
||||
The `egress: vpn` contract is realized by a **node-level VPN tunnel steered by Cilium Egress Gateway, with a
|
||||
host-level kill-switch**:
|
||||
|
||||
- **Tunnel — host service, not a pod.** One or two labeled **egress-gateway nodes** run a **host OpenVPN
|
||||
systemd service** (PIA over OpenVPN-UDP — provider/protocol per ADR-0013, same `.ovpn`). It is **Ansible-
|
||||
managed host infrastructure** (like k3s itself), not a Flux workload. It is configured **split-tunnel**:
|
||||
it creates `tun0` but does **not** set the node default route (`route-noexec` + policy routing), so the
|
||||
node's own traffic (control plane, image pulls, other pods) stays on the WAN. Only traffic Cilium points
|
||||
at `tun0` uses the VPN.
|
||||
- **Steering — Cilium Egress Gateway.** A `CiliumEgressGatewayPolicy` selects pods labeled
|
||||
`homelab.keithjackson.dev/egress-class=vpn` and egresses them via the gateway node's `tun0` (SNAT to the
|
||||
tunnel address). Native Cilium eBPF — **no client-pod changes, no sidecar, no overlay**. (`egressGateway`,
|
||||
`bpf.masquerade`, `kubeProxyReplacement` are already enabled per ADR-0002.)
|
||||
- **Kill-switch (fail-closed, R-NET-06) — host nftables.** An **nftables rule on the gateway node** DROPS the
|
||||
steered (`egress-class=vpn`) traffic whenever `tun0` is absent/down — a host backstop **independent of**
|
||||
Cilium egress-gateway fail-open edge cases. This is a *stronger* guarantee than either pod-based option.
|
||||
- **Resilience.** Two egress-gateway nodes + **Cilium Egress Gateway HA** (gateway failover) provide the
|
||||
active/standby the earlier ADRs asked for.
|
||||
|
||||
The user-facing contract is unchanged: services are labeled `egress-class=vpn` (Kyverno-enforced), have **no
|
||||
independent egress path**, and the rules are **enforced outside the service** — now via a Cilium policy + host
|
||||
nft rather than an in-cluster gateway pod.
|
||||
|
||||
## Consequences
|
||||
- Steering is **Cilium-native and declarative** (a `CiliumEgressGatewayPolicy` in Git) and touches no app pods
|
||||
— flipping a service's `egress-class` label re-routes it with no workload rearchitecting.
|
||||
- **Two-layer ownership:** the tunnel is host infra (**Ansible** role on the gateway nodes); the steering
|
||||
policy is **Flux/GitOps**. Consistent with how k3s is provisioned. This is the intended split, not a leak.
|
||||
- **Secrets move host-side:** PIA credentials + `.ovpn` live on the gateway node(s) via **Ansible vault**, not
|
||||
a cluster SOPS Secret — see docs/07. (SOPS still governs in-cluster secrets.)
|
||||
- **Gluetun is dropped** as the gateway. Its validated custom-mode config + the working PIA `.ovpn` transfer
|
||||
directly to the host OpenVPN service, so the connectivity work is retained; only the packaging changes.
|
||||
- **Port forwarding** (PIA NAT-PMP for qBittorrent) must be re-solved host-side (a small pf/natpmp helper that
|
||||
publishes the assigned port to the app) — Phase 6, as before.
|
||||
- **SPOF** on a single gateway node unless run HA (two nodes + Cilium failover).
|
||||
- Validated end-to-end by the **`egress-class`** (R-NET-05/07) and **`vpn-killswitch`** (R-NET-06) conformance
|
||||
tests: vpn-class egress shows the PIA IP, internet-class shows the WAN IP, a git label flip re-routes, and
|
||||
forcing `tun0` down drops vpn-class egress (no leak) while internet-class is unaffected.
|
||||
|
||||
## Alternatives considered
|
||||
- **In-cluster Gluetun pod + Cilium Egress Gateway → its `tun0`** (ADR-0013's deferred option 1) — NON-VIABLE:
|
||||
`hostNetwork` Gluetun hijacks the node default route.
|
||||
- **pod-gateway pattern** (VXLAN overlay + admission webhook + gateway NAT) — the accepted *in-cluster*
|
||||
alternative; works but heavier, mutates client pods, bypasses Cilium's datapath. Rejected for a Cilium
|
||||
cluster in favor of the native node-level egress gateway.
|
||||
- **Per-pod Gluetun sidecar** (ADR-0008 Pattern A) — rejected earlier: privilege colocation with the workload
|
||||
and no declarative east-west policy.
|
||||
- **Keeping the working in-cluster Gluetun pod for its OWN egress only** — retained as the fallback if the
|
||||
node-level tunnel proves troublesome, but it cannot serve *other* pods, which is the whole `egress: vpn`
|
||||
requirement.
|
||||
@@ -208,10 +208,9 @@ implementation.
|
||||
cluster**; the specific backing is **TBD later** and does not block the core platform. Plex's
|
||||
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 **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).
|
||||
- **OQ-3 (VPN)** — **RESOLVED**: **PIA over OpenVPN-UDP** (non-TCP; ADR-0013). Egress is a **node-level
|
||||
host tunnel steered by Cilium Egress Gateway** with a host nftables kill-switch (ADR-0015) — `vpn`-class
|
||||
pods routed through it, rules enforced outside the service. See [`03`](03-networking-isolation-and-egress.md).
|
||||
|
||||
- **OQ-4 (TLS/DNS challenge access)** — **RESOLVED**: a **scoped Cloudflare API token**
|
||||
(`Zone·DNS·Edit`, `keithjackson.dev`), SOPS-encrypted, drives cert-manager DNS-01 and ExternalDNS.
|
||||
|
||||
@@ -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** (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) |
|
||||
| VPN egress | **Node-level PIA/OpenVPN tunnel** steered by **Cilium Egress Gateway** (host nftables kill-switch) | R-NET-05/06/07 | [ADR-0015](../decisions/adr-0015-vpn-egress-steering-node-tunnel-cilium.md) (provider [0013](../decisions/adr-0013-vpn-provider-pia-gluetun-wireguard.md)) | [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) |
|
||||
@@ -46,7 +46,7 @@ choice here without a superseding ADR.**
|
||||
│ Storage: Longhorn (replicated block) + bulk media store (see ADR-0003) │
|
||||
├──────────────────────────────────────────────────────────────────────────┤
|
||||
│ Network: Cilium (default-deny NetworkPolicy · Egress Gateway · Hubble) │
|
||||
│ VPN egress (Gluetun sidecars / VPN gateway) · Multi-WAN routing │
|
||||
│ VPN egress (node tunnel + Cilium egress GW) · Multi-WAN routing │
|
||||
├──────────────────────────────────────────────────────────────────────────┤
|
||||
│ Kubernetes: k3s (3 nodes, HA control plane, embedded etcd) │
|
||||
├──────────────────────────────────────────────────────────────────────────┤
|
||||
|
||||
@@ -86,36 +86,39 @@ as multi-ISP routing but as **two egress paths selected by routing rules**:
|
||||
ports) to the ingress VIP** for `public` services — a documented manual step ([09](09-dns-and-ingress.md)).
|
||||
- The **service-facing contract is unchanged**: a service still just declares `egress: internet|vpn`.
|
||||
|
||||
## VPN egress model: single shared PIA/Gluetun gateway (R-NET-05/06 — ADR-0013, supersedes ADR-0012/0008)
|
||||
## VPN egress model: node-level tunnel + Cilium Egress Gateway (R-NET-05/06 — ADR-0015; provider ADR-0013)
|
||||
|
||||
VPN-class services: **sonarr, radarr, jackett, qbittorrent**. Per OQ-3 the design is a **single,
|
||||
shared VPN connection** — not a per-service sidecar — so that **network rules are enforced outside
|
||||
the service**:
|
||||
the service**. The steering mechanism was settled by **ADR-0015** after the original in-cluster-Gluetun
|
||||
approach proved un-steerable (a Gluetun *pod*'s `tun0` is invisible to the node datapath, and
|
||||
`hostNetwork` Gluetun hijacks the node's own default route). The model:
|
||||
|
||||
- **One in-cluster VPN egress gateway** (an HA-capable Deployment) runs **Gluetun** configured for
|
||||
**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
|
||||
**no independent egress path**. The kill-switch and allow rules are enforced at the **gateway +
|
||||
namespace/policy level**, i.e. *outside* each service — the service is just labeled `egress: vpn`
|
||||
and cannot leak on its own.
|
||||
- **Kill-switch (fail-closed) is mandatory and enforced centrally:** if the tunnel is down, `vpn`
|
||||
pods have **no route to the plain internet**; only the tunnel path exists. `internet`-class
|
||||
services are unaffected.
|
||||
- **Port forwarding for qBittorrent:** Gluetun **native PF** (`VPN_PORT_FORWARDING=on`) on a
|
||||
**PF-enabled PIA region**; a **port-sync helper** (e.g. `qbittorrent-natmap`) writes the assigned
|
||||
port into qBittorrent so it tracks reconnects — kept hands-off. Alert if PF acquisition fails.
|
||||
- **Trade-off (documented):** one shared tunnel = one shared egress IP and a single failure domain
|
||||
for `vpn` services → run the gateway **active/standby** for resilience; alert on tunnel state
|
||||
([10](10-observability-and-alerting.md)).
|
||||
- **Prerequisite (simple):** just the **PIA username/password**, stored SOPS-encrypted
|
||||
([07](07-security-and-secrets.md)) — no manual `.ovpn` extraction. Validate end-to-end (tunnel +
|
||||
kill-switch + PF) in dev/test.
|
||||
- **Tunnel on the node, not in a pod.** One or two labeled **egress-gateway nodes** run a **host OpenVPN
|
||||
systemd service** — **PIA over OpenVPN-UDP** (provider/protocol per ADR-0013; "non-TCP or better"),
|
||||
**Ansible-managed host infra** (like k3s itself), NOT a Flux workload. It is **split-tunnel**: it
|
||||
creates `tun0` but does **not** set the node's default route (`route-noexec` + policy routing), so the
|
||||
node's own traffic stays on the WAN — only what Cilium points at `tun0` uses the VPN. The tunnel is
|
||||
on the cluster nodes, not the edge router (dumb SOHO edge, per OQ-1).
|
||||
- **`vpn`-class pods are steered by Cilium Egress Gateway.** A `CiliumEgressGatewayPolicy` selects pods
|
||||
labeled `egress-class=vpn` and egresses them via the gateway node's `tun0` (native eBPF SNAT to the
|
||||
tunnel IP). **No client-pod changes, no sidecar, no overlay.** They have **no independent egress path**;
|
||||
the service is just labeled `egress: vpn` and cannot leak on its own.
|
||||
- **Kill-switch (fail-closed) — host nftables.** An **nftables rule on the gateway node** DROPS steered
|
||||
(`egress-class=vpn`) traffic whenever `tun0` is down — a host backstop independent of any Cilium
|
||||
egress-gateway fail-open edge case. If the tunnel is down, `vpn` pods have **no route to the plain
|
||||
internet**; `internet`-class services are unaffected.
|
||||
- **Port forwarding for qBittorrent:** PIA NAT-PMP, re-solved **host-side** (a small pf/natpmp helper on
|
||||
the gateway node that publishes the assigned forwarded port into qBittorrent) — kept hands-off. Alert
|
||||
if PF acquisition fails.
|
||||
- **Resilience:** two egress-gateway nodes + **Cilium Egress Gateway HA** (gateway failover) give the
|
||||
active/standby resilience; alert on tunnel state ([10](10-observability-and-alerting.md)).
|
||||
- **Prerequisite:** the **PIA username/password + `.ovpn`** on the gateway node(s) via **Ansible vault**
|
||||
([07](07-security-and-secrets.md)) — the tunnel is host infra, so its creds are host-side (not a cluster
|
||||
SOPS Secret). Validate end-to-end (tunnel + steering + kill-switch + PF) in dev/test.
|
||||
|
||||
**Kill-switch is an acceptance gate:** with the tunnel forcibly down, every `vpn` service must fail
|
||||
closed (no packets to the plain internet). Tested, not assumed.
|
||||
closed (no packets to the plain internet), while `internet`-class is unaffected. Tested, not assumed.
|
||||
|
||||
## Ingress (north-south) — summary
|
||||
|
||||
@@ -139,8 +142,8 @@ 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 **OpenVPN-UDP**, native port
|
||||
forwarding, rules enforced outside the service (ADR-0013, supersedes ADR-0012's ExpressVPN choice).
|
||||
- **OQ-3** — **PIA over OpenVPN-UDP** (ADR-0013); egress is a **node-level host tunnel** steered by **Cilium
|
||||
Egress Gateway** with a host nftables kill-switch (ADR-0015), rules enforced outside the service.
|
||||
|
||||
## Open questions
|
||||
- Exact steering mechanism for `vpn` pods → gateway (Cilium Egress Gateway vs routing/SNAT) —
|
||||
|
||||
@@ -47,7 +47,8 @@ Defense in depth with declarative, auditable controls:
|
||||
[AGENTS.md §5](../AGENTS.md)).
|
||||
|
||||
### Secret categories to manage
|
||||
- VPN credentials (PIA username/password; OpenVPN-UDP) for the egress model (ADR-0013).
|
||||
- VPN credentials (PIA username/password + `.ovpn`; OpenVPN-UDP) — held **host-side on the egress-gateway
|
||||
node(s) via Ansible vault** (the tunnel is host infra, not a cluster pod; ADR-0015), NOT a cluster SOPS Secret.
|
||||
- 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.
|
||||
|
||||
Reference in new issue
Block a user