Add the at-home peer (10.90.0.3) and list ${WG_CLIENT_CIDR} in the PiHole
DNS frontends' loadBalancerSourceRanges. wg0 is a Cilium device, so tunnel
clients reach the source-range check untranslated; a LAN-only list is
predicted to drop their DNS. Adds an L0 guard that both ranges are present
and every range is RFC1918, and corrects the README's SNAT and IPv6 claims.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
97 lines
6.2 KiB
Plaintext
97 lines
6.2 KiB
Plaintext
# THIS FILE IS THE MOUNTED CONTENT. It is turned into the `wg-peers` ConfigMap by
|
|
# `configMapGenerator` in kustomization.yaml, whose generated name carries a CONTENT HASH — so
|
|
# editing this file changes the ConfigMap name, which changes the DaemonSet's pod template, which
|
|
# ROLLS THE PODS. That is the whole reason it is a generator and not an inline ConfigMap manifest:
|
|
# a fixed-name ConfigMap updates in place, the pod template never changes, no rollout happens, and
|
|
# the init container's wg0.conf (built once at pod start, in tmpfs) keeps the OLD peer list
|
|
# forever. Measured 2026-10-06: a merged peer sat in the ConfigMap while `wg show` listed none and
|
|
# the DaemonSet reported generation=1 — it had never rolled. Same mechanism and same fix as
|
|
# runbooks/kustomization.yaml.
|
|
#
|
|
# Every line here is WireGuard config. `#` is a legal comment in it, so the documentation below
|
|
# travels inside the file that is actually mounted, rather than in a wrapper that gets stripped.
|
|
#
|
|
# ============================================================================================
|
|
# THE PEER REGISTER — every device allowed to dial in, in PLAINTEXT, in git, on purpose.
|
|
# ============================================================================================
|
|
# WireGuard has no usernames, no passwords and no PKI. Authorisation is exactly one thing: a
|
|
# device's PUBLIC key appears in this file, or it cannot complete a handshake. An unlisted key
|
|
# gets no reply at all — not a rejection, silence (see README "Why this is safe to expose").
|
|
# Revoking a device is therefore deleting its stanza, and nothing else.
|
|
#
|
|
# WHY PLAINTEXT, AND WHY THIS IS NOT A SECRET LEAK. A WireGuard peer's PUBLIC key is public by
|
|
# construction — it is the half you are supposed to hand to the server, and holding it grants
|
|
# nothing. The only secret in the whole scheme is the SERVER's private key, which is NOT here:
|
|
# it lives in the `wg-server-key` Secret and is assembled into the running config at pod start
|
|
# (daemonset.yaml). Keeping the register in cleartext buys the property that matters most for an
|
|
# access-control list: **"who can get into my house" is reviewable in a pull-request diff**, and
|
|
# its history is `git log` on this file. A SOPS-encrypted register would hide exactly the change
|
|
# a reviewer most needs to see — a new line granting a new device entry to the whole LAN.
|
|
#
|
|
# This is the same division of labour the repo already uses for the opposite case: a Secret's KEY
|
|
# NAMES are cleartext while values are encrypted (.sops.yaml `encrypted_regex`), so diffs stay
|
|
# readable. Here the whole file is the key-name half.
|
|
#
|
|
# ADDING A DEVICE — never hand-roll this, and never generate a key in a chat transcript.
|
|
# ./infra/vpn-ingress/new-peer.sh <device-name> <last-octet>
|
|
# It generates the keypair ON THE OPERATOR'S MACHINE, writes/renders the client profile (and a
|
|
# QR code for phones), prints ONLY the public half for pasting below, and never transmits or
|
|
# logs the private half. Then: add the stanza, PR, merge; Flux rolls the DaemonSet.
|
|
#
|
|
# ADDRESS ALLOCATION. ${WG_CLIENT_CIDR} is the tunnel subnet; .1 is the server (every node).
|
|
# Give each device ONE address as a /32 in AllowedIPs. A /32 is not a formality — on the SERVER
|
|
# side AllowedIPs is the cryptokey ROUTING table AND the inbound source filter: a peer may only
|
|
# send from, and receive traffic for, the addresses listed in its own stanza. A sloppy /24 here
|
|
# would let any one device impersonate every other device's tunnel address.
|
|
#
|
|
# .1 the server (this DaemonSet, on every node)
|
|
# .2 - .254 devices, one each, allocated below. Keep this list dense and in order so the
|
|
# next free octet is obvious; never re-use an octet after revoking a device
|
|
# (an old client profile would silently start working again as someone else).
|
|
#
|
|
# PRESHARED KEYS are deliberately NOT used. They add a symmetric post-quantum hedge on top of
|
|
# the Noise handshake and would be a genuine secret — which would force this register into SOPS
|
|
# and cost the reviewability above. Revisit only if the threat model changes; if it does, the PSK
|
|
# belongs in the `wg-server-key` Secret, not here. See README "Hardening not taken".
|
|
#
|
|
# name: wg-peers
|
|
# namespace: vpn-ingress
|
|
# labels:
|
|
# app.kubernetes.io/name: vpn-ingress
|
|
# Appended VERBATIM to the generated [Interface] block, in this order, at pod start.
|
|
#
|
|
# *** THERE ARE NO PEERS YET. THAT IS THE CORRECT INITIAL STATE, NOT AN OVERSIGHT. ***
|
|
# An agent must not mint key material (CLAUDE.md §6), so no device could be enrolled as part of
|
|
# authoring this component. A zero-peer WireGuard interface is valid and starts cleanly: the
|
|
# DaemonSet will run and listen, and every handshake will be refused because no key is trusted.
|
|
# The VPN is therefore INERT-BUT-HEALTHY until the first stanza lands — fail-closed, and visible
|
|
# (`wg show` reports an interface with no peers).
|
|
#
|
|
# COPY THIS TEMPLATE, uncomment, one block per device:
|
|
#
|
|
# # <device-name> — <owner>, enrolled <YYYY-MM-DD>
|
|
# [Peer]
|
|
# PublicKey = <the public key new-peer.sh printed>
|
|
# AllowedIPs = 10.90.0.<octet>/32
|
|
#
|
|
# The placeholders above are deliberately NOT valid values. An earlier version of this template
|
|
# used a real-looking `10.90.0.2/32`, and new-peer.sh's duplicate-address check — which grepped
|
|
# the whole file — matched it, so `.2` could never be allocated and the script insisted it was
|
|
# already taken. The check now ignores comments; keeping the example unmatchable too means a
|
|
# future grep cannot be fooled either.
|
|
#
|
|
# Keep the `# <name> — <owner>, enrolled <date>` comment line. It is the only place a key is
|
|
# tied to a human-recognisable device; `wg show` can only ever print the key itself, so without
|
|
# it an incident responder cannot tell which stanza to delete.
|
|
#
|
|
# No peers enrolled yet. See the template in this file's comments and
|
|
# ./infra/vpn-ingress/new-peer.sh. One [Peer] block per device.
|
|
# keith-phone — <owner>, enrolled 2026-10-06
|
|
[Peer]
|
|
PublicKey = pBRSHrRyG2hnsuZ6FvHfEzF8hOVTF5stHUb/7fW2Km8=
|
|
AllowedIPs = 10.90.0.2/32
|
|
# keith-phone-home — <owner>, enrolled 2026-11-08 (at-home profile, LAN Endpoint; no hairpin NAT)
|
|
[Peer]
|
|
PublicKey = AT8lBJHqrVkv8RL9n0qWDsh5nyl0e3qgrfTAaYKgBz0=
|
|
AllowedIPs = 10.90.0.3/32
|