docs(adr): buy ifrit for IOPS and keep the range disposable (#96) - #258
Merged
Conversation
ADR-0014 settled where `ifrit` goes and left three things as adjectives. ADR-0017 pins them, and `build-the-playground.md` is the runbook ADR-0014 asked for when it said its constraints are "checked by the build runbook rather than assumed". The purchase: NVMe not a spindle, because the range's whole operation is snapshot-and-revert and ADR-0007 already records that the fleet's complaint is spindles rather than RAM; socketed RAM in two slots, because `prometheus`'s is soldered and that ceiling was bought by accident; quiet and not rack-mount, because ADR-0007 paid the DL360's acoustic cost once in an occupied office. Capacity, CPU and the memory kit are chosen at the till and recorded in `hardware.md`, the same split ADR-0016 used for the NAS. No redundancy: the recovery model is rebuild. The addresses: `172.30.30.0/24` on the isolated bridge, and nothing on it has a default route — `192.168.0.0/16` is the default of every vulnerable image, `100.64.0.0/10` is Tailscale's, and any further `10.0.0.0/16` is forbidden by ADR-0014 because a leak has to miss the ImaginationLAN pass to be caught by default deny. `ifrit` takes `10.0.30.30`, leaving `.20` clear for `Saruman`'s move out of the Kea pool, which ADR-0014 requires at the same time and which is a step in the runbook. The upkeep: none. No backups, because a deliberately-vulnerable image in the estate's backup store is a supported path for it to arrive somewhere it should not be. No monitoring, because a host that is off by design makes every rule watching it permanently firing or permanently silenced, and it would need a pass into Winterfell that ADR-0014's rule accounting does not have. No patching of the guests; the host is patched, because the host is the isolation mechanism. "After the main network is finished" now names issues: #101 first, because an attack VM pointed at an uninstrumented estate teaches nothing; #234 before the segment holds attackers; #235 decided before this build makes it true. #228 and #229 are explicitly not gates, and the ADR says why. The runbook checks each of ADR-0014's four properties with a command, and §9 proves the "a leak reports itself" claim by deliberately causing one and finding the block in Loki. Two traps found on the way: enabling the Proxmox datacenter firewall brings the per-NIC guest flag to life, and a range whose hypervisor quietly filters its own guests lies to you about what your tooling did; and installing Docker on the attack VM sets `ip_forward=1` at daemon start whatever the sysctl file says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
There are a few concrete documentation/runbook correctness issues (a truncated sentence and non-portable grep patterns) that should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR closes out the remaining documentation work for the ifrit playground decision by adding ADR-0017 and a concrete build runbook, and then propagating the chosen address plan and “disposable range” stance into the network/security/roadmap docs.
Changes:
- Add ADR-0017 defining purchase constraints,
172.30.30.0/24for the isolated bridge, and the explicit “no backups/monitoring/guest patching” policy for the range. - Add
docs/runbooks/build-the-playground.mdto operationalize ADR-0014’s constraints with explicit verification commands and a leak-proof test. - Update security/network/roadmap/ADR-0014 to reference ADR-0017 and the runbook, and to pin the chosen subnet/address details.
File summaries
| File | Description |
|---|---|
| docs/security.md | Updates threat-model row with the finalized unrouted target subnet and ADR references. |
| docs/runbooks/build-the-playground.md | New end-to-end runbook for building and validating the isolated playground on ifrit. |
| docs/roadmap.md | Expands the #96 roadmap entry with ADR-0017’s purchase/address/upkeep decisions and links to the runbook. |
| docs/network.md | Pins ifrit/range addressing and documents the unrouted 172.30.30.0/24 bridge behavior. |
| docs/adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md | New ADR capturing purchase constraints, addressing, and “disposable range” maintenance policy. |
| docs/adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md | Adds a forward-pointer note to ADR-0017 + runbook without amending ADR-0014’s core constraints. |
Review details
Suppressed comments (1)
docs/runbooks/build-the-playground.md:302
- Same portability issue here: the grep alternation uses | in basic regex mode. Use -E and a plain | so the command behaves consistently across grep implementations.
nft list ruleset 2>/dev/null | grep -i 'masquerade\|snat' || echo "no NAT"
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review feedback on #258. Two greps used GNU basic-regex alternation in commands meant to be copied and pasted; both are now `-E` with a plain `|`. The VLAN-aware check changes what it matches while it is there: the old pattern's two alternatives were the same test, since `vlan-aware` already matches `bridge-vlan-aware`. Proxmox writes `bridge-vids` alongside it on a VLAN-aware bridge, so that is the second marker worth looking for. And the threat-table row in `security.md` ended on "a default route to" with no object. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Closes #96 — as far as documents can. The purchase and the build are the two things left, and both need hardware that does not exist yet.
What this issue actually still contained
The issue calls
ifrit's deferred isolation mechanism "the real content", and it was — but ADR-0014 already answered it, jointly with #86.roadmap.mdagrees: "What is left is the purchase and a build runbook that checks each of those."What ADR-0014 did leave were three adjectives:
10.0.0.0/16" — a runbook cannot be written against an adjective.ADR-0017 pins those.
build-the-playground.mdis the runbook ADR-0014 asked for when it said its constraints are "checked by the build runbook rather than assumed".The purchase
Following ADR-0016's discipline — decide the half that cannot be changed later, choose the rest at the till:
Saruman's shape buys the same complaint twiceprometheus's is soldered at 8 GB. That ceiling was bought by accidentNo redundancy, deliberately: ADR-0016 gave the NAS a mirror because the requirement was availability. Here the recovery model is rebuild, and two disks protecting contents that are deliberately worthless is the kind of consistency that isn't one.
The addresses
172.30.30.0/24on the isolated bridge, with no default route anywhere on it and no address on the host.Rejected:
192.168.0.0/16(the default of nearly every vulnerable image, and a192.168source is the least remarkable line in a log),100.64.0.0/10(Tailscale's), and any further10.0.0.0/16— ADR-0014 forbids it, because a leak has to miss the ImaginationLAN pass rule to be caught by default deny. The /24 sits high in172.16.0.0/12because Docker's pool walks that block upward from172.17.No DHCP on the bridge: the obvious place to run it is the dual-homed VM, and a DHCP server that binds the wrong interface there is rogue DHCP on the segment holding the estate's hypervisor and its BMC.
ifrittakes10.0.30.30, leaving.20clear forSaruman— whose move out of the Kea pool ADR-0014 requires "at the same time", so it is a step in the runbook rather than a detail of it.The upkeep: none, and that is the decision rather than the omission
ifritis off by design, so every rule watching it is permanently firing or permanently silenced — and it would need a pass into Winterfell that ADR-0014's "one log-only tripwire, zero passes, zero blocks" accounting does not have."After the main network is finished" now names issues
shivalayer-2 adjacent to a Kali VM.Not gates, said explicitly so it isn't assumed: #228 waits on this build's list of what a Hicks workstation reaches, and #229's exposure runs in the direction the range does not travel.
The runbook
Each of ADR-0014's four properties gets a command, and §9 proves the "a leak reports itself" claim by deliberately causing one and finding the block in Loki with a
172.30.30.xsource.Two traps it names:
ip_forward=1at daemon start, whatever the sysctl file says, and does it again every boot.Also in here
ADR-0014 gets a forward-pointer note;
network.md,security.mdandroadmap.mdcarry the subnet and the addresses. ADR-0017's Consequences record two things it found and did not decide:8007onifritwill admit nothing (ADR-0014 wrote one management rule for two hosts, and this one givesifritno PBS), and nothing in the estate knows whether the range is powered — there is no cheap alert shape for "this host is on when it should be off".make validatepasses.🤖 Generated with Claude Code