WireGuard gateways¶
Pre-release, v0.3.0 — not yet e2e-validated
The wireguard-protocol path has been proven against the reconciler/
render unit and envtest suites and the kernel prerequisite below has
been live-verified on vpn01's worker nodes — but the full apply-loop
has not yet run a live end-to-end tunnel validation pass. See the
index page and Known
Limitations.
VPNGateway{spec.protocol: wireguard} runs a WireGuard gateway pod instead
of a strongSwan one — same unified VPNGateway/VPNPeer
model, same exposure/authorization semantics, different
dataplane. This page covers what's specific to the wireguard path: the
kernel prerequisite, security-group differences, the absence of a metrics
exporter, road-warrior addressing, and key handling.
Kernel prerequisite: the wireguard kernel module¶
WireGuard needs kernel support (CONFIG_WIREGUARD) on the node the
gateway pod's NET_ADMIN/NET_RAW container runs on — it's not something
a container image alone can provide. Verified present on Garden Linux
6.12 (vpn01's worker nodes, kernel 6.12.87): wireguard.ko ships as
CONFIG_WIREGUARD=m with the rtnl-link-wireguard alias, which means it
auto-loads the moment something runs ip link add type wireguard —
the wireguard-apply container's own interface-creation step triggers the
load; no shoot/node-pool change or manual modprobe is required on
b'nerd's own estate. If you're running gateway nodes outside that
verified environment, confirm the module is available (modinfo
wireguard on the node, or check for CONFIG_WIREGUARD in the running
kernel's config) before onboarding a wireguard-protocol gateway there —
this operator cannot detect or work around a missing module; the
interface creation simply fails.
Security-group semantics differ by peer shape¶
vpnSecurityGroupRules (internal/controller/vpngateway_controller.go)
opens the gateway's spec.wireguard.listenPort (UDP) differently
depending on what kind of peer is asking for it:
- A site peer (one with
wireguard.subnetsset, i.e. a fixed remote network behind a static endpoint) gets its ingress rule scoped to its own endpoint host's/32— same tight scoping as the ipsec path's per-peer IKE rules, never0.0.0.0/0. - A road-warrior peer (one with
wireguard.addressset instead ofsubnets— no fixed subnet, no fixed source) gets the listen port opened to0.0.0.0/0, because a roaming client has no stable source address to scope the rule to. This widening applies to the whole gateway's listen port the moment even one road-warrior peer exists — document this explicitly to anyone reviewing the gateway's security posture: one road-warrior peer on an otherwise all-site-peer gateway widens ingress for everyone, not just that one peer, because the SG rule is per-port, not per-peer-and-port. - A site peer whose
endpointis a hostname rather than a literal IPv4 address also falls through to the open0.0.0.0/0rule — a hostname can't be pinned to a/32without a DNS lookup the SG-rule builder doesn't perform. This is the same low-risk posture as the road-warrior open rule (WireGuard's real gate is public-key crypto, not source scoping), and it's surfaced on the gateway'sSecurityGroupScopedcondition:False/HostnameEndpointOpensPort, naming the affected peer(s), when any site peer's endpoint opened the port this way;True/AllSourcesPinnedwhen every peer's handshake source is either a pinned/32or an intentional road-warrior open. Pin the peer to a literal IP endpoint to get the port scoped again.
No Prometheus exporter for WireGuard gateways¶
The ipsec-protocol gateway pod runs an ipsec-exporter sidecar (VICI →
/metrics). WireGuard gateways have no equivalent exporter in
v0.3.0 — WireGuard has no VICI-style RPC socket to scrape, and no
exporter container is built into the wireguard container set
(wireguard-apply, init-nat, status-agent; no fourth container). If
you're used to alerting on the ipsec gateway's Prometheus metrics, that
path does not exist yet for a wireguard gateway.
Monitoring a WireGuard gateway in v0.3.0 is via status.peers[] on
the VPNGateway instead — the status-agent container (--protocol
wireguard) reads wg show state and patches each peer's
lastHandshake/bytesIn/bytesOut (and state) onto the gateway's
status subresource, the same per-peer status shape a VPNPeer itself also
carries:
A Prometheus exporter for WireGuard gateways is a known gap, not a design decision — see Known Limitations.
Road-warrior addressing¶
Road-warrior peers (those with wireguard.address set, subnets empty)
get their tunnel /32 carved from the top /26 of the gateway's
tenant /24 serviceCIDR — e.g. for 100.64.7.0/24, the road-warrior
range is 100.64.7.192/26. Inside that range, the gateway's own wg0
interface takes the network address's first usable host (.193), and
road-warrior peers are assigned from .194 upward. This default is
configurable per gateway via spec.wireguard.roadWarriorCIDR — when
pinned, it must be a canonical (no host bits set) IPv4 CIDR that sits
inside the gateway's serviceCIDR (Validate() rejects an override
outside that range).
A peer's wireguard.address field accepts either a literal IPv4 address
inside the road-warrior range, or "auto" to let the allocator assign the
next free one.
Key handling¶
- The gateway's WireGuard private key lives in a Kubernetes
Secret— either one you reference viaspec.wireguard.privateKeySecretRef, or (when unset) one the operator generates itself, named<gateway-name> -wg-key. The key is mounted read-only into the pod and assembled into/run/wg/wg0.confby thewireguard-applycontainer's apply script — that/run/wgmount is memory-backed (emptyDir{medium: Memory}), never node disk, so the assembled config (private key included) never touches the node's filesystem. The key never appears in container argv or logs — the apply script assembles the config via file redirection, not by interpolating the key into a shell command line. - The gateway's public key is derived from that private key and
surfaced on
status.wireguardPublicKey— safe to read and hand to a peer out-of-band, since a WireGuard public key carries no secrecy requirement by design. - Peer public keys (
VPNPeer.spec.wireguard.publicKey) are likewise public by nature and live directly in the CR — noSecretindirection needed or provided for them. - A per-peer pre-shared key (
wireguard.presharedKeySecretRef) is optional, additive defense-in-depth on top of the Curve25519 key exchange (standard WireGuard PSK semantics) — when set, it's mounted the same way a peer's PSK is on the ipsec side, oneSecretvolume per peer.