--- # WireGuard remote-access VPN (north-south INGRESS). Reconciled by its OWN non-gating post-platform # Flux Kustomization (clusters/base/vpn-ingress.yaml, dependsOn platform-config, wait:false) — the # same placement as crafty/headlamp/homepage/runbooks, for the same reason: a leaf whose pod can be # legitimately unhealthy must never sit in the gating `platform` tier, where one component's # unhealth stalls platform-config and every apps wave behind it (DECISIONS-LOG: "Headlamp gating # stall"). That rationale is unusually concrete here: until the operator replaces the `REPLACE-ME` # placeholder in wg-server-key.sops.yaml (runbooks/vpn-remote-access.md step 2) this DaemonSet # CANNOT start, so putting it in the gating tier would have taken the whole platform down on merge. # # NOT in platform/kustomization.yaml — see clusters/base/vpn-ingress.yaml. apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization # REQUIRED for the configMapGenerator below to be wired up at all, not merely tidy. A generated # ConfigMap inherits NO namespace, every other object here declares `vpn-ingress`, and kustomize's # nameReference transformer will not rewrite a reference across a namespace boundary — so without # this the DaemonSet keeps mounting the unhashed name `wg-peers`, which does not exist, and the # pods wedge on a missing volume. Verified by building it both ways (kustomize v5.8.1). # Same placement as runbooks/kustomization.yaml, which uses `namespace: runbooks` for its own # content generator. namespace: vpn-ingress resources: - namespace.yaml # The listener itself: hostNetwork DaemonSet, udp/51820 on every node. - daemonset.yaml # Namespace default-deny baseline. NOTE: does NOT constrain the hostNetwork DaemonSet — the file # says so at length rather than implying a protection it cannot provide. - networkpolicy.yaml # The server's WireGuard private key — the ONE secret here. SOPS-encrypted (R-SEC-01, spec # docs/07), decrypted per-env by the `vpn-ingress` Kustomization, referenced by name only from # daemonset.yaml (R-IDP-04). Its plaintext `.example` sibling carries the operator hand-off -- # deliberately NOT named here: conformance/tests/l0-static/no-plaintext-secrets fails any # kustomization that mentions an unencrypted template, because naming one is how a plaintext # Secret gets applied by accident. (It matches comments too. That is correct, not overzealous.) # # SHIPS WITH A `REPLACE-ME` PLACEHOLDER, already encrypted. An agent must not # mint a long-lived private key (CLAUDE.md §6), and the init container validates the value with # `wg pubkey`, so the unfilled state is a loud CrashLoop naming this file, never a server that # quietly cannot handshake. - wg-server-key.sops.yaml # # THIS COMPONENT CARRIES AN ENCRYPTED SECRET, so BOTH overlays must patch the `vpn-ingress` # Kustomization with a `decryption:` stanza — clusters/dev and clusters/prod both do, added by the # same change as this file. That patch is as load-bearing as the file itself: without it kustomize # hands Flux the raw ciphertext, the Secret APPLIES CLEANLY full of ENC[...], and nothing in any # Flux condition looks wrong. The init container's `wg pubkey` guard exists partly to turn exactly # that into a named error rather than a silently non-working tunnel. # ── THE PEER REGISTER, as a CONTENT-HASHED ConfigMap ───────────────────────────────────────── # peers.conf (plaintext, reviewable — see the file) becomes the `wg-peers` ConfigMap. It is a # GENERATOR, not a plain resource, and that distinction is load-bearing rather than stylistic: # kustomize appends a hash of the content to the generated name and rewrites the DaemonSet's # volume reference to match, so adding or revoking a peer changes the pod template and ROLLS THE # PODS automatically. # # A fixed-name ConfigMap (which is what this component shipped first) does not do that. Flux # updates it in place, the pod template is byte-identical, no rollout is triggered — and because # the init container assembles wg0.conf ONCE at pod start into a tmpfs, the running tunnel keeps # the old peer list indefinitely. Measured on prod 2026-10-06: a merged peer reached the # ConfigMap while `wg show` listed no peers and the DaemonSet still read generation=1, having # never rolled. The runbook's "PR -> merge -> Flux rolls the DaemonSet" was false until this. # # DO NOT set `disableNameSuffixHash` here. The hash IS the mechanism. configMapGenerator: - name: wg-peers files: - peers.conf options: labels: app.kubernetes.io/name: vpn-ingress