feat(wireguard): self-hosted tunnel to OpenBao, no second LoadBalancer - #22
Merged
Conversation
4 tasks
nbrieussel
marked this pull request as ready for review
August 10, 2026 12:47
…plit-DNS Deploys the WireGuard server as a pod with: - wireguard container (NET_ADMIN) holding the actual tunnel interface - render-wg-conf initContainer building [Peer] blocks at startup from a peers Secret (sourced from OpenBao, not hardcoded — see the init chart) - proxy-gateway sidecar: resolves Envoy Gateway's real Service dynamically via the Kubernetes API (label selector on the owning Gateway) and socats 443/80 to it — one generic proxy for everything behind the shared Gateway (Grafana, ArgoCD, OpenBao's public route, etc.), not a dedicated per-app proxy. Superseded an earlier per-target sidecar design once it became clear a single generic proxy covers the same trust boundary with less duplication. - dns sidecar (dnsmasq): split-DNS resolving scalepack.fr to the tunnel's own address for connected peers, so the same hostnames work whether or not the tunnel is up. Exposed via a NodePort (UDP/30820) with externalTrafficPolicy: Local and a dedicated NetworkPolicy, not the shared Gateway's LoadBalancer — Scaleway's cloud-controller-manager silently drops non-TCP LB ports (confirmed via provider source), and WireGuard needs UDP. external-dns picks up the NodePort via its native nodesExternalTrafficPolicyTypeLocal handling, so wg.scalepack.fr self-heals across node/cluster churn without scripting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv
Two ExternalSecrets (split from an earlier single-file version): one for the WireGuard server's private key, one for the peer allowlist (publicKey + allowedIPs per peer). Both read from OpenBao (apps/wireguard/server-key, apps/wireguard/peers), which the infrastructure repo's 05-secrets/openbao/managed populates straight from 04-vpn/wireguard's Terraform state — no hardcoded peer public keys in this repo, no manual copy-paste when a peer's added or rotated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv
…creds Adds WG_CI_PRIVATE_KEY to the infrastructure-scaleway target's secrets list, riding the existing SCW_ACCESS_KEY/SCW_SECRET_KEY pipeline instead of a separate mechanism. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv
nbrieussel
force-pushed
the
feature/openbao-wireguard-tunnel
branch
from
August 11, 2026 09:05
87141f2 to
3967bdf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Companion PR to IntegratedDynamic/infrastructure#53. Adds a self-hosted WireGuard tunnel so Terraform's
vaultprovider — and, as it turned out, any OIDC-gated web UI behind the shared Gateway — can reach the cluster without the public gateway route. Human OIDC/UI login (services/platform/openbao/config) is untouched, still public.services/platform/wireguardapp:initpulls the tunnel server's private key from OpenBao (mirrors every other app'sinitchart);configruns the server.NodePort, not the shared Gateway's LoadBalancer — confirmed live that Scaleway's cloud-controller-manager silently drops non-TCP Service ports when building the LB, so aUDPRoutethere was never reachable regardless of Envoy Gateway's own config. Hardened since a raw node IP is a genuinely new entry point:externalTrafficPolicy: Local, an explicit least-privilegeNetworkPolicy, and a dedicated node security group (infra repo companion PR — Scaleway's auto-managed default group has zero inbound rules and blocks everything).wg.scalepack.fr, kept pointed at whichever node currently hosts the pod by external-dns's nativeNodePort+externalTrafficPolicy: Localsupport — no custom script, same mechanism every other*.scalepack.frhostname already uses. Survives this cluster's daily destroy/rebuild automatically.proxy-gatewaysidecar: raw TCP passthrough (SNI/Host untouched) straight into Envoy Gateway's own real Service, discovered at startup via itsowning-gateway-namelabel (newrbac.yaml) rather than hardcoded, since that Service name is auto-generated per Gateway object. Every app behind the Gateway becomes reachable over the tunnel with zero per-app config — this is also what makes OIDC logins work: from every app's point of view, tunnel traffic is indistinguishable from the public path.dnssidecar: split-DNS,*.scalepack.fr→ the tunnel's own address, everything else forwarded upstream. Pushed to peers via theirwg-quickconfig'sDNSsetting (infra repo) — scoped to the tunnel interface's own lifetime, nothing to remember to revert when it's down.Design notes / dead ends ruled out live
net.ipv4.ip_forward+ iptables MASQUERADE) to reach OpenBao's ClusterIP — Kapsule's kubelet won't allowlist that sysctl, and Scaleway'skubelet_argsAPI refuses to widen it for this cluster's k8s version. Replaced with the userspace proxy approach above.proxyTargetsentry per app (tried for both OpenBao and Grafana) — works fine for plain API access (nothing browser-based cares about origins), but silently can't complete an OIDC login: Grafana (and similarly any OIDC client) generates itsredirect_urifrom one hardcodedroot_url, so a login started via a tunnel-specific address/port can never complete there (confirmed live: Grafana's "Missing saved oauth state"). The genericproxy-gateway+dnscombination replaces both dedicated entries with one mechanism that covers every current and future app behind the Gateway.Test plan
helm lint/helm templateclean on every touched chartcurlto OpenBao through the tunnel;terraform planon the infra repo's05-secrets/openbao/managedthrough the tunnel; Grafana OIDC login completing through the tunnel (proves the split-DNS + Gateway-passthrough design generalizes, not just an OpenBao special case); OpenBao's Terraform access confirmed working through the same genericproxy-gatewaypath after removing its dedicated proxy