Files
home-network-k3s-impl/decisions/2026-10-06-new-peer-ignores-template-comments.md
jakman85andClaude Opus 5 146dd9a906
gate / gate (pull_request) Successful in 1m51s
gate / conformance (pull_request) Has been skipped
fix(vpn-ingress): new-peer.sh counted its own commented-out example as an allocated address
The first real enrolment failed:

    $ ./infra/vpn-ingress/new-peer.sh keith-phone 2
    new-peer.sh: 10.90.0.2/32 is already allocated ... Pick another octet.

Nothing was allocated -- peers.yaml had zero peers, as the pod's init
container reported (`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 anyone would ask for, and the one my own runbook uses as its example
-- was permanently un-allocatable, with a message asserting the opposite of
the truth.

Fixed on both sides, because either alone leaves the trap half-set:

  * the check strips comment lines before matching, which is the correct
    predicate rather than a patch: a real stanza's AllowedIPs line is never
    commented, while the template and every peer's `# <name> — <owner>` label
    always are.
  * the example now uses placeholders that cannot match anything real
    (`10.90.0.<octet>/32`, `<device-name>`), so 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. This is its third
instance 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 a kustomization named it in a comment.
Make examples structurally unmatchable, not merely commented out.

Verified by running it: .2 now renders a profile (via the real
`sops -d | wg pubkey` path, which this also proves against the operator's
actual key for the first time), and an uncommented duplicate stanza is still
correctly refused.

No sensitive paths; merges on a green gate.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-06 02:28:37 -05:00

4.2 KiB

date, branch, title
date branch title
2026-10-06 fix/new-peer-ignores-template-comments 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.