|
|
|
@@ -0,0 +1,73 @@
|
|
|
|
|
---
|
|
|
|
|
date: 2026-10-06
|
|
|
|
|
branch: fix/new-peer-ignores-template-comments
|
|
|
|
|
title: new-peer.sh refused the first VPN address because its duplicate check matched its own commented-out example
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## What changed
|
|
|
|
|
|
|
|
|
|
The operator's first real enrolment failed:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
$ ./infra/vpn-ingress/new-peer.sh keith-phone 2
|
|
|
|
|
new-peer.sh: 10.90.0.2/32 is already allocated in platform/vpn-ingress/peers.yaml. Pick another octet.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Nothing was allocated.** `peers.yaml` had zero peers — the pod's own init container reported
|
|
|
|
|
`wg0.conf assembled: 0 peer(s)`. The duplicate-address check grepped the WHOLE file, and
|
|
|
|
|
`peers.yaml` line 66 is a **commented-out example stanza** I wrote, containing literally
|
|
|
|
|
`# AllowedIPs = 10.90.0.2/32`. So `.2` — the first address anybody would ask for, and the one my
|
|
|
|
|
own runbook uses in its example — was permanently un-allocatable, with a message asserting the
|
|
|
|
|
opposite of the truth.
|
|
|
|
|
|
|
|
|
|
Both halves fixed, because either alone leaves the trap half-set:
|
|
|
|
|
|
|
|
|
|
- **`infra/vpn-ingress/new-peer.sh`**: the check strips comment lines before matching
|
|
|
|
|
(`grep -vE '^[[:space:]]*#' | grep -q …`). That is the correct predicate rather than a patch: a
|
|
|
|
|
real stanza's `AllowedIPs` line is never commented, while both the template and every peer's
|
|
|
|
|
`# <name> — <owner>` label always are.
|
|
|
|
|
- **`platform/vpn-ingress/peers.yaml`**: the example now uses placeholders that cannot match
|
|
|
|
|
anything real — `# <device-name> — <owner>, enrolled <YYYY-MM-DD>` and
|
|
|
|
|
`AllowedIPs = 10.90.0.<octet>/32`. A future grep cannot be fooled by it either.
|
|
|
|
|
|
|
|
|
|
The bug class is worth naming: **a documentation example that is byte-identical to real data will
|
|
|
|
|
eventually be parsed as real data.** The same shape caused two earlier defects in this component —
|
|
|
|
|
`flux-postbuild-substitution` flagged a `${...}` written inside a comment *explaining* that hazard,
|
|
|
|
|
and `no-plaintext-secrets` flagged the `.sops.yaml.example` because the kustomization named it in a
|
|
|
|
|
comment. Three instances now; the rule is to make examples structurally unmatchable, not merely
|
|
|
|
|
commented out.
|
|
|
|
|
|
|
|
|
|
No sensitive paths (`platform/vpn-ingress/**`, `infra/vpn-ingress/**`, `decisions/**`), so this
|
|
|
|
|
merges on a green gate with no override.
|
|
|
|
|
|
|
|
|
|
## What I verified
|
|
|
|
|
|
|
|
|
|
Run in the pinned toolbox against the live tree, 2026-10-06.
|
|
|
|
|
|
|
|
|
|
- **`.2` now works**, and it went through the REAL key path: `new-peer.sh keith-phone 2` rendered a
|
|
|
|
|
profile with `Address 10.90.0.2/32`, `AllowedIPs = 10.90.0.2/32` in the printed stanza, full
|
|
|
|
|
tunnel, at `.cache/wg-peers/keith-phone.conf` (0600). The throwaway keypair was deleted after.
|
|
|
|
|
- **This incidentally closes a gap left open in #60**: the profile could only have been rendered by
|
|
|
|
|
deriving the server's public key via `sops -d --extract … | wg pubkey` from
|
|
|
|
|
`wg-server-key.sops.yaml` — so that pipeline is now proven **against the operator's real key**,
|
|
|
|
|
not just against a placeholder it was expected to reject.
|
|
|
|
|
- **A real duplicate is still refused.** Appending an uncommented
|
|
|
|
|
`AllowedIPs = 10.90.0.5/32` stanza and asking for octet 5 produced the correct refusal; the tree
|
|
|
|
|
was restored afterwards and `git diff --stat` showed only the intended template edit.
|
|
|
|
|
- **Zero active allocations remain**: `grep -vE '^[[:space:]]*#' peers.yaml | grep -c AllowedIPs`
|
|
|
|
|
→ `0`.
|
|
|
|
|
- **Toolbox gate**: rc 3, with `spec-immutability`, `decisions-log`, `lint`, `l0`, `render-golden`,
|
|
|
|
|
`l1` and `approval-policy` all pass; rc 3 is the two environmental skips (`gitea-settings`,
|
|
|
|
|
conformance L2/L3). **Not green. Reported as rc 3.**
|
|
|
|
|
|
|
|
|
|
## What I did NOT verify
|
|
|
|
|
|
|
|
|
|
- **No peer is enrolled by this branch.** The keypair I generated was a throwaway and was deleted;
|
|
|
|
|
the operator must run the script themselves, since the device's private key must exist only on
|
|
|
|
|
the device. So the end of the enrolment path — a stanza merged into `peers.yaml`, Flux rolling
|
|
|
|
|
the DaemonSet, and `wg show` listing a peer — is still unexercised.
|
|
|
|
|
- **The duplicate-NAME warning** (`grep -qi "^ *# *${NAME} "`) deliberately still inspects
|
|
|
|
|
comments, because per-peer labels are comments. It is no longer tripped by the template, but I
|
|
|
|
|
did not test it against a real enrolled peer.
|
|
|
|
|
- **Nothing about client traffic.** The WAN port-forward is still not configured and there is still
|
|
|
|
|
no off-LAN vantage point, so the tunnel has carried no packet.
|