Lets the operator's Android / Linux / Windows / macOS devices dial in from anywhere and behave like LAN machines: full reach to 192.168.2.0/24 plus internet egress out the house WAN. Opposite direction to platform/vpn-egress, which sends pods OUT through PIA; the two share nothing but the word "VPN". WireGuard because it is the only option with a first-party client on all four named platforms, and because its handshake MAC is keyed with the responder's own public key: an unauthenticated packet gets NO reply, so udp/51820 is indistinguishable from a closed port. That, not "it is encrypted", is what makes an unfiltered WAN port defensible, and why there is no source allowlist. hostNetwork: true is REQUIRED, not convenient. A pod-networked server must SNAT clients to its own pod IP, so every remote client would carry that pod's Cilium identity -- and a pod-sourced request to the shared Gateway VIP returns 403 from Envoy for ALL 16 hostnames (measured 2026-10-04), because Cilium's Gateway preserves the original source identity and enforces the destination's ingress policy. The obvious build would have shipped a healthy-looking tunnel on which every cluster web UI was 403 and *.home.keithjackson.dev did not even resolve. In the host netns traffic is SNATed to the node and arrives as host/remote-node, exactly like a LAN client. It also sidesteps the unsafe ip_forward sysctl, the L2-announcement lease lottery, and double encapsulation (hence MTU 1420). Permitted by restrict-host-namespaces' own carve-out for platform namespaces. The cost is stated in networkpolicy.yaml rather than papered over: no NetworkPolicy constrains a hostNetwork pod. A DaemonSet so the hand-made WAN forward cannot be invalidated by a reschedule; all three nodes answer the same keys, so failover is a one-line router change. Peer PUBLIC keys are plaintext in peers.yaml, which makes "who can get into my house" a reviewable PR diff. The only secret -- the server's private key -- is an out-of-band Secret: an agent must not mint a long-lived private key, and the two halves are joined only at pod start into a tmpfs that dies with the pod. Ships INERT-BUT-HEALTHY with zero peers. THREE off-repo manual steps remain (kernel module, server key, WAN forward) -- runbooks/vpn-remote-access.md. Touches clusters/** , so ci/approval-policy.sh will block pending the operator's /approve-override. Deliberately not posted by me (CLAUDE.md §1). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
31 lines
1.9 KiB
YAML
31 lines
1.9 KiB
YAML
---
|
|
# 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 creates the `wg-server-key`
|
|
# Secret (runbooks/vpn-remote-access.md step 1) 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
|
|
resources:
|
|
- namespace.yaml
|
|
# The peer register: which devices may dial in. PLAINTEXT and reviewable on purpose (see the file).
|
|
- peers.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
|
|
#
|
|
# DELIBERATELY ABSENT: a Secret. The server's private key (`wg-server-key`) is created out of band
|
|
# by the operator and referenced BY NAME from daemonset.yaml. There is no `*.sops.yaml` here, so
|
|
# this component needs NO SOPS decryption stanza in clusters/dev or clusters/prod — unlike
|
|
# crafty/headlamp/velero. If that key is ever migrated into SOPS, BOTH halves must land together:
|
|
# the file here, and a `decryption:` patch naming the `vpn-ingress` Kustomization in each overlay.
|
|
# Without the patch, kustomize hands Flux the raw ciphertext and the pod mounts a key full of
|
|
# ENC[...] — which applies cleanly and fails only at handshake time.
|