From 0058641c73d92a6ea620abc5ef98c72b5eeabfe3 Mon Sep 17 00:00:00 2001 From: Nicolas Brieussel Date: Tue, 11 Aug 2026 11:03:10 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feat(vpn):=20stand=20up=2004-vpn/wireguard?= =?UTF-8?q?=20=E2=80=94=20self-hosted=20tunnel=20to=20OpenBao?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mints a WireGuard server keypair plus one keypair per human/CI peer, renders full wg-quick configs per peer (server pubkey, allowed IPs, persistent keepalive, split-DNS pointing scalepack.fr at the tunnel's own address). Endpoint is a stable external-dns hostname (wg.scalepack.fr:30820), not a raw node IP, so it survives node/cluster churn. Peer private keys are never bulk-written to disk: `peer_confs` is a sensitive output, and mise's vpn-generate/vpn-up/vpn-down tasks extract and manage exactly one peer's own file at a time — anyone running `terraform apply` on this root doesn't end up with every peer's plaintext key on their machine. Goal: eliminate OpenBao's public gateway route as a dependency for this repo's own `vault` provider, without a third-party control plane (no Tailscale). Human OIDC/UI login via Dex stays on the public route, untouched. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv --- .gitignore | 2 + 04-vpn/wireguard/.terraform.lock.hcl | 25 ++++ 04-vpn/wireguard/README.md | 120 ++++++++++++++++++ .../wireguard/env/04-network-wireguard.tfvars | 8 ++ 04-vpn/wireguard/main.tf | 42 ++++++ 04-vpn/wireguard/outputs.tf | 39 ++++++ 04-vpn/wireguard/variables.tf | 41 ++++++ 04-vpn/wireguard/version.tf | 20 +++ CLAUDE.md | 7 + mise.toml | 44 +++++++ 10 files changed, 348 insertions(+) create mode 100644 04-vpn/wireguard/.terraform.lock.hcl create mode 100644 04-vpn/wireguard/README.md create mode 100644 04-vpn/wireguard/env/04-network-wireguard.tfvars create mode 100644 04-vpn/wireguard/main.tf create mode 100644 04-vpn/wireguard/outputs.tf create mode 100644 04-vpn/wireguard/variables.tf create mode 100644 04-vpn/wireguard/version.tf diff --git a/.gitignore b/.gitignore index c70a600..15ba9a1 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ override.tf.json terraform.rc .specify + +*.conf diff --git a/04-vpn/wireguard/.terraform.lock.hcl b/04-vpn/wireguard/.terraform.lock.hcl new file mode 100644 index 0000000..1bddd7b --- /dev/null +++ b/04-vpn/wireguard/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/ojford/wireguard" { + version = "0.4.1" + constraints = "~> 0.4" + hashes = [ + "h1:c09xyC2LPfUHgVG/nis4l1B51rBZOCJwfQIcn5RJ3/k=", + "h1:cM9oObFO/yFR74wIcyXnSjvp1nB0n9ANJMCWq186eAk=", + "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", + "zh:1cdcd10ba61520d7e21590ea4d96d188cefbce3dcdbf89b277696d6023ec7620", + "zh:26bde3cf4850ba8605c028c84bed6075d2175853a0744303f6051cc3b5c51dd1", + "zh:28c351481689762dcd653229aa5f847473f71568a8222fafbddda0c600377a46", + "zh:3bd7ab222f13286449342d35d6dd3dbe343f575e47d4c2e6f8f64149386070c7", + "zh:3d25381c96df52253e7767aae49f0bead427c79f34ecdfb38026335e03c12de2", + "zh:4017a40b50886d3f1f0e886f8eacd1924c5f03888c90862c55ba98bb376198b2", + "zh:561978966998a3703053ac990796d5a88c7c14afd77e623846941a66b6fc9c92", + "zh:5f8460880a5f9cc9f4cc7c7c3c41156b0dae3c2b56f29cc259b3dc75ad650fe1", + "zh:724592993a0cccd45e0290aba2a6b72f7bb668a4ae3b8d778bb5d379d4105863", + "zh:b4de1157176eaf1fcc624d812ae6134893d6c73f3d1d2325c94cd9e707678d7b", + "zh:c5530bc71beedba579be57ea580f4151ec87223f198147771a924debff2e255e", + "zh:d3c7863e5baaf05e76fa5db3d3f58ff94440408873e370770b6a0080981cec37", + "zh:ec0a7d3a540cfb92e15b9a7acc43446059a9254b5904b48c940564c70e2b8aa1", + ] +} diff --git a/04-vpn/wireguard/README.md b/04-vpn/wireguard/README.md new file mode 100644 index 0000000..cbdbe8f --- /dev/null +++ b/04-vpn/wireguard/README.md @@ -0,0 +1,120 @@ +# 04-vpn/wireguard — WireGuard peer keys + client configs for the OpenBao tunnel + +## Connect + +Requires `wireguard-tools` (`brew install wireguard-tools` on macOS) — the +official WireGuard.app GUI has known DNS resolution issues with its +sandboxed NetworkExtension that `wg-quick` doesn't have; use the CLI. + +```bash +mise run vpn-generate # once, and again any time your config needs refreshing +mise run vpn-up # your own tunnel — add a peer name if you're not "nicolas" +mise run vpn-down # when you're done +``` + +`vpn-generate` writes a ready-to-use `wg-quick` config per peer to +`generated/` (gitignored, `0600`) — `vpn-up`/`vpn-down` just point +`wg-quick` at the right one, since its own "bare name" lookup only finds +configs already living in its own config directory, not ones Terraform +generates here. + +Adding a new peer: add an entry to `var.peers` (both `variables.tf`'s +default and `env/04-network-wireguard.tfvars`), `mise run vpn-generate`, +then do the manual hand-offs below for just that new entry. + +## Manual hand-offs — hardcoded cross-repo coupling to track + +Nothing here talks to the gitops repo or to OpenBao directly. Every row is +a human copying a `terraform output` value somewhere else — same "two +systems kept in sync by convention, not automation" pattern as +`05-secrets/openbao/managed`'s `secrets_sync_github` vs. the gitops repo's +`apps/secrets-sync/values.yaml`. Accepted for now; each row is a spot that +silently breaks if only one side changes. + +| Value | From | To | Why manual | +|---|---|---|---| +| `server_public_key`, `peer_public_keys` (not secret) | this root's outputs | gitops `services/platform/wireguard/config/values-scaleway.yaml` | no cross-repo automation exists anywhere in this setup | +| `server_private_key` (sensitive) | this root's output | `05-secrets/openbao/managed`'s `wireguard_server_private_key` (`local.auto.tfvars`) → `kv/apps/wireguard/server-key` → gitops `wireguard-init`'s ExternalSecret | same | +| `peer_private_keys["ci-github-actions"]` (sensitive) | this root's output | `05-secrets/openbao/managed`'s `wireguard_ci_private_key` → merged into the *existing* `kv/apps/secrets-sync/github/infrastructure-scaleway` object (not its own KV path — nothing reads that) → secrets-sync → GitHub Actions secret | same; direction is OpenBao→GitHub via in-cluster ESO, so CI never needs the tunnel to fetch its own tunnel key | +| every other peer's private key | this root's output | that peer's own machine only (`generated/.conf`) | never touches OpenBao or git, by design | +| NodePort `30820` | gitops `services/platform/wireguard/config/values.yaml` `server.nodePort` | infra `10-cluster/scaleway/main.tf`'s security group rule, **and** this root's `wg_endpoint` port | two independent files, must match exactly | + +## Why this exists + +`05-secrets/openbao/{bootstrap,managed}`'s `vault` provider needs a network +path to OpenBao's API — this root exists so that path can be a self-hosted +tunnel instead of the public gateway route (`openbao.scalepack.fr`), which +stays public only for a separate, legitimate reason: human OIDC/UI login +(gitops repo's `services/platform/openbao/config`), untouched by this +domain. No third-party control plane (Tailscale evaluated, ruled out) — +just `wg`, terminated by a small server workload in the gitops repo +(`services/platform/wireguard`). + +**End-to-end validated live 2026-08-09**: a peer handshakes and reaches +OpenBao's `/v1/sys/health` through the full chain (tunnel → gitops's +`proxy-openbao` sidecar → OpenBao's Service). + +## Why exposure is a NodePort, not the shared Gateway + +Scaleway's cloud-controller-manager silently drops any non-TCP `Service` +port when building its LoadBalancer (`loadbalancers.go`: +`if port.Protocol != v1.ProtocolTCP { skip }`) — a `UDPRoute` on the shared +Gateway's LB was never reachable no matter how correctly Envoy Gateway +itself was configured; confirmed live, zero datagrams ever arrived despite +every Gateway API resource reporting `Accepted`/`Programmed`. + +Exposed instead as a `NodePort` directly on a Kapsule node's public IP — a +genuinely new entry point, hardened accordingly: `externalTrafficPolicy: +Local` + an explicit least-privilege `NetworkPolicy` (gitops repo's +`services/platform/wireguard/config`), plus a dedicated +`scaleway_instance_security_group` replacing Scaleway's auto-managed +default one, which ships with zero inbound rules and would otherwise block +this (and everything else) at the instance level regardless of any +Kubernetes-side config (`10-cluster/scaleway/main.tf`). + +A raw node IP isn't stable — it changes on every reschedule, including +this cluster's own daily destroy/rebuild. Fixed via `external-dns`, not a +custom script: its `service` source has first-class support for exactly +`NodePort` + `externalTrafficPolicy: Local` (resolves the ExternalIP of +whichever node currently has a live pod), so `wg.scalepack.fr` — the +`wg_endpoint` default — stays correct on its own, the same mechanism every +other `*.scalepack.fr` hostname in this cluster already relies on. See +that chart's `templates/service.yaml` annotation. + +## Why the tunnel doesn't route to OpenBao's ClusterIP directly + +An earlier design routed/NAT'd traffic to OpenBao's `ClusterIP` at the +kernel level (`net.ipv4.ip_forward` + `iptables` MASQUERADE) — a dead end: +Kapsule's kubelet doesn't allowlist that sysctl, and Scaleway's +`kubelet_args` API refuses to widen the allowlist for this cluster's k8s +version at all. Even where available, that's a cluster-wide relaxation for +one workload's benefit — worth avoiding regardless. + +The gitops chart instead terminates the tunnel and proxies to OpenBao at +the application layer (a `socat` sidecar resolving +`openbao.openbao.svc.cluster.local` via ordinary in-cluster DNS) — no +kernel routing, no sysctls, no dependency on OpenBao's `ClusterIP` being +stable across a cluster rebuild. + +## Why its own domain, and why it's temporary + +Identities normally live in `01-iam/`, secrets in `05-secrets/`. Neither +fits a WireGuard peer well: it isn't a cloud-provider IAM identity, and a +keypair's public half isn't a secret at all. Placeholder domain of its +own, explicitly temporary — expect this to fold into `01-iam/workload/` (or +be replaced entirely) once the shape of "what kind of thing is a WireGuard +peer" is clearer. + +## What it creates + +- `wireguard_asymmetric_key.server` — the tunnel server's own keypair. +- `wireguard_asymmetric_key.peer` (`for_each = var.peers`) — one keypair + per peer. +- `data.wireguard_config_document.peer` — renders each peer's full + `wg-quick` config (their own key, the server's public key, `AllowedIPs`, + `wg_endpoint`). +- `local_sensitive_file.peer_conf` — writes that config to `generated/`. + +Providers: [`OJFord/wireguard`](https://registry.terraform.io/providers/OJFord/wireguard) +(local key generation + config rendering, no credentials, no API calls) and +`hashicorp/local` (writes the file). diff --git a/04-vpn/wireguard/env/04-network-wireguard.tfvars b/04-vpn/wireguard/env/04-network-wireguard.tfvars new file mode 100644 index 0000000..52248da --- /dev/null +++ b/04-vpn/wireguard/env/04-network-wireguard.tfvars @@ -0,0 +1,8 @@ +# Matches variables.tf's defaults — spelled out explicitly rather than left +# implicit, same convention as every other root's env/ file. +peers = { + "nicolas" = { address = "10.100.0.2/32" } + "ci-github-actions" = { address = "10.100.0.3/32" } +} +server_address = "10.100.0.1/24" +wg_endpoint = "wg.scalepack.fr:30820" diff --git a/04-vpn/wireguard/main.tf b/04-vpn/wireguard/main.tf new file mode 100644 index 0000000..c340587 --- /dev/null +++ b/04-vpn/wireguard/main.tf @@ -0,0 +1,42 @@ +# The WireGuard server's own keypair — one per cluster, not per peer. Private +# key hands off to 05-secrets/openbao/managed (kv/apps/wireguard/server-key), +# which ESO then materializes into the gitops repo's +# services/platform/wireguard Deployment. Public key is non-secret — copy it +# into that same chart's values (it's the identity peers dial, not a secret). +resource "wireguard_asymmetric_key" "server" {} + +# One keypair per peer in var.peers. Public keys are non-secret — routed +# through 05-secrets/openbao/managed into the gitops repo's peer allowlist. +# Private keys: ci-github-actions hands off to 05-secrets/openbao/managed +# the same way the server key does (see README.md); every other entry is a +# human peer's own credential — see outputs.tf's peer_confs for why that +# never gets written to disk by this root itself. +resource "wireguard_asymmetric_key" "peer" { + for_each = var.peers +} + +# Renders a ready-to-import wg-quick config per peer — the whole point of +# minting keys in Terraform instead of by hand with `wg genkey`. +# ci-github-actions gets one too for consistency, but nothing consumes its +# file: CI only ever needs the raw private key (see +# 05-secrets/openbao/managed's wireguard_ci_private_key), never a config file. +data "wireguard_config_document" "peer" { + for_each = var.peers + + private_key = wireguard_asymmetric_key.peer[each.key].private_key + addresses = [each.value.address] + # Split DNS: the tunnel server's own `dns` sidecar (gitops repo's + # services/platform/wireguard/config) answers *.scalepack.fr with its own + # tunnel address while forwarding everything else upstream — so every + # OIDC-gated app (Grafana, ArgoCD, ...) works over the tunnel using its + # real hostname, scoped to exactly this interface's lifetime (nothing to + # revert when the tunnel goes down, unlike an /etc/hosts edit). + dns = [split("/", var.server_address)[0]] + + peer { + public_key = wireguard_asymmetric_key.server.public_key + allowed_ips = ["${split("/", var.server_address)[0]}/32"] + endpoint = var.wg_endpoint + persistent_keepalive = 25 + } +} diff --git a/04-vpn/wireguard/outputs.tf b/04-vpn/wireguard/outputs.tf new file mode 100644 index 0000000..c750047 --- /dev/null +++ b/04-vpn/wireguard/outputs.tf @@ -0,0 +1,39 @@ +output "server_public_key" { + description = "WireGuard server public key — non-secret, copy into gitops services/platform/wireguard's values." + value = wireguard_asymmetric_key.server.public_key +} + +output "server_private_key" { + description = "WireGuard server private key — sensitive. Read directly by 05-secrets/openbao/managed via terraform_remote_state (kv/apps/wireguard/server-key). Never committed here." + value = wireguard_asymmetric_key.server.private_key + sensitive = true +} + +output "peer_public_keys" { + description = "peer name -> public key. Non-secret. Read (with peer_addresses) by 05-secrets/openbao/managed via terraform_remote_state into kv/apps/wireguard/peers, which the gitops repo's services/platform/wireguard/init syncs down — nothing hardcodes these." + value = { for name, key in wireguard_asymmetric_key.peer : name => key.public_key } +} + +output "peer_addresses" { + description = "peer name -> overlay tunnel address (var.peers[name].address, echoed back as its own output for the same reason as peer_public_keys above)." + value = { for name, cfg in var.peers : name => cfg.address } +} + +output "peer_private_keys" { + description = "peer name -> private key. Sensitive. ci-github-actions is read directly by 05-secrets/openbao/managed via terraform_remote_state; every other entry stays local — see peer_confs — never committed, never round-tripped through OpenBao." + value = { for name, key in wireguard_asymmetric_key.peer : name => key.private_key } + sensitive = true +} + +# peer name -> full rendered wg-quick config. Deliberately NOT written to +# disk by this root (no local_sensitive_file/for_each here) — that would +# mean anyone running `terraform apply` on this root ends up with every +# peer's private key in plaintext on their own machine, not just their +# own. Extracting and writing just one peer's file is `mise run +# vpn-generate`'s job (mise.toml), scoped to a single name via +# `terraform output -json peer_confs | jq -r '.[""]'`. +output "peer_confs" { + description = "peer name -> full rendered wg-quick config (sensitive). Never written to disk in bulk — see mise.toml's vpn-generate task for how one peer's own file gets extracted." + value = { for name, doc in data.wireguard_config_document.peer : name => doc.conf } + sensitive = true +} diff --git a/04-vpn/wireguard/variables.tf b/04-vpn/wireguard/variables.tf new file mode 100644 index 0000000..adc406c --- /dev/null +++ b/04-vpn/wireguard/variables.tf @@ -0,0 +1,41 @@ +# One keypair per identity that needs to reach OpenBao's ClusterIP over the +# tunnel instead of its public gateway route — a human dev machine or the CI +# service identity. `address` is that peer's own /32 on the tunnel's overlay +# subnet (var.server_address) — this map is the single source of truth for +# peer overlay addressing; the gitops repo's services/platform/wireguard +# chart copies it in as a hand-synced value (see README.md's hand-off table). +variable "peers" { + description = "Peer name -> its own overlay tunnel address. One keypair minted per entry." + type = map(object({ + address = string + })) + default = { + "nicolas" = { address = "10.100.0.2/32" } + "ci-github-actions" = { address = "10.100.0.3/32" } + } +} + +# The tunnel server's own overlay address (CIDR) — must match the gitops +# repo's services/platform/wireguard/config values.yaml `server.address`. +variable "server_address" { + description = "WireGuard server's own overlay address (CIDR, e.g. 10.100.0.1/24)." + type = string + default = "10.100.0.1/24" +} + +# Where a peer actually dials to reach the tunnel server. A hostname, not a +# raw node IP: Scaleway LBs don't do UDP passthrough (see README.md), so +# this has to be a Kapsule node's public IP directly — which isn't stable +# on its own (changes whenever the server pod reschedules, including this +# cluster's own daily destroy/rebuild). external-dns keeps wg.scalepack.fr +# pointed at whichever node currently hosts the pod (gitops repo's +# services/platform/wireguard/config/templates/service.yaml's +# external-dns.alpha.kubernetes.io/hostname annotation — same mechanism +# every other *.scalepack.fr hostname already uses, no custom script). +# Port must match that chart's values.yaml `server.nodePort` exactly (same +# coupling noted in this infra repo's 10-cluster/scaleway/main.tf). +variable "wg_endpoint" { + description = "Where peers dial the tunnel server: :." + type = string + default = "wg.scalepack.fr:30820" +} diff --git a/04-vpn/wireguard/version.tf b/04-vpn/wireguard/version.tf new file mode 100644 index 0000000..bef2c28 --- /dev/null +++ b/04-vpn/wireguard/version.tf @@ -0,0 +1,20 @@ +terraform { + backend "s3" { + bucket = "id-terraform-state20260612164136440800000001" + region = "eu-west-3" + workspace_key_prefix = "network/wireguard" + key = "terraform.tfstate" + encrypt = true + use_lockfile = true + } + + required_providers { + wireguard = { + source = "OJFord/wireguard" + version = "~> 0.4" + } + } +} + +# Pure local keypair generation — no API, no credentials to configure. +provider "wireguard" {} diff --git a/CLAUDE.md b/CLAUDE.md index d7fe52c..4fbeb00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,6 +40,7 @@ terraform -chdir=01-iam/bootstrap/scaleway providers lock -platform=dar terraform -chdir=01-iam/workload/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=02-encryption/aws providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=03-storage/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 +terraform -chdir=04-vpn/wireguard providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=05-secrets/openbao/bootstrap providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=10-cluster/local providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=10-cluster/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 @@ -105,6 +106,12 @@ modules/ scaleway/ # domain: Scaleway tool buckets + their scoped # identities (backup, velero today; home for # future tool buckets) +04-vpn/ + wireguard/ # domain: WireGuard peer keypairs for the + # OpenBao tunnel (05-secrets/openbao's vault + # provider, not the human OIDC/UI login) — + # EXPLICITLY TEMPORARY, see its own README + # for why it isn't 01-iam/ or 05-secrets/ yet 05-secrets/ openbao/ # domain: OpenBao itself (bootstrap/ + managed/, # see that directory) — untouched by the diff --git a/mise.toml b/mise.toml index 82b7ff3..cdf23c4 100644 --- a/mise.toml +++ b/mise.toml @@ -55,6 +55,49 @@ description = "Reset local minikube cluster" run = "minikube delete" +# ── VPN (WireGuard, 04-vpn/wireguard) ──────────────────────────────────────── +# `wg-quick`'s own "bare name" lookup only resolves configs already living in +# its own directory (/opt/homebrew/etc/wireguard on macOS) — not the ones +# written under generated/. These just point it at the right file so the +# whole workflow is `mise run vpn-generate` once, then `vpn-up`/`vpn-down` +# — no path wrangling, no `wg-quick: ...conf.conf' does not exist` +# surprises. Requires wireguard-tools (`brew install wireguard-tools` on +# macOS) — not mise-managed, no mise plugin for it. + +[tasks.vpn-generate] +usage = 'arg "" help="Peer name from 04-vpn/wireguard/variables.tf var.peers" default="nicolas"' +description = "Write your own WireGuard config to disk (never anyone else's)" +dir = "04-vpn/wireguard" +run = """ +terraform init +terraform workspace select -or-create 04-network-wireguard +terraform apply -var-file=env/04-network-wireguard.tfvars +mkdir -p generated +terraform output -json peer_confs | jq -r --arg peer "${usage_peer?}" '.[$peer]' > "generated/${usage_peer?}.conf" +chmod 600 "generated/${usage_peer?}.conf" +echo "wrote generated/${usage_peer?}.conf" +""" + +[tasks.vpn-up] +usage = 'arg "" help="Peer name from 04-vpn/wireguard/variables.tf var.peers" default="nicolas"' +description = "Bring up your WireGuard tunnel to OpenBao" +dir = "04-vpn/wireguard" +run = """ +conf="generated/${usage_peer?}.conf" +if [ ! -f "$conf" ]; then + echo "error: $conf not found — run 'mise run vpn-generate' first" >&2 + exit 1 +fi +sudo wg-quick up "$(pwd)/$conf" +""" + +[tasks.vpn-down] +usage = 'arg "" help="Peer name from 04-vpn/wireguard/variables.tf var.peers" default="nicolas"' +description = "Tear down your WireGuard tunnel" +dir = "04-vpn/wireguard" +run = 'sudo wg-quick down "$(pwd)/generated/${usage_peer?}.conf"' + + # ── Provider locks ─────────────────────────────────────────────────────────── [tasks.lock] @@ -66,6 +109,7 @@ terraform -chdir=01-iam/bootstrap/scaleway providers lock -platform=dar terraform -chdir=01-iam/workload/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=02-encryption/aws providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=03-storage/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 +terraform -chdir=04-vpn/wireguard providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=05-secrets/openbao/bootstrap providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=10-cluster/local providers lock -platform=darwin_arm64 -platform=linux_amd64 terraform -chdir=10-cluster/scaleway providers lock -platform=darwin_arm64 -platform=linux_amd64 From 3aa3195030453eec8458e21af3c07383863af82f Mon Sep 17 00:00:00 2001 From: Nicolas Brieussel Date: Tue, 11 Aug 2026 11:03:21 +0200 Subject: [PATCH 2/3] feat(cluster): dedicated node security group for the WireGuard NodePort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kapsule's default "default security group" allows zero inbound traffic. Adds a scaleway_instance_security_group with a default-drop stance plus one explicit allow rule for UDP/30820 (the WireGuard NodePort), attached to the node pool via security_group_id — an in-place update, not a pool recreate (verified against the provider's Go source: that field isn't ForceNew, despite an adjacent field's ForceNew warning in the docs being easy to misattribute to it). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv --- 10-cluster/scaleway/main.tf | 50 +++++++++++++++++++++++++++----- 10-cluster/scaleway/variables.tf | 4 +-- 10-cluster/scaleway/version.tf | 2 +- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/10-cluster/scaleway/main.tf b/10-cluster/scaleway/main.tf index 5915d4b..0bc3617 100644 --- a/10-cluster/scaleway/main.tf +++ b/10-cluster/scaleway/main.tf @@ -18,10 +18,46 @@ resource "scaleway_k8s_cluster" "this" { tags = ["homelab", "terraform"] } +# Replaces Scaleway's auto-managed "Kapsule default security group" on the +# node pool below — that group ships with ZERO inbound rules and a +# default-drop policy, meaning nothing reaches a node's public IP directly +# at all. Confirmed live 2026-08-09 investigating why the WireGuard +# NodePort (gitops repo's services/platform/wireguard) never received a +# single packet despite correct Kubernetes-level config (Service, +# NetworkPolicy) — this sits below Kubernetes entirely, at the instance +# level, so nothing in-cluster could have fixed it. +# +# Same default-drop inbound posture as Scaleway's own default, plus +# exactly one explicit allow. 80/443 keep working unaffected either way — +# that traffic arrives via the LB over Scaleway's internal path, never +# subject to this instance-level firewall. +# +# COUPLING: the port below must match the gitops repo's +# services/platform/wireguard/config/values.yaml `server.nodePort` exactly +# — same "two systems kept in sync by convention" pattern already used for +# 05-secrets/openbao/managed's secrets_sync_github vs. that repo's +# apps/secrets-sync/values.yaml. Changing one without the other silently +# breaks the tunnel again, the same way this whole investigation started. +resource "scaleway_instance_security_group" "cluster_nodes" { + name = "${var.cluster_name}-nodes" + description = "Explicit inbound allowlist for cluster nodes' public IPs — see main.tf's comment on this resource." + + inbound_default_policy = "drop" + outbound_default_policy = "accept" + enable_default_security = true + + inbound_rule { + action = "accept" + protocol = "UDP" + port = 30820 # gitops: services/platform/wireguard/config/values.yaml server.nodePort + } +} + resource "scaleway_k8s_pool" "default" { - cluster_id = scaleway_k8s_cluster.this.id - name = "default" - node_type = "DEV1-M" + cluster_id = scaleway_k8s_cluster.this.id + name = "default" + node_type = "DEV1-M" + security_group_id = scaleway_instance_security_group.cluster_nodes.id # Put whatever number is required to avoid node pressure signals during startup: https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/ # Node pressure during startup can end-up with unexcepted race conditions. size = var.node_count @@ -100,9 +136,9 @@ resource "kubernetes_secret" "scaleway_s3_credentials" { } data = { - bucket = data.terraform_remote_state.backup_scaleway.outputs.bucket_name - AWS_ACCESS_KEY_ID = data.terraform_remote_state.backup_scaleway.outputs.workload_access_key - AWS_SECRET_ACCESS_KEY = data.terraform_remote_state.backup_scaleway.outputs.workload_secret_key + bucket = data.terraform_remote_state.backup_scaleway.outputs.bucket_name + AWS_ACCESS_KEY_ID = data.terraform_remote_state.backup_scaleway.outputs.workload_access_key + AWS_SECRET_ACCESS_KEY = data.terraform_remote_state.backup_scaleway.outputs.workload_secret_key } } @@ -118,7 +154,7 @@ resource "kubernetes_secret" "openbao_unseal_aws" { } data = { - AWS_ACCESS_KEY_ID = data.terraform_remote_state.openbao_unseal_aws.outputs.openbao_unseal_access_key_id + AWS_ACCESS_KEY_ID = data.terraform_remote_state.openbao_unseal_aws.outputs.openbao_unseal_access_key_id AWS_SECRET_ACCESS_KEY = data.terraform_remote_state.openbao_unseal_aws.outputs.openbao_unseal_secret_access_key } } diff --git a/10-cluster/scaleway/variables.tf b/10-cluster/scaleway/variables.tf index 2e9a558..b075d04 100644 --- a/10-cluster/scaleway/variables.tf +++ b/10-cluster/scaleway/variables.tf @@ -40,8 +40,8 @@ variable "gitops_revision" { } variable "update_kubeconfig" { - type = bool - default = false + type = bool + default = false description = "Set to true when using locally to automatically update you ~/.kube/config. Require `kubectl` and `scw` installed & configured." } diff --git a/10-cluster/scaleway/version.tf b/10-cluster/scaleway/version.tf index e2e23e2..1064466 100644 --- a/10-cluster/scaleway/version.tf +++ b/10-cluster/scaleway/version.tf @@ -40,7 +40,7 @@ terraform { provider "scaleway" {} # provider "infisical" { - + # auth = { # ## Uncomment `universal` and comment `oidc` when running terraform locally. # ## By default, even with `INFISICAL_UNIVERSAL_AUTH_CLIENT_XXXX` environment variable, due to `auth.oidc` being present, infisical provider expect OIDC configuration, and nothing else. From f70d2925e71d0fe82c8044bc0e63605ef32e075d Mon Sep 17 00:00:00 2001 From: Nicolas Brieussel Date: Tue, 11 Aug 2026 11:03:53 +0200 Subject: [PATCH 3/3] feat(openbao): route vault provider through the tunnel, drop key copy-paste MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `vault` provider in 05-secrets/openbao/managed now defaults to openbao.scalepack.fr resolving through the WireGuard tunnel's split-DNS (same hostname either way — falls back to the real public route if the tunnel's down, which is fine, that route still exists for human OIDC/UI login). A commented-out direct port-forward address is kept as a documented fallback for breaking any future tunnel/OpenBao chicken-and-egg deadlock. Both the WireGuard server private key and the CI peer's private key are now read straight from 04-vpn/wireguard's state via terraform_remote_state instead of hand-copied between files, matching this repo's existing convention for cross-root secrets. Also syncs full rendered peer configs into OpenBao (apps/wireguard/confs) for self-service retrieval without needing local Terraform state. data_json_wo_version bumped 1->2 on both new WireGuard KV secrets: write- only diffing can't detect a raft snapshot restoring OpenBao from before these were first written (confirmed live 2026-08-10 after a full cluster rebuild), so the version bump is what re-triggers the write. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017ZMCuQ5t9hdrmUctLk7hEv --- 05-secrets/openbao/bootstrap/version.tf | 17 +-- .../openbao/managed/.terraform.lock.hcl | 25 ++++ 05-secrets/openbao/managed/README.md | 26 +++- 05-secrets/openbao/managed/main.tf | 113 ++++++++++++++++-- 05-secrets/openbao/managed/variables.tf | 5 +- 05-secrets/openbao/managed/version.tf | 23 ++-- 6 files changed, 180 insertions(+), 29 deletions(-) diff --git a/05-secrets/openbao/bootstrap/version.tf b/05-secrets/openbao/bootstrap/version.tf index 5df3b2b..e464b24 100644 --- a/05-secrets/openbao/bootstrap/version.tf +++ b/05-secrets/openbao/bootstrap/version.tf @@ -1,11 +1,11 @@ terraform { backend "s3" { - bucket = "id-terraform-state20260612164136440800000001" - region = "eu-west-3" - workspace_key_prefix = "secrets/bootstrap/openbao" - key = "terraform.tfstate" - encrypt = true - use_lockfile = true + bucket = "id-terraform-state20260612164136440800000001" + region = "eu-west-3" + workspace_key_prefix = "secrets/bootstrap/openbao" + key = "terraform.tfstate" + encrypt = true + use_lockfile = true } required_providers { @@ -21,7 +21,10 @@ terraform { # session doesn't populate the env var this hashicorp/vault provider expects. # Only the token stays in the environment — see README. provider "vault" { + # Same hostname as the public route, resolved through the WireGuard + # tunnel via split-DNS while it's up — see + # 05-secrets/openbao/managed/version.tf's comment for the full rationale. address = "https://openbao.scalepack.fr/" - # address = "http://127.0.0.1:8200/" + # address = "http://127.0.0.1:8200/" # kubectl port-forward, independent of the tunnel # token = var.root_token } diff --git a/05-secrets/openbao/managed/.terraform.lock.hcl b/05-secrets/openbao/managed/.terraform.lock.hcl index c1af532..905a64d 100644 --- a/05-secrets/openbao/managed/.terraform.lock.hcl +++ b/05-secrets/openbao/managed/.terraform.lock.hcl @@ -1,11 +1,35 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. +provider "registry.terraform.io/hashicorp/aws" { + version = "6.58.0" + hashes = [ + "h1:OWl47Bo8Vzlf5srTUCmA6v4kvQGfah/P1joRtIYUUMc=", + "zh:1221253beee5629fb503d79cebc9bc661279cbc4be5d01db9ab4c1b702108250", + "zh:132bd0925bdc4b72446ac750b7ccb1e19b9ba8fbb6df57b2c1423314d2195d4f", + "zh:18cda250b9e82b753808715893c8927f132273c00ffae7a697d65ac1cb577e48", + "zh:204c944f1fb7f440a335bb2083c9691a9d1f677aea9701025dd5816aee41f0ba", + "zh:2dc41df289f2b10a01e650cdd73699955f0ab0645d09cfb114a8cd0f4cc4ede7", + "zh:345633dfa9a234659d52aadd126e6dce658518c3ab5cbf6d871221287ed5ec56", + "zh:4dadcced73e742903158bc9838936d911f3fa4c2c37b5591c1a28f8f2a1902a6", + "zh:5bc60cc2b8c093da98b211d9f6c21c9ecec0f21b944d9ecbe3961fba33086e80", + "zh:6cc8f084938b0033a9c0c910989919dad6b1683e76e0afa1a5c604e39f398a75", + "zh:7db214647f79de9a033b5dfd6cbfaa42d53c4d056b32cb3acc7ad99306dd548a", + "zh:9078589ec881cee7ed9403af262c98ff257fb3e1baae72ff6429a398b1c730af", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:bd5bce6aec4d4922b1127b8575688bd4bc4279670ee28d198ede404709826c7c", + "zh:cd900ecf56d21023873898b06e40234f3f4d350f2343b7d9b980d6c5cb604fae", + "zh:dbe93b276a84421026b956c3c5b4eb8897da6cbb54b93cb89f5d6ebbd30805ca", + "zh:f6b6c7bb2dbf04ee085e5c22f7a65b3ccaebf368ed95584dc0dfee8a22771056", + ] +} + provider "registry.terraform.io/hashicorp/random" { version = "3.9.0" constraints = "~> 3.6" hashes = [ "h1:OO+IuvQJSPmWdN8AyyIEvPJbLvDQpgX/zbktoa9KsJE=", + "h1:UlBuNVuCGJ39tTv2c5gz2NRZnQbXfbIWbTzWcth5o74=", "zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1", "zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea", "zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f", @@ -27,6 +51,7 @@ provider "registry.terraform.io/hashicorp/vault" { constraints = "~> 5.0" hashes = [ "h1:f+4Eh/Bzl1iun3tG3oCol/zsEqAwtFpl1PP59l8DL+k=", + "h1:lyFssgWG/91rFC++zoiJQDaqhzWieLL64bIF6+53Hq4=", "zh:0de49889bc7cfb66f9004651252e4c38acb0c927335ce0cbe1ee59a576030a44", "zh:1875cbcbac4bf19f2e9f9bd6f7a4589419f65969676c879b5c80ca4823818011", "zh:32f51e4eae2157a3c56ff40cd72f218776a9a8cc2151a6d78a36078c87243ac7", diff --git a/05-secrets/openbao/managed/README.md b/05-secrets/openbao/managed/README.md index 3a6fbfd..6054b67 100644 --- a/05-secrets/openbao/managed/README.md +++ b/05-secrets/openbao/managed/README.md @@ -64,6 +64,20 @@ that triggers a rewrite; bump it to rotate: - `secrets_sync_github_global` / `secrets_sync_github_repo[...]` / `secrets_sync_github_repo_env[...]` (`apps/secrets-sync/github/*`) — see `var.secrets_sync_github` below. +- `wireguard_server_key` (`apps/wireguard/server-key`) — the WireGuard + tunnel server's own key, sourced from `04-vpn/wireguard`'s + `server_private_key` output (external to this provider — that root only + generates keys locally, it doesn't write to OpenBao itself). Read back out + by the gitops repo's `services/platform/wireguard/init` (ExternalSecret, + same pattern as every other app's `init` chart) — its own object, since + nothing else ever reads it. +- `secrets_sync_github_infrastructure_scaleway`'s `WG_CI_PRIVATE_KEY` field + (below) — CI's own WireGuard peer key, sourced from `04-vpn/wireguard`'s + `peer_private_keys["ci-github-actions"]` output, merged in alongside + `SCW_ACCESS_KEY`/`SCW_SECRET_KEY` rather than a separate KV object: it + needs to ride the *existing* `secrets-sync` pipeline out to a GitHub + Actions secret, which reads from that one object, not a new one nothing + would push. See `04-vpn/wireguard/README.md` for the full hand-off. - `velero_scaleway_s3_credentials` (`apps/velero/scaleway-s3-credentials`) — `SCW_ACCESS_KEY`/`SCW_SECRET_KEY` come from `data.terraform_remote_state.backup_scaleway` (infra's own `03-storage/scaleway` @@ -92,7 +106,8 @@ secrets_sync_github = { environments = { scaleway = { TEST_SCALEWAY = "..." } # -> kv/apps/secrets-sync/github/infrastructure-scaleway # SCW_ACCESS_KEY/SCW_SECRET_KEY are merged in from - # data.terraform_remote_state.dns_scaleway, not set here. + # data.terraform_remote_state.dns_scaleway, WG_CI_PRIVATE_KEY from + # var.wireguard_ci_private_key — none of the three set here. } } } @@ -122,6 +137,15 @@ convention, not automation. a gitignored `local.auto.tfvars` (matches `*.auto.tfvars` in the repo `.gitignore`, same convention as `10-cluster/scaleway/local.auto.tfvars`). Never paste secret values into shell history or commit them. +- **`var.wireguard_server_private_key`**, **`var.wireguard_ci_private_key`**: + from `04-vpn/wireguard`'s outputs — + + ```bash + terraform -chdir=../../../04-vpn/wireguard output -raw server_private_key + terraform -chdir=../../../04-vpn/wireguard output -json peer_private_keys | jq -r '."ci-github-actions"' + ``` + + Same `local.auto.tfvars` as above. - Everything else (`random_password.*`) is Terraform-generated — no variable needed. diff --git a/05-secrets/openbao/managed/main.tf b/05-secrets/openbao/managed/main.tf index 14d4573..8aa5458 100644 --- a/05-secrets/openbao/managed/main.tf +++ b/05-secrets/openbao/managed/main.tf @@ -25,23 +25,23 @@ resource "vault_kubernetes_auth_backend_config" "kubernetes" { # openbao-snapshot/openbao — the raft snapshot agent bundled in the openbao # Helm chart (gitops repo platform/scaleway/openbao.yml, snapshotAgent). resource "vault_kubernetes_auth_backend_role" "snapshot" { - backend = vault_auth_backend.kubernetes.path - role_name = "snapshot" - bound_service_account_names = ["openbao-snapshot"] - bound_service_account_namespaces = ["openbao"] - token_policies = [vault_policy.snapshot.name] - token_ttl = 3600 + backend = vault_auth_backend.kubernetes.path + role_name = "snapshot" + bound_service_account_names = ["openbao-snapshot"] + bound_service_account_namespaces = ["openbao"] + token_policies = [vault_policy.snapshot.name] + token_ttl = 3600 } # external-secrets/external-secrets — ESO's ClusterSecretStore # (gitops repo apps/openbao-init/templates/clustersecretstore.yaml). resource "vault_kubernetes_auth_backend_role" "external_secrets" { - backend = vault_auth_backend.kubernetes.path - role_name = "external-secrets" - bound_service_account_names = ["external-secrets"] - bound_service_account_namespaces = ["external-secrets"] - token_policies = [vault_policy.eso_read.name] - token_ttl = 3600 + backend = vault_auth_backend.kubernetes.path + role_name = "external-secrets" + bound_service_account_names = ["external-secrets"] + bound_service_account_namespaces = ["external-secrets"] + token_policies = [vault_policy.eso_read.name] + token_ttl = 3600 } # --- OIDC auth: human login via Dex (gitops repo platform/scaleway/dex.yml, @@ -216,6 +216,22 @@ data "terraform_remote_state" "backup_scaleway" { } } +# 04-vpn/wireguard's own state — read directly instead of a hand-copied +# local.auto.tfvars value, same as every other cross-root credential on +# this page (dns_scaleway/backup_scaleway above, openbao_bootstrap in +# version.tf). Key is workspace_key_prefix/workspace/key from that root's +# version.tf — both left unchanged by the 04-network -> 04-vpn rename +# (CLAUDE.md's "backend keys are decoupled from paths"), so this doesn't +# move if that domain gets renamed again. +data "terraform_remote_state" "wireguard" { + backend = "s3" + config = { + bucket = "id-terraform-state20260612164136440800000001" + region = "eu-west-3" + key = "network/wireguard/04-network-wireguard/terraform.tfstate" + } +} + # --- Arbitrary Dex<->client shared secrets: nothing external constrains # these values, so Terraform generates and owns them outright — ArgoCD, # Grafana, and Dex itself all read the *same* kv/apps/dex/credentials object @@ -339,7 +355,8 @@ resource "vault_kv_secret_v2" "secrets_sync_github_repo" { # The only repo+environment target today. Written as a plain resource, not a # for_each over var.secrets_sync_github.repos.*.environments — there's one # member, and it needs a special-cased merge (SCW_ACCESS_KEY/SCW_SECRET_KEY -# from infra's own 01-iam/workload/scaleway state, not a hand-copied variable value). +# from infra's own 01-iam/workload/scaleway state, WG_CI_PRIVATE_KEY from +# 04-vpn/wireguard's state — see data.terraform_remote_state.wireguard above). # A generic for_each here would just be a single case with a fake abstraction # wrapped around it. Revisit if/when a second repo+environment target with no # remote-state merge shows up. @@ -352,11 +369,81 @@ resource "vault_kv_secret_v2" "secrets_sync_github_infrastructure_scaleway" { { SCW_ACCESS_KEY = data.terraform_remote_state.dns_scaleway.outputs.workload_access_key SCW_SECRET_KEY = data.terraform_remote_state.dns_scaleway.outputs.workload_secret_key + # CI's own WireGuard peer key — brings up the tunnel to OpenBao + # before CI's own `terraform plan/apply` on + # 05-secrets/openbao/{bootstrap,managed}. Read straight from + # 04-vpn/wireguard's state, no local.auto.tfvars copy-paste — same + # as SCW_ACCESS_KEY/SCW_SECRET_KEY above. + WG_CI_PRIVATE_KEY = data.terraform_remote_state.wireguard.outputs.peer_private_keys["ci-github-actions"] } )) + # Bumped from 1: adding WG_CI_PRIVATE_KEY to the merge above is a content + # change data_json_wo's write-only diffing can't see on its own (see the + # write-only explainer near the top of this file) — without this bump, + # `apply` would report "no changes" and never actually write the new key. + data_json_wo_version = 2 +} + +# apps/wireguard/server-key — the tunnel server's own key, read back out by +# the gitops repo's services/platform/wireguard/init (ExternalSecret, same +# pattern as every other app's init chart). Its own object, not merged into +# anything else: nothing but that one ExternalSecret ever reads it. Read +# straight from 04-vpn/wireguard's state, no local.auto.tfvars copy-paste. +resource "vault_kv_secret_v2" "wireguard_server_key" { + mount = vault_mount.kv.path + name = "apps/wireguard/server-key" + + data_json_wo = jsonencode({ + "private-key" = data.terraform_remote_state.wireguard.outputs.server_private_key + }) data_json_wo_version = 1 } +# apps/wireguard/peers — not actually secret (public keys + overlay +# addresses), but routed through the exact same OpenBao -> ESO pipe as +# everything else that crosses from this repo into the gitops repo, rather +# than hardcoded into that repo's values-scaleway.yaml. name -> {publicKey, +# allowedIPs}, matching the shape the gitops repo's services/platform/ +# wireguard/config chart expects for its peer allowlist. +resource "vault_kv_secret_v2" "wireguard_peers" { + mount = vault_mount.kv.path + name = "apps/wireguard/peers" + + data_json_wo = jsonencode({ + for name, key in data.terraform_remote_state.wireguard.outputs.peer_public_keys : + name => { + publicKey = key + allowedIPs = data.terraform_remote_state.wireguard.outputs.peer_addresses[name] + } + }) + # Bumped from 1: confirmed live 2026-08-10 that a cluster rebuild restored + # OpenBao from an hourly raft snapshot taken *before* this object was + # first written — write-only diffing has no way to detect that kind of + # out-of-band data loss on its own (it never reads the live value back, + # by design), so the version bump is what actually re-triggers the write. + data_json_wo_version = 2 +} + +# apps/wireguard/confs — full rendered wg-quick config per peer (server +# key + AllowedIPs + Endpoint already baked in), sensitive. Deliberately +# NOT synced into the cluster by anything (no ESO ExternalSecret reads +# this) — the only consumer is a human fetching their own config from +# OpenBao's UI/API directly, without needing local Terraform state access +# (recovering onto a new machine, etc.). Same admin-policy trust boundary +# already covers wireguard_server_key/CI's key above — any OpenBao admin +# can already read those, this is no wider a surface than what already +# exists; it just makes retrieval self-service instead of requiring this +# repo's state. +resource "vault_kv_secret_v2" "wireguard_confs" { + mount = vault_mount.kv.path + name = "apps/wireguard/confs" + + data_json_wo = jsonencode(data.terraform_remote_state.wireguard.outputs.peer_confs) + # Bumped from 1 — same raft-snapshot-predates-the-write gap as + # wireguard_peers above, confirmed live 2026-08-10. + data_json_wo_version = 2 +} + # apps/velero/scaleway-s3-credentials — Velero's Object Storage credentials # (gitops repo apps/velero-init, platform/scaleway/velero.yml). A SEPARATE IAM # key AND bucket from scaleway-s3-credentials (OpenBao's own snapshot agent): diff --git a/05-secrets/openbao/managed/variables.tf b/05-secrets/openbao/managed/variables.tf index 8bff7d5..d214cbf 100644 --- a/05-secrets/openbao/managed/variables.tf +++ b/05-secrets/openbao/managed/variables.tf @@ -39,7 +39,10 @@ variable "secrets_sync_github_eso_private_key" { # # SCW_ACCESS_KEY/SCW_SECRET_KEY under repos.infrastructure.environments.scaleway # are NOT supplied here — main.tf merges those in from -# data.terraform_remote_state.dns_scaleway instead. +# data.terraform_remote_state.dns_scaleway instead. WireGuard's keys are the +# same story: read straight from data.terraform_remote_state.wireguard, no +# variable needed. + variable "secrets_sync_github" { description = "GitHub secrets-sync content (kv/apps/secrets-sync/github/*), scoped by org/repo/repo+environment." type = object({ diff --git a/05-secrets/openbao/managed/version.tf b/05-secrets/openbao/managed/version.tf index 4c8c452..c2e9d33 100644 --- a/05-secrets/openbao/managed/version.tf +++ b/05-secrets/openbao/managed/version.tf @@ -1,11 +1,11 @@ terraform { backend "s3" { - bucket = "id-terraform-state20260612164136440800000001" - region = "eu-west-3" - workspace_key_prefix = "secrets/managed/openbao" - key = "terraform.tfstate" - encrypt = true - use_lockfile = true + bucket = "id-terraform-state20260612164136440800000001" + region = "eu-west-3" + workspace_key_prefix = "secrets/managed/openbao" + key = "terraform.tfstate" + encrypt = true + use_lockfile = true } required_providers { @@ -42,9 +42,18 @@ data "terraform_remote_state" "openbao_bootstrap" { # not Vault's VAULT_ADDR/VAULT_TOKEN, so relying on the env var is a trap (see # the bootstrap root's version.tf/README for the incident this came from). provider "vault" { + # Same hostname as OpenBao's public route, on purpose — not a fallback, + # the actual mechanism: split-DNS (the tunnel's dns sidecar, gitops + # repo's services/platform/wireguard/config) resolves this to the + # WireGuard tunnel's own address (04-vpn/wireguard) while it's up, + # routing privately through Envoy Gateway's real Service (proxy-gateway + # sidecar) instead of the public internet. Same address either way — + # bring the tunnel up first (`wg-quick up `), or + # this just hits the real public route (fine; that's still there for + # human OIDC/UI login, this provider just doesn't need it anymore). address = "https://openbao.scalepack.fr/" - # Alternative URL when something goes wrong with public connection + # Direct port-forward, independent of the tunnel/gateway path entirely. # Requires Kubernetes permissions to run `kubectl port-forward -n openbao openbao-0 8200:8200` # address = "http://127.0.0.1:8200/"