Skip to content

feat(wireguard): self-hosted tunnel to OpenBao, no second LoadBalancer - #22

Merged
nbrieussel merged 3 commits into
mainfrom
feature/openbao-wireguard-tunnel
Aug 11, 2026
Merged

feat(wireguard): self-hosted tunnel to OpenBao, no second LoadBalancer#22
nbrieussel merged 3 commits into
mainfrom
feature/openbao-wireguard-tunnel

Conversation

@nbrieussel

@nbrieussel nbrieussel commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Companion PR to IntegratedDynamic/infrastructure#53. Adds a self-hosted WireGuard tunnel so Terraform's vault provider — 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.

  • New services/platform/wireguard app: init pulls the tunnel server's private key from OpenBao (mirrors every other app's init chart); config runs the server.
  • Exposed via a hardened 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 a UDPRoute there 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-privilege NetworkPolicy, and a dedicated node security group (infra repo companion PR — Scaleway's auto-managed default group has zero inbound rules and blocks everything).
  • Self-healing address: wg.scalepack.fr, kept pointed at whichever node currently hosts the pod by external-dns's native NodePort + externalTrafficPolicy: Local support — no custom script, same mechanism every other *.scalepack.fr hostname already uses. Survives this cluster's daily destroy/rebuild automatically.
  • proxy-gateway sidecar: raw TCP passthrough (SNI/Host untouched) straight into Envoy Gateway's own real Service, discovered at startup via its owning-gateway-name label (new rbac.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.
  • dns sidecar: split-DNS, *.scalepack.fr → the tunnel's own address, everything else forwarded upstream. Pushed to peers via their wg-quick config's DNS setting (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

  • Kernel-level routing (net.ipv4.ip_forward + iptables MASQUERADE) to reach OpenBao's ClusterIP — Kapsule's kubelet won't allowlist that sysctl, and Scaleway's kubelet_args API refuses to widen it for this cluster's k8s version. Replaced with the userspace proxy approach above.
  • A dedicated proxyTargets entry 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 its redirect_uri from one hardcoded root_url, so a login started via a tunnel-specific address/port can never complete there (confirmed live: Grafana's "Missing saved oauth state"). The generic proxy-gateway + dns combination replaces both dedicated entries with one mechanism that covers every current and future app behind the Gateway.

Test plan

  • helm lint/helm template clean on every touched chart
  • Live end-to-end, repeatedly, across a real cluster rebuild: WireGuard handshake and curl to OpenBao through the tunnel; terraform plan on the infra repo's 05-secrets/openbao/managed through 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 generic proxy-gateway path after removing its dedicated proxy
  • Not merging yet — branch used for live iteration/testing, per explicit instruction

Nicolas Brieussel and others added 3 commits August 11, 2026 11:04
…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
nbrieussel force-pushed the feature/openbao-wireguard-tunnel branch from 87141f2 to 3967bdf Compare August 11, 2026 09:05
@nbrieussel
nbrieussel merged commit 0c136df into main Aug 11, 2026
1 check passed
@nbrieussel
nbrieussel deleted the feature/openbao-wireguard-tunnel branch August 11, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant