WireGuard to the lab jumpbox: terminate on VLAN 30, route it, and record what it costs - #495
Merged
Merged
Conversation
The estate has no remote access, and nothing in docs/ describes an alternative. This designs one on a quiet afternoon rather than under time pressure: WireGuard terminating on the #436 jumpbox, on ImaginationLAN, reaching the lab only. Three things the issue did not carry: ADR-0022's second trigger is fired, not dodged. The narrow reading — that "any of it" means the sensitive tier, which a lab-only tunnel leaves unreachable — does not survive ADR-0014, whose reopening clause already says remote access into VLAN 30 "takes ADR-0008's no external access premise with it", and trigger 2 is that premise made testable. It is also wrong on the facts: stacks/lab publishes Grafana OSS on alexander, which ADR-0022's own table says can carry no second factor in any edition. So the trigger is recorded as fired and the deferral re-accepted with reasons, which is what ADR-0022 provides for. Routed, not masqueraded. NAT would collapse every peer into the jumpbox's address, so the Proxmox pass #436 needs on Saruman would extend to every peer and the tripwire would lose attribution. The cost is that a second source subnet arrives on igc0.30: the blocks and the tripwire are sourced from an interface network macro and LabSegmentReachedInternalNetwork hard-codes 10.0.30.x, so neither matches a peer. Both are widened here — unwidened, routed mode is strictly worse than NAT. The endpoint does not exist. The WAN address is DHCP-assigned, there is no dynamic DNS, and ADR-0011 measured "no rdr, no inbound WAN pass". Recorded as blocking and left to its own issue: a static IP is a recurring purchase and a DDNS provider is a third party handed a pointer to the house. 172.31.0.0/24 for the peers, /32 each. An earlier draft used 172.31.30.0/24 to echo the VLAN; rejected because it is one digit from ifrit's 172.30.30.0/24 and the two mean opposite things in a log line. 172.30. is the range, 172.31. is the tunnel. ADR-0014's clause says this gets a superseding ADR. It does not, and declining that is said out loud: the clause fired on a premise borrowed from ADR-0008, not on anything ADR-0014 decided, and every bullet it decided still governs. ADR-0007's note form is the precedent. ADR-0008, ADR-0011, ADR-0014 and ADR-0022 get forward pointers; no body is edited. Authored ahead of the host, as stacks/sensitive was ahead of trinity. The jumpbox does not exist and nothing here is deployed. Refs #442 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 17, 2026
Gerrrt
added a commit
that referenced
this pull request
Sep 17, 2026
… counts
`main` is red. Three failures, all from two PRs merging within a minute of each
other without seeing one another:
README.md:95 claims 41 ADRs; the repository has 42
README.md:96 claims 26 runbooks; the repository has 27
ADR-0041 is claimed by 2 files — renumber the one that landed second
#491 gave ADR-0041 to *Run the CRS326 on RouterOS* at 23:27:57Z and #495 gave
the same number to *Terminate the remote path on the lab* at 23:28:52Z. The
ADR-numbering assertion is exactly the check for this; it fired one merge too
late, because each branch was green against a `main` that did not yet hold the
other.
The remote-path ADR landed second, so it becomes **ADR-0042** — which is what
`check_docs.py` asks for by name. Renumbered: the filename, its own H1, eleven
links that targeted it, and thirteen bare `ADR-0041` mentions that meant it.
Every remaining `ADR-0041` in the tree is the CRS326 one, checked individually
rather than by pattern, because the two ADRs shared a number and a
search-and-replace would have renumbered both.
Two of the bare mentions are inside other ADRs' amendment blockquotes (ADR-0011,
ADR-0014). Updating a pointer to a renumbered file is not revising a decision,
so ADR-0001's immutability is not in question — and leaving them would be
leaving a reference to a number that now means something else.
`open-the-remote-path.md`'s pfSense rule descriptions change with it
(`WireGuard peers — ADR-0042`). Safe: the tunnel does not exist, its runbook is
gated on the jumpbox of #436, and `restore-the-firewall.md` still counts the
tripwires as though it were unbuilt.
The two README counts are the arithmetic consequence of the same two merges —
one ADR and two runbooks arrived, and the sentence that counts them did not
move.
Refs #491 #495
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 17, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Designs the estate's first remote path, and records the decision rather than
the mechanism: WireGuard terminating on the #436
jumpbox, on ImaginationLAN, reaching the lab only. Adds ADR-0041 and
docs/runbooks/open-the-remote-path.md, widens the lab tripwire's alert forthe peer subnet, and puts forward pointers on the four ADRs this disturbs.
Nothing is deployed. The jumpbox does not exist — #436 is Wave 4 and
unbuilt — so this is authored ahead of the host the way
stacks/sensitivewasauthored ahead of
trinity.Refs #442, notCloses.Why
#442 asks for the remote path to be designed deliberately rather than
improvised under time pressure. Three things surfaced that the issue did not
carry, and each changed the shape of the work.
ADR-0022's second trigger is fired, and is recorded as fired. The issue
says terminating on 30 "trips nothing". The narrow reading that gets you there
— "any of it" means the sensitive tier, which a lab-only tunnel leaves
unreachable — does not survive ADR-0014, whose reopening clause already says
remote access into VLAN 30 "takes ADR-0008's 'no external access' premise with
it", and trigger 2 is that premise made testable. It is also wrong on the
facts:
stacks/labpublishes Grafana OSS onalexander, which ADR-0022's owntable says can carry no second factor in any edition. So the trigger is
declared fired and the deferral re-accepted with reasons, which is exactly
what ADR-0022 provides for. Triggers 1 and 3 keep their full force.
Routed, not masqueraded — and that has a cost the issue does not mention.
NAT would collapse every peer into the jumpbox's own address, so the Proxmox
pass #436 needs on
Sarumanwould silently extend to every VPN peer, and thetripwire would lose attribution. Dropping the NAT fixes that and introduces a
second source subnet on
igc0.30— which the blocks and the tripwire, sourcedfrom an interface network macro, do not match, and which
LabSegmentReachedInternalNetworkdoes not match either because it hard-codes10.0.30.x. Both are widened here. Unwidened, routed mode is strictly worsethan NAT, because it puts a subnet on the lab interface that nothing watches.
There is no way to be an endpoint today. The WAN address is ISP-assigned by
DHCP, there is no dynamic DNS anywhere in the repo, and ADR-0011 records a
live-verified "no
rdrport forwards, no inbound WAN passes beyond DHCPclient replies". Recorded as blocking and left to its own decision: a static
IP is a recurring purchase, and a DDNS provider is a third party handed a
continuously-updated pointer to a house whose WAN address
security.mddeliberately withholds.
Blast radius
Documentation and one alert regex. No firewall rule is created, no host is
touched, and the tunnel does not exist.
secrets/*.sops.yamlThe first box is deliberately unticked: this decides a segmentation change
(one inbound WAN pass, one static route, a doubled block set and tripwire on
igc0.30) without making one.docs/firewall-claims.yamlis unchanged andstill accurate —
igc0.30'swholesalestays[]— and ADR-0041 records thatthe file's per-interface shape cannot express "a second source subnet arrives
here", so a green
make check-firewallshould not be read as more than it is..sops.yamlis deliberately untouched: a jumpboxcreation_rulewith nomatching file fails
scripts/check_sops_rules.py, which is the check thatexists because a dead rule once fell through to the catch-all.
On ADR immutability
ADR-0014's clause says this gets a superseding ADR. It does not, and ADR-0041
says so out loud rather than quietly writing a note. The clause fired on a
premise ADR-0014 borrowed from ADR-0008, not on anything ADR-0014 decided —
every bullet it decided still governs and is still checked by
build-the-playground.md. ADR-0007's note-block form is the precedent.ADR-0008, ADR-0011, ADR-0014 and ADR-0022 all get forward pointers; no body is
edited.
Known collision, recorded not resolved
ADR-0014 closes Proxmox
8006to10.0.50.0/24only, and ADR-0039 restates itas "Nothing on VLAN 30 gains a path to
8006on either hypervisor." #436'sjumpbox is on VLAN 30 and exists to hold a Proxmox API token. Either that rule
widens — which changes an ADR-0014 decision and would need a partial
supersession rather than a note — or the jumpbox cannot do its job. That is
#436's call and is not taken here. Routed mode is what stops a widened rule
from silently admitting every peer.
Verification
Offline; nothing touches a host.
scripts/check_docs.py— passes;41 ADRs/26 runbooksbumped inREADME.md, and the already-stale39 architecture decision recordsfixed
scripts/check_loki_rules.sh— 18 rules parse and evaluatescripts/lint.sh— yamllint, markdownlint, shellcheck, actionlint,editorconfig all green
check_sops_rules.py,check-sops-encrypted.sh,check-tracked-artefacts.sh,check_image_pins.py— all greenmake validate— still running locally against a docker daemonshared with ~20 concurrent runs from sibling worktrees. Every check this
diff touches is green above; CI is the authority here
does not exist and the endpoint question is unanswered
🤖 Generated with Claude Code