Skip to content

docs(adr): ADR-0043 — keep the CA on prometheus, build phoenix as the deployment host (#436) - #505

Merged
Gerrrt merged 3 commits into
mainfrom
gerrrt/jumpbox-ca-placement-0370a9
Sep 18, 2026
Merged

Gerrrt merged 3 commits into
mainfrom
gerrrt/jumpbox-ca-placement-0370a9

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Sep 17, 2026

Copy link
Copy Markdown
Owner

What changed

  • ADR-0043 decides the question Build phoenix, the deployment host on ImaginationLAN (ADR-0043) #436 carried: the estate's CA key stays on prometheus, and phoenix is built as the deployment host — a guest on Saruman, 10.0.30.70, VMID 170, no stack, no Docker, no age key.
  • build-the-jumpbox.md, authored ahead of the build the way odin's was: the VM, the Kea reservation, the Proxmox user/role/token (pveum, privilege separation off, a role scoped to /vms and never PVEAdmin), the one host-firewall line, the Alloy agent from the Mac, verification, the build-day edits, and the hand-off to open-the-remote-path.md.
  • Registrations: a **Not built yet** row in docs/architecture.md, a planned-guest note in docs/network.md, an Automation bullet in docs/roadmap.md, README counts.
  • Marked notes on ADR-0014 (one rule superseded), ADR-0039 (one consequence amended) and ADR-0042 (a reopening condition fired and discharged), per ADR-0001 — text left as written.
  • Renumber, because main's CI is red without it: two ADRs landed as 0041 (docs(adr): ADR-0041 — the CRS326 runs RouterOS, keeps neo, and serves TLS #491, WireGuard to the lab jumpbox: terminate on VLAN 30, route it, and record what it costs #495). check_docs.py says renumber the one that landed second, so the remote-path ADR is now 0042, with every link and bare mention moved; README's counts were stale at 41/26 against 42/27 files and now say 43/28.
  • Comment-only edits in stacks/lab/compose.yaml, stacks/lab/.env.example, security.rules.yaml and scripts/gen-certs.sh; one sentence in build-the-soc-guest.md §7.

Why

#436. Three findings shaped the answer, and each is in the ADR rather than only here:

  1. The issue's CA premise is wrong. The estate root is pathlen:0 (gen-certs.sh); the tier's step-ca is its own root, not an intermediate beneath it — that is what ADR-0037 retracted. So the CA question was only ever where one key file sits, and the loss is bounded by a re-mint path that is already written down.
  2. The host that holds credentials for every other host — and, since ADR-0042, terminates the internet's only path in — must not also hold the key every other host trusts. That, plus ADR-0014's own description of VLAN 30, decides it before any disk comparison.
  3. The issue's "needs no new rule" was about morpheus. The Proxmox firewall on Saruman admits 8006 from Hicks only, and ADR-0039 relies on nothing on VLAN 30 reaching it; ADR-0042 saw the collision and left it to Build phoenix, the deployment host on ImaginationLAN (ADR-0043) #436. Driving the API from a VLAN 30 guest widens that rule by one address on one port, in /etc/pve. Accepted with its cost stated: source-address on a shared wire, spoofable by a guest that takes .70 while phoenix is off; the token is the control, the rule is the door, and ADR-0042's routed mode is what keeps a VPN peer from inheriting it.

Also found: certificates/ca-key.pem has no custody story at all. Named in the ADR with what the answer is not, and carried by #496.

Blast radius

  • Segmentation: no change on morpheus. One line in Saruman's own host.fw on build day, 10.0.30.70 → 8006 only — supersedes that rule of ADR-0014, recorded on ADR-0014, ADR-0039 and ADR-0042. firewall-claims.yaml cannot see it.
  • Ports: none published. The lab's ports: blocks stay commented; the comments now name both clients that will open them.
  • Credentials: none in this PR. The token is created at build time and lives on phoenix at mode 600, outside the repository — a stated deviation from the issue's step 2, and the ADR says why it is forced.

Verification

  • python3 scripts/check_docs.py and ./scripts/lint.sh pass after the rebase and renumber; make validate passed before them on the same content, and is re-running.
  • Deployed to the lab — no: the guest is not built. prometheus cannot reach VLAN 30; the runbook is for a person at the Mac. The architecture row keeps **Not built yet** until then, and check_docs.py enforces the inversion.
  • Docs updated

Not Closes: #436 is done on the day the guest exists and §8 of the runbook is followed.

🤖 Generated with Claude Code

Gerrrt and others added 3 commits September 17, 2026 23:39
… deployment host (#436)

#436 asked for a deployment jumpbox on ImaginationLAN and a decision on
where the estate's certificate authority lives. Reading the root first
changed the question: the issue's premise — that the tier's step-ca is
already an intermediate beneath the estate's CA, "so the tree is shaped
for it either way" — is the sentence ADR-0037 retracted. The estate's
root is pathlen:0, nothing can sit beneath it, and the only thing to
decide was where one key file sits.

It stays on prometheus. The argument that decides it is not the disk:
phoenix is by design the host that holds credentials for every other
host — a Proxmox API token, an SSH key — and, since ADR-0042, the host
the estate's only inbound path from the internet terminates on. The key
every other host trusts must not sit beside either. VLAN 30 is the
segment ADR-0014 built to hold attackers, at an address the range is
scoped to reach, and the through-the-Mac transfer the move was meant to
remove inverts and doubles instead. Cold storage, trinity, and a
pathlen:1 re-mint are each counted and each loses.

Two things the issue did not count, both recorded:

- The Proxmox firewall on Saruman admits 8006 from Hicks only (ADR-0014),
  and ADR-0039 relies on nothing on VLAN 30 reaching it. ADR-0042 saw
  the collision and left it to #436. A guest that drives the API has to
  be admitted, so the build widens that rule by one address on one
  port, on /etc/pve and nowhere on morpheus. That is a change to a
  decision, so ADR-0014's rule is superseded rather than noted, ADR-0039's
  consequence is amended, and ADR-0042 gets the forward pointer for the
  reopening condition it named — discharged, because its own routed mode
  is what stops the pass admitting every VPN peer.
- certificates/ca-key.pem has no backup or custody story at all, unlike
  the age key (ADR-0024) and the tier's root. Named in the ADR with what
  the answer is not — not SOPS-in-git, not a passphrase — and carried
  by #496.

The runbook authors the guest ahead of the build, the way odin was:
phoenix, 10.0.30.70, VMID 170, no stack, no Docker, no age key, and the
CAUTION that the CA key does not come here. The Alloy agent is
deploy-agent.sh's native runtime with --monitoring-host 10.0.30.40, the
flag's first use; the lab's Prometheus and Loki ports are opened by
whichever of odin and phoenix is built first, and the comments in
stacks/lab and build-the-soc-guest.md §7 stop assuming which. The token
lives on phoenix at mode 600 outside the repository — a stated
deviation from the issue's "credential into secrets/", forced because
check_sops_rules.py proves rules against the stack directories and a
phoenix rule would match nothing.

Also here, because CI on main is red without it: two ADRs landed as
0041 (#491's CRS326 and #495's remote path), and check_docs.py says to
renumber the one that landed second. The remote path is now ADR-0042,
with every link and bare mention moved — the runbook, restore-the-
firewall.md, security.rules.yaml's comments, and the notes on ADR-0008,
-0011, -0014 and -0022 — and README's counts, stale at 41/26 against 42
and 27 files, now say 43 and 28.

Comment-only in stacks/lab/compose.yaml, .env.example, the Loki rule
file and scripts/gen-certs.sh — no service definition changes, nothing
is recreated.

Verified: python3 scripts/check_docs.py and ./scripts/lint.sh after the
rebase and renumber; make validate before them, on the same content.

Refs #436 #496

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#503 renumbered the remote-path ADR to 0042 on main, the same move this
branch made, so the two agree everywhere; the one conflict was README's
counts, and the merged tree has 43 ADRs and 28 runbooks.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Five more PRs landed, two of them runbooks (#497, #507), and main's
README count had not moved with them. The merged tree has 43 ADRs and
30 runbooks; README now says so, which check_docs.py requires.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Gerrrt
Gerrrt merged commit 18c61fa into main Sep 18, 2026
4 checks passed
@Gerrrt
Gerrrt deleted the gerrrt/jumpbox-ca-placement-0370a9 branch September 18, 2026 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant