Skip to content

feat(tofu): build ragnarok on gandalf, and finish and prove it with a script - #1464

Merged
Gerrrt merged 6 commits into
mainfrom
tofu/ragnarok
Nov 12, 2026
Merged

Gerrrt merged 6 commits into
mainfrom
tofu/ragnarok

Conversation

@Gerrrt

@Gerrrt Gerrrt commented Nov 12, 2026

Copy link
Copy Markdown
Owner

What changed

The second half of #1443. ragnarok (130), the range's attack VM on gandalf, is now made by tofu/, and the script that made it now only finishes it and proves it. ADR-0116 records the decision and amends ADR-0076.

Built on #1463 (eden). This branch contains #1463's commit, because both change the guest module. Merge #1463 first; this diff then shrinks to its own commit.

  • tofu/providers.tf: provider "proxmox" { alias = "gandalf" }, reading var.gandalf_endpoint and var.gandalf_api_token. Those come from two new TF_VAR_gandalf_* lines in phoenix.env, so every existing runbook step that sources phoenix.env before a plan still works. TLS is verified.
  • tofu/modules/guest: a new extra_networks option adds NICs after net0, each with a static address, no gateway and the firewall flag off. pool_id becomes optional.
  • tofu/guests.tf: ragnarok as module.range_guest with the gandalf provider:
    • a clone of 902 on range, 4 cores and 8 GiB;
    • net0 on vmbr0 with DHCP and its MAC pinned;
    • net1 on vmbr1 at 172.30.30.10/24 with no gateway;
    • user kali with phoenix's key;
    • no pool, so no new grant on gandalf;
    • Secure Boot off, as on tpl-kali.
  • scripts/make-attack-vm.sh → scripts/finish-attack-vm.sh: it no longer clones or configures, and refuses to run unless 130 exists. It:
    • checks the /vms/130 grant;
    • reads the config back, now also checking net1's address;
    • starts the guest if it is stopped;
    • then, unchanged, writes 99-no-forward.conf and runs §8's third check over SSH, against the host key read through the guest agent.
  • Docs:
    • docs/adr/0116-…md, with a back-link note on ADR-0076, and README's count goes from 115 to 116 ADRs;
    • build-the-playground.md §5 is rewritten: grants, the two phoenix.env lines, plan, apply, then the script, and the once-only destroy of the script-built guest;
    • provision-lab-guests.md §1 says where the two lines come from;
    • the script's name is updated in architecture.md, build-the-lab-templates.md, scripts/README.md, packer/kali.pkr.hcl and tofu/README.md.

Why

Item 18 of the 2026-11-11 audit, with the owner's calls on #1443:

  • One state with an aliased provider, not a second tree.
  • tofu builds the VM, and the script proves it.
  • The sysctl file stays in the script. Putting it in cloud-init would need tofu to upload a snippet to gandalf over SSH, a second credential path for one file. The owner chose to keep it in the script.

One change from what I recorded on #1443: the two TF_VAR_gandalf_* lines live in phoenix.env, not gandalf.env. In gandalf.env, every existing tofu step in six runbooks would have to source both files, or it would stop at a prompt for the gandalf variables. The cost is the gandalf token in two mode-600 files on phoenix. ADR-0116 records this.

Refs #1443

Blast radius

  • ragnarok: destroyed once and remade from the pipeline. The range is disposable (ADR-0017). An import would have planned a replace anyway.

  • Every plan now needs gandalf up and the two phoenix.env lines present, even for a Saruman-only change. That is the accepted cost of one state. The alternative is -target with gandalf down.

  • The range's isolation: if the module wrote net1 wrongly, the script's read-back now fails on the address or a gateway before anything else runs. I tested that check with jq against a correct config and against one with a gateway.

  • Merging changes no host. Nothing happens until someone runs it on phoenix.

  • No change to network segmentation or firewall rules

  • No new port published to a VLAN that could not already reach the service

  • No credential added outside secrets/*.sops.yaml. The gandalf token, already on phoenix in gandalf.env, gets a second copy in phoenix.env, which is outside the repository as ADR-0074 decides.

Verification

  • Every .tf file under tofu/ parses with python-hcl2.

  • tofu fmt -check and tofu validate were not run here: this session has no tofu and no Docker. CI's Lint job runs both.

  • shellcheck passes on finish-attack-vm.sh.

  • python3 scripts/check_docs.py: OK, including the new back-link check and the 116 count. --self-test passes.

  • ./scripts/lint.sh: passed, 7 skipped (packer ×2, tofu ×3, ansible-lint and the editorconfig-checker container). ec was run directly on the changed files and passed. check-tracked-artefacts.sh: OK.

  • The real proof is on phoenix and gandalf:

    • plan -target='module.range_guest["ragnarok"]' adds one guest and nothing else;
    • finish-attack-vm.sh then passes;
    • a plain plan changes nothing on Saruman.
  • make validate was not run in full: no Docker here. lint, check_docs and the artefact check above were.

  • Deployed to the lab and confirmed working: not yet. build-the-playground.md §5 is the deploy.

  • Docs updated (docs/, service README, or docs/roadmap.md)


Generated by Claude Code

eden (141, BloodHound) was a hand `qm clone 901 141`, so tofu could not
rebuild it (#1443, audit finding 18). It is now in guests.tf, in a
`bloodhound` pool, with ADR-0081's shape: the OS disk on local-lvm without
the SSD flag, a 32 GiB data disk on large_data, a static 10.0.30.41 with its
gateway and nameserver, and garnet's key from alexander beside phoenix's.

The module gains os_disk_ssd, data_disks, ipv4 and dns_servers. Each
defaults to what every other guest has, so their plans do not change.

The hand-built eden is imported once. As with the domain's six, the plan
then replaces it, because a hand-built guest has no clone to read back.
ADR-0081 allows that: the graph is derived data and eden is not backed up.
The runbook says so, then destroys and applies, then continues from section
2, with section 11 for the new key.

Refs #1443

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2EweeKAdMf5A1aeFy1anN
… script

ADR-0116, amending ADR-0076. The range's attack VM was made by
scripts/make-attack-vm.sh, outside the pipeline (#1443, audit finding 18).

- providers.tf: an aliased provider for gandalf, from
  TF_VAR_gandalf_endpoint and TF_VAR_gandalf_api_token in phoenix.env, so
  every runbook step that sources phoenix.env before a plan still works.
- modules/guest: extra_networks, for NICs after net0 with a static address
  and no gateway; pool_id becomes optional.
- guests.tf: ragnarok (130) as module.range_guest on gandalf: a clone of 902
  on `range`, net0 vmbr0 DHCP with its MAC pinned, net1 vmbr1
  172.30.30.10/24 with no gateway, firewall flags off, user kali, no pool,
  Secure Boot off as tpl-kali's is.
- make-attack-vm.sh becomes finish-attack-vm.sh: it no longer clones or
  configures. It checks the /vms/130 grant, reads the config back, now
  including net1's address, starts the guest if it is stopped, then writes
  99-no-forward.conf and runs §8's third check over host-key-checked SSH,
  as before.

The script-built ragnarok is destroyed and remade, not imported: an import
would plan a replace anyway, and the range is disposable (ADR-0017).
build-the-playground.md §5 says so, and how.

Refs #1443

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2EweeKAdMf5A1aeFy1anN
Copilot AI balanced review requested due to automatic review settings November 12, 2026 00:12
…sends a migration to the import first

build-the-lab-domain.md step 5 destroyed with -target=module.guest, which
is every Saruman guest in the tree: garuda, the dotfiles VMs and, with
this PR, eden. That would take garuda's case notes and eden's graph with
the domain, against ADR-0081's reason for putting BloodHound off it. Steps
5 and 7 now target the six by name, and tofu/README.md and
provision-lab-guests.md §2 say so.

build-the-bloodhound-guest.md §1 put the first-build plan and apply before
the import subsection. Followed in order against the existing eden, the
apply fails on the clone into 141 after making the pool. A migration is now
sent to the import before any apply.

Refs #1443

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2EweeKAdMf5A1aeFy1anN

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The post-apply isolation instructions still reference the renamed scripts/make-attack-vm.sh, causing a missing-file error; update the path to scripts/finish-attack-vm.sh.

1 open issue
What changed in this PR

Moves ragnarok provisioning into the existing OpenTofu tree while retaining a separate script to finish configuration and verify range isolation. This builds on the included eden provisioning changes from #1463.

Changes:

  • Adds the aliased gandalf provider and guest-module support for additional network interfaces and optional pools.
  • Converts the attack-VM creation script into a post-apply configuration and isolation check.
  • Updates deployment runbooks and records the provisioning decision in ADR-0116.
File Description
tofu/​variables.tf Adds eden key and gandalf provider inputs.
tofu/​README.md Documents both newly declared guests.
tofu/​providers.tf Adds the TLS-verified gandalf provider alias.
tofu/​modules/​guest/​variables.tf Adds disk, networking and optional-pool inputs.
tofu/​modules/​guest/​main.tf Implements configurable disks and additional interfaces.
tofu/​identities.auto.tfvars.example Adds placeholder identities for both guests.
tofu/​guests.tf Declares eden and ragnarok.
scripts/​README.md Documents the renamed finishing script.
scripts/​finish-attack-vm.sh Replaces cloning with post-apply configuration and verification.
README.md Updates the ADR count.
packer/​kali.pkr.hcl Updates comments describing the provisioning flow.
docs/​runbooks/​provision-lab-guests.md Documents provider credentials and the BloodHound pool grant.
docs/​runbooks/​build-the-playground.md Documents ragnarok migration, apply and verification.
docs/​runbooks/​build-the-lab-templates.md Attributes range-interface provisioning to OpenTofu.
docs/​runbooks/​build-the-bloodhound-guest.md Moves eden creation and teardown into OpenTofu.
docs/​architecture.md Updates the attack VM’s provisioning description.
docs/​adr/​0116-build-the-range-attack-vm-with-opentofu-and-prove-it-with-a-script.md Records the shared-state and post-apply verification decision.
docs/​adr/​0076-provision-lab-guests-with-opentofu-and-encrypt-its-state-from-the-first-apply.md Adds an amendment link to ADR-0116.

Review effort: Balanced

Comment thread tofu/guests.tf Resolved
…med script

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2EweeKAdMf5A1aeFy1anN
# Conflicts:
#	tofu/README.md
#	tofu/identities.auto.tfvars.example
#	tofu/modules/guest/main.tf
#	tofu/modules/guest/variables.tf
@Gerrrt
Gerrrt merged commit cc4d28f into main Nov 12, 2026
8 checks passed
@Gerrrt
Gerrrt deleted the tofu/ragnarok branch November 12, 2026 00:59
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.

3 participants