Skip to content

Tracking: VPN gateway operator #37

Description

@jacaudi

Umbrella issue for turning this operator into a Kubernetes-native VPN gateway: a WireGuard server that terminates client tunnels and forwards their traffic onward through gluetun to a commercial VPN provider, with clients declared explicitly or injected into existing workloads by label.

Full design rationale, architecture and leak analysis: docs/ROADMAP.md.

Phase 0 — Foundations

Blocks everything else. #1 and #2 in particular: until there is one Deployment builder and declarative reconciliation, every new spec field needs a bespoke drift check, and anything implemented in internal/resources has no runtime effect at all.

Phase 1 — Pod extension points

Phase 2 — gluetun

Phase 3 — Client paths and ingress

Phase 4 — Webhook infrastructure

Phase 5 — Implicit clients

Phase 6 — Project


Decisions

Recorded so they don't get relitigated. Rationale in docs/ROADMAP.md.

Single server, not HA. A generic WireGuard client already reconnects on its own after the server pod reschedules, with no client-side logic, given a stable endpoint address and a persisted server key — both of which already hold. WireGuard has no connection to lose. Recovery is ~20–40s for a pod crash and tunable for node failure via tolerationSeconds. HA would have cost multi-peer config generation, a failover sidecar, metric semantics, per-server credential management, and a permanent split between how internal and external clients fail over. The CRD stays forward-compatible so HA remains additive.

Three client paths. ClusterIP for in-cluster pods, LoadBalancer UDP for mobile and stock clients, Gateway plus wstunnel for obfuscated desktop clients. Mobile cannot use the tunnel path — PreUp and friends are wg-quick features and are unsupported on iOS and Android.

LoadBalancer by default for raw UDP, UDPRoute opt-in. Envoy Gateway supports UDPRoute, but it proxies non-transparently and puts every VPN packet through a userspace proxy. Gateway API is used unconditionally for the wstunnel path, where L7 termination is the actual value.

Implicit clients are minted per Pod, not per Deployment. A peer identity shared across replicas misdelivers silently: WireGuard tracks one endpoint per public key, so the server sends pod A's return traffic to whichever pod handshook last.

Opt-in is a label, not an annotation. Webhook objectSelector matches labels only.

Killswitch in two layers. In-pod rules work on any CNI; the CNI policy survives sidecar crash or interface teardown, which is the failure that actually leaks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions