diff --git a/SECURITY.md b/SECURITY.md index dfb559ef..c91892de 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -47,7 +47,7 @@ is a very different thing from an overlooked one. Full detail in | Alertmanager webhook URL and the MokerLink SNMP community, in a local Claude Code session transcript | Found 2026-08-20 by a value-level sweep of the host. Redacted in place; mode 600, never committed or synced. The webhook topic was rotated — on the public ntfy instance the topic name *is* the credential, there is nothing to revoke — and delivery re-verified end to end. The switch community deliberately was not: rotating it means the `neo` residual above all over again. **That reasoning is spent, and this value now retires with the hardware.** The overwrite it was afraid of was done on 2026-09-12 and persisted, and the MokerLink is being replaced by a switch commissioned with SNMP v2c off entirely — so what retires this community is not a fresh community but the absence of one. The thing that must not be carried across is any value the MokerLink held, passphrases included. **Retired 2026-11-05**, when the CRS326 replaced it with no v2c community at all and v3 passphrases generated fresh ([#444](https://github.com/Gerrrt/HomeLab/issues/444)); the switch section above has the proof. | | Pre-purge objects still served by GitHub after the history rewrite | The 2026-08-19 rewrite (`021d2b6`) removed both secrets above from every *reachable* commit, but GitHub still serves the orphaned objects by SHA. Verified 2026-08-26: `647d90a`, `21afcad`, `efb2632` and `ee3d443` all still resolve through the API, and the tree at `21afcad` still lists `certificates/Gandalf.Gondor.Lab/ca-key.pem` and `cert-key.pem`. Garbage collection requested from GitHub Support on 2026-08-26 — **pending**; this is the [purge runbook](docs/runbooks/purge-git-history.md)'s *Afterwards* step, and it is the last one outstanding. The repository has no forks and a network count of 0, so nothing else is perpetuating them. Both credentials were rotated *before* the rewrite, so this changes nothing about their status: the old keys and the old community remain superseded and must still be treated as public. Re-check with `gh api repos/Gerrrt/HomeLab/commits/647d90a --jq .sha` — a `404` means GitHub has collected them. | | Alertmanager published on `0.0.0.0`, letting anyone who could reach it silence an alert | Fixed 2026-08-30 — 9093 now binds to `127.0.0.1` ([#70](https://github.com/Gerrrt/HomeLab/issues/70), [ADR-0012](docs/adr/0012-publish-only-ports-with-an-off-host-consumer.md)). This was the sharpest of the three because a silence switches off monitoring and the record of it lives in the system being switched off. Nothing off-host ever used the port: silences are reached through Grafana, which proxies Alertmanager over the compose network behind a login, so closing it cost no capability. | -| Prometheus and Loki published on `0.0.0.0` with no authentication | Fixed — [#182](https://github.com/Gerrrt/HomeLab/issues/182), [ADR-0067](docs/adr/0067-authenticate-the-ingest-ports-with-a-token-per-client.md). Until then anything that could route to `10.0.99.20:9090` or `:3100` could read every metric and log line, inject metrics through the remote-write receiver, and delete log ranges through Loki's delete API, with firewall default-deny as the whole control. Prometheus and Loki now publish on `127.0.0.1` only. The host's address on both ports is an ingest proxy that serves a push to one token per agent (`oracle`, `trinity`, `Saruman`), a query to one reader token (Glance, Home Assistant, the deploy script's arrival check), and the admin and delete APIs to nobody. `IngestAuthNotEnforced` pages if the published address ever answers a token-less request with anything but the proxy's 401. The tokens no longer cross the wire in cleartext: both ports serve TLS under the estate CA, and every client verifies it ([#764](https://github.com/Gerrrt/HomeLab/issues/764), [ADR-0086](docs/adr/0086-serve-the-ingest-ports-over-tls-under-the-estate-ca.md)). **One thing remains, deliberately:** loopback is unauthenticated, because a local user on the monitoring host already holds the SOPS key. The firewall still narrows who can try: Winterfell (99) itself and `Saruman`'s one pass from `10.0.30.110`. | +| Prometheus and Loki published on `0.0.0.0` with no authentication | Fixed — [#182](https://github.com/Gerrrt/HomeLab/issues/182), [ADR-0067](docs/adr/0067-authenticate-the-ingest-ports-with-a-token-per-client.md). Until then anything that could route to `10.0.99.20:9090` or `:3100` could read every metric and log line, inject metrics through the remote-write receiver, and delete log ranges through Loki's delete API, with firewall default-deny as the whole control. Prometheus and Loki now publish on `127.0.0.1` only. The host's address on both ports is an ingest proxy that serves a push to one token per agent (`oracle`, `trinity`, `Saruman`), a query to one reader token (Glance, Home Assistant, the deploy script's arrival check), and the admin and delete APIs to nobody. `IngestAuthNotEnforced` pages if the published address ever answers a token-less request with anything but the proxy's 401. The tokens no longer cross the wire in cleartext: both ports serve TLS under the estate CA, and every client verifies it ([#764](https://github.com/Gerrrt/HomeLab/issues/764), [ADR-0086](docs/adr/0086-serve-the-ingest-ports-over-tls-under-the-estate-ca.md)). **One thing remains, deliberately:** loopback is unauthenticated, because a local user on the monitoring host already holds the SOPS key. The firewall still narrows who can try: Winterfell (99) itself and `Saruman`'s one pass from `10.0.30.20`. | | The lab's Prometheus and Loki published to VLAN 30 with no authentication | **Open until the rollout lands.** The fix is authored ([#834](https://github.com/Gerrrt/HomeLab/issues/834)), and none of the four hosts converges, so it is live only once `stacks/lab/README.md`'s ordered rollout has run and its three `curl`s return 401. `alexander` published both to the segment that exists to hold attackers (#436, 2026-09). Anything on it could `POST /-/quit`, forge series and delete log ranges in the lab's evidence store. Once rolled out, they are on `127.0.0.1`, behind `stacks/lab/Caddyfile`: one token each for `odin`, `phoenix` and `fenrir`, and a reader token, all the lab's own and never the estate's. **Two residuals.** The tokens cross VLAN 30 as plain HTTP, where ARP spoofing is expected (#764's question). And nothing pages if the proxy is bypassed, because the lab has no Alertmanager (ADR-0020, [#858](https://github.com/Gerrrt/HomeLab/issues/858)). | | The monitoring host's disk and swap are unencrypted | **Accepted residual, not a fix in progress** — see below. | | Loki's log volume has no size ceiling | **Accepted residual, not a fix in progress** — see below. Prometheus has a byte ceiling since [#184](https://github.com/Gerrrt/HomeLab/issues/184); Loki has no size-based retention to set, only time. `HostDiskWillFillIn24h` is the control. | diff --git a/docs/adr/0007-defensive-estate-and-offensive-range.md b/docs/adr/0007-defensive-estate-and-offensive-range.md index f3cf3a55..7cb3b825 100644 --- a/docs/adr/0007-defensive-estate-and-offensive-range.md +++ b/docs/adr/0007-defensive-estate-and-offensive-range.md @@ -89,6 +89,13 @@ > filesystems are crosses, read by the hypervisor through the guest agent and > sent over `Saruman`'s existing pass. No path is opened, and nothing else a > guest produces crosses. The text here is left as written, per ADR-0001. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md b/docs/adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md index 029863b8..0855ea4a 100644 --- a/docs/adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md +++ b/docs/adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md @@ -54,6 +54,13 @@ > ImaginationLAN of the shape ADR-0053 gives `golem` > ([ADR-0072](0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md)). > Same kind of pass, host- and port-scoped, and the table is not edited. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md b/docs/adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md index 2a5bf2c6..6673d39b 100644 --- a/docs/adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md +++ b/docs/adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md @@ -39,6 +39,13 @@ > ([#451](https://github.com/Gerrrt/HomeLab/issues/451)). The collector still > runs from the range when there is one. The text here is left as written, per > ADR-0001. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0020-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md b/docs/adr/0020-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md index e3bcb61b..3f102eed 100644 --- a/docs/adr/0020-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md +++ b/docs/adr/0020-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md @@ -2,6 +2,14 @@ **Status:** Accepted · 2026-09 +> [!NOTE] +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. + ## Context [ADR-0007](0007-defensive-estate-and-offensive-range.md) gave `Saruman` the diff --git a/docs/adr/0028-let-guest-liveness-cross-but-not-guest-telemetry.md b/docs/adr/0028-let-guest-liveness-cross-but-not-guest-telemetry.md index c5c09f33..c51e505e 100644 --- a/docs/adr/0028-let-guest-liveness-cross-but-not-guest-telemetry.md +++ b/docs/adr/0028-let-guest-liveness-cross-but-not-guest-telemetry.md @@ -22,6 +22,13 @@ > `qm guest exec`. This closes the half the first consequence below leaves > open, for those services only. Everything else in "What services it runs, > and their health" stays in the lab. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0039-decline-proxmox-clustering-while-ifrit-is-the-range.md b/docs/adr/0039-decline-proxmox-clustering-while-ifrit-is-the-range.md index b522f495..b0c70979 100644 --- a/docs/adr/0039-decline-proxmox-clustering-while-ifrit-is-the-range.md +++ b/docs/adr/0039-decline-proxmox-clustering-while-ifrit-is-the-range.md @@ -25,6 +25,13 @@ > converted. The argument here is unchanged: the mirror is on `Saruman`'s own > bridge, so what removes Zeek's view is moving the domain guests off it, not > a cluster. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0049-shut-down-on-the-ups-from-a-nut-server-on-the-firewall.md b/docs/adr/0049-shut-down-on-the-ups-from-a-nut-server-on-the-firewall.md index 0c66aec8..e7d6d1a2 100644 --- a/docs/adr/0049-shut-down-on-the-ups-from-a-nut-server-on-the-firewall.md +++ b/docs/adr/0049-shut-down-on-the-ups-from-a-nut-server-on-the-firewall.md @@ -47,6 +47,13 @@ power source [#413](https://github.com/Gerrrt/HomeLab/issues/413) owed > the intent of reusing it, and it is now true on the card as well as here. > A user of NUT's own remains the way to separate them. The decision is > unchanged. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0068-mirror-the-lab-bridge-to-zeek-with-tc-not-open-vswitch.md b/docs/adr/0068-mirror-the-lab-bridge-to-zeek-with-tc-not-open-vswitch.md index 690adffd..061526f8 100644 --- a/docs/adr/0068-mirror-the-lab-bridge-to-zeek-with-tc-not-open-vswitch.md +++ b/docs/adr/0068-mirror-the-lab-bridge-to-zeek-with-tc-not-open-vswitch.md @@ -12,6 +12,13 @@ > [ADR-0069](0069-vendor-the-ja4-scripts-into-the-sensor-stack-rather-than-build-an-image.md) > decides. [`stacks/sensor/README.md`](../../stacks/sensor/README.md) has the > query that reads them. The text below is left as written, per ADR-0001. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md b/docs/adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md index 6dae441f..5820c6e8 100644 --- a/docs/adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md +++ b/docs/adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md @@ -17,6 +17,13 @@ on ImaginationLAN rather than Hicks or Winterfell; decides > §2b). `phoenix` holds read-only `PVEAuditor` on the store, so it cannot > write to it either. What this costs is up to a day between a replacement > and the page. The text below is left as written, per ADR-0001. +> +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. ## Context diff --git a/docs/adr/0079-tag-on-demand-guests-and-leave-them-out-of-the-stopped-guest-alert.md b/docs/adr/0079-tag-on-demand-guests-and-leave-them-out-of-the-stopped-guest-alert.md index 67ba29bc..60baa108 100644 --- a/docs/adr/0079-tag-on-demand-guests-and-leave-them-out-of-the-stopped-guest-alert.md +++ b/docs/adr/0079-tag-on-demand-guests-and-leave-them-out-of-the-stopped-guest-alert.md @@ -2,6 +2,14 @@ **Status:** Accepted · 2026-10 +> [!NOTE] +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. + ## Context `HypervisorGuestStopped` (`stacks/observability/prometheus/rules/host.rules.yaml`) diff --git a/docs/adr/0084-read-management-certificate-expiry-from-inside-each-segment.md b/docs/adr/0084-read-management-certificate-expiry-from-inside-each-segment.md index a248f3f3..6857dca2 100644 --- a/docs/adr/0084-read-management-certificate-expiry-from-inside-each-segment.md +++ b/docs/adr/0084-read-management-certificate-expiry-from-inside-each-segment.md @@ -2,6 +2,14 @@ **Status:** Accepted · 2026-10 +> [!NOTE] +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. + ## Context Four management consoles serve self-signed certificates that nothing watched diff --git a/docs/adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md b/docs/adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md index 91352885..0b19fce1 100644 --- a/docs/adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md +++ b/docs/adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md @@ -4,6 +4,14 @@ [ADR-0014](0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md) and [ADR-0017](0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md) +> [!NOTE] +> **`Saruman`'s address.** `Saruman` moved from `10.0.30.110` to +> **`10.0.30.20`** on 2026-11-07, as +> [`build-the-playground.md`](../runbooks/build-the-playground.md) §3 has it +> ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). Each rule, export, +> alias and command below that names `.110` now names `.20`, and the decision +> is unchanged. The text below is left as written, per ADR-0001. + ## Context [ADR-0014](0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md) diff --git a/docs/architecture.md b/docs/architecture.md index cb1e855e..003818b6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -185,7 +185,7 @@ hole from the monitoring VLAN into the monitored one. | Host | VLAN | Stack | Contents | | --- | --- | --- | --- | | `prometheus` (10.0.99.20) | 🔴 99 | [`stacks/observability`](../stacks/observability) | Prometheus, Alertmanager, Loki, Grafana, snmp-exporter, blackbox-exporter, speedtest-tracker, docker-socket-proxy, Alloy | -| `Saruman` (10.0.30.110) | 🟢 30 | *(none — and none intended)* | Proxmox VE 9, hosting twenty-one guests: `alexander`, built 2026-09-05 ([#262](https://github.com/Gerrrt/HomeLab/issues/262)); `phoenix`, built 2026-09-20 ([#436](https://github.com/Gerrrt/HomeLab/issues/436)); `odin`, built 2026-09-27 for the security tooling (ADR-0030); `fenrir`, built 2026-09-30 as the Zeek sensor ([#437](https://github.com/Gerrrt/HomeLab/issues/437)); `golem`, built 2026-10-03 as the backup server ([#485](https://github.com/Gerrrt/HomeLab/issues/485)); `eden`, built 2026-10-07 as the BloodHound guest ([#451](https://github.com/Gerrrt/HomeLab/issues/451)), off between sessions; `garuda`, built 2026-10-10 as the analyst workstation ([#921](https://github.com/Gerrrt/HomeLab/issues/921)); and ADR-0029's six-machine domain, built by hand 2026-09-24 to 2026-09-25 ([#414](https://github.com/Gerrrt/HomeLab/issues/414)), of which `carbuncle` and `siren` run per session; and all eight of [ADR-0090](adr/0090-test-the-dotfiles-os-layers-on-on-demand-saruman-guests.md)'s on-demand dotfiles test VMs, `dot-debian`, `dot-fedora` and `dot-windows`, built 2026-10-08, and `dot-opensuse`, `dot-arch`, `dot-alpine`, `dot-gentoo` and `dot-nixos`, built 2026-10-09 ([#920](https://github.com/Gerrrt/HomeLab/issues/920)). Alloy agent (native package). It runs no compose stack by decision, not by omission: Docker would rewrite the iptables its own firewall relies on ([ADR-0014](adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md)), which is why the agent here is the native package and why `stacks/lab` runs in a guest | +| `Saruman` (10.0.30.20) | 🟢 30 | *(none — and none intended)* | Proxmox VE 9, hosting twenty-one guests: `alexander`, built 2026-09-05 ([#262](https://github.com/Gerrrt/HomeLab/issues/262)); `phoenix`, built 2026-09-20 ([#436](https://github.com/Gerrrt/HomeLab/issues/436)); `odin`, built 2026-09-27 for the security tooling (ADR-0030); `fenrir`, built 2026-09-30 as the Zeek sensor ([#437](https://github.com/Gerrrt/HomeLab/issues/437)); `golem`, built 2026-10-03 as the backup server ([#485](https://github.com/Gerrrt/HomeLab/issues/485)); `eden`, built 2026-10-07 as the BloodHound guest ([#451](https://github.com/Gerrrt/HomeLab/issues/451)), off between sessions; `garuda`, built 2026-10-10 as the analyst workstation ([#921](https://github.com/Gerrrt/HomeLab/issues/921)); and ADR-0029's six-machine domain, built by hand 2026-09-24 to 2026-09-25 ([#414](https://github.com/Gerrrt/HomeLab/issues/414)), of which `carbuncle` and `siren` run per session; and all eight of [ADR-0090](adr/0090-test-the-dotfiles-os-layers-on-on-demand-saruman-guests.md)'s on-demand dotfiles test VMs, `dot-debian`, `dot-fedora` and `dot-windows`, built 2026-10-08, and `dot-opensuse`, `dot-arch`, `dot-alpine`, `dot-gentoo` and `dot-nixos`, built 2026-10-09 ([#920](https://github.com/Gerrrt/HomeLab/issues/920)). Alloy agent (native package). It runs no compose stack by decision, not by omission: Docker would rewrite the iptables its own firewall relies on ([ADR-0014](adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md)), which is why the agent here is the native package and why `stacks/lab` runs in a guest | | `gandalf` (10.0.30.30) | 🟢 30 | *(none — and none intended)* | Proxmox VE 9, the range host, installed 2026-11-07 ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). No guests yet. Guest disks go on the ZFS pool `range`, targets go on the portless `vmbr1`, and it is off between sessions ([ADR-0017](adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md), [ADR-0106](adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md)) | | `alexander` (10.0.30.40) | 🟢 30 | [`stacks/lab`](../stacks/lab) | Prometheus, Loki, Grafana, Alloy and its Docker socket proxy, and Caddy as the ingest proxy in front of the first two ([#834](https://github.com/Gerrrt/HomeLab/issues/834)) — the lab's own observability, which never remote-writes to VLAN 99 ([ADR-0007](adr/0007-defensive-estate-and-offensive-range.md), [ADR-0020](adr/0020-run-the-lab-stack-in-a-guest-with-its-own-prometheus.md)). A guest on `Saruman`, not the hypervisor; Alloy agent (Docker) | | `odin` (10.0.30.60) | 🟢 30 | [`stacks/soc`](../stacks/soc) | Wazuh (indexer, manager, dashboard), Velociraptor and Alloy: the security half of ADR-0007, placed by [ADR-0030](adr/0030-give-the-security-tooling-its-own-guest-and-its-own-stack.md) on a second guest on `Saruman` because `alexander`'s 8 GiB cannot hold both. Built 2026-09-27 by [`build-the-soc-guest.md`](runbooks/build-the-soc-guest.md), the stack authored and CI-validated ahead of the guest the way `stacks/lab` was ahead of `alexander` ([#266](https://github.com/Gerrrt/HomeLab/issues/266), [#267](https://github.com/Gerrrt/HomeLab/issues/267)); the six domain machines enrol as agents by GPO, which is the day those two issues close. Its Alloy pushes to `alexander`, never to VLAN 99, and the indexer's health is the one series that crosses into the lab's Prometheus. Alloy agent (Docker) | diff --git a/docs/changelog.md b/docs/changelog.md index b4452750..f2496529 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -19,6 +19,29 @@ docstring gives: it is a record, not a claim about now. ## 2026-11-07 +- **`Saruman` moved from `10.0.30.110` to `10.0.30.20`** + ([#421](https://github.com/Gerrrt/HomeLab/issues/421), + [`build-the-playground.md`](runbooks/build-the-playground.md) §3). The + hypervisor no longer sits inside ImaginationLAN's `.100–.200` DHCP pool, the + pool the attack VM will lease from. The `.20` rules and export were added + first and the `.110` ones removed only after every path had been checked + over `.20`: the Alloy push, NUT, the ISO store's NFS mount, `8006` from + Hicks (and refused from `morpheus`), and `phoenix`'s API call with TLS + verified. The same 10 guests stayed running throughout. The new pfSense + rules kept their copy descriptions, which end in `(.20)`. + + §3 had listed two things that name the address. A grep of the repository + found six more on the hosts: the NUT pass, the host firewall's + `local_network` alias, the API certificate's names, `phoenix.env`, the + Mac's `known_hosts` and its SSH config. If the alias had stayed at `.110`, + the next DHCP client to lease `.110` would have joined Proxmox's + `management` set. The cutover also turned up a trap: `ifreload` starts + with `#!/usr/bin/env python3`, and root's dotfiles put mise's Python first, + so a plain `ifreload -a` failed with `ModuleNotFoundError` and changed + nothing. Under `env -i` with the system PATH it worked. Boot was never at + risk, because `networking.service` runs with systemd's PATH. §3 now + records all of this, and the twelve ADRs that quote `.110` carry a note. + - **`DiskIoErrors` fired on a healthy disk, and its ATA pattern now needs the kernel's prefix.** `oracle` sent it at 22:33 and 23:28 UTC, each time clearing 15 minutes later. The disk was fine: no kernel I/O error, SMART diff --git a/docs/diagrams/current/network.png b/docs/diagrams/current/network.png index 2bd1d701..4b77ac61 100644 Binary files a/docs/diagrams/current/network.png and b/docs/diagrams/current/network.png differ diff --git a/docs/diagrams/current/network.svg b/docs/diagrams/current/network.svg index 107c6052..8fcb3c30 100644 --- a/docs/diagrams/current/network.svg +++ b/docs/diagrams/current/network.svg @@ -495,7 +495,7 @@ Saruman -.110 +.20 Proxmox VE · hypervisor Alloy → prometheus 9090/3100 @@ -658,7 +658,7 @@ gandalf -Proxmox VE · range host, no guests yet · Saruman → .20 +Proxmox VE · range host, no guests yet .30 diff --git a/docs/hardware.md b/docs/hardware.md index a0dcfe2b..b2633ec5 100644 --- a/docs/hardware.md +++ b/docs/hardware.md @@ -320,7 +320,7 @@ version component in either table. | `Saruman` | `shiva` — HPE iLO 4, firmware 2.82 | `10.0.30.10` | iLO Advanced licensed. Dedicated network port. DHCP with a reservation. Hardened 2026-09-09 per [ADR-0033](adr/0033-keep-the-ilo-on-the-lab-segment.md): IPMI-over-LAN, SSH and iLO Federation off; HTTPS, the remote console and SNMP stay on; the account's credential is shared with nothing else; the security log was read for a baseline. Polled over SNMPv3 authPriv (SHA, AES, user `prometheus`) since 2026-09-20 per [ADR-0036](adr/0036-poll-the-ilo-and-the-ups-card-over-snmpv3-and-keep-the-firewall-on-bsnmpd.md), with *SNMPv1 Request* off — it answers no community at all | The BMC and the host it manages carry different names and different addresses: -`shiva` is the iLO, `Saruman` is the hypervisor at `10.0.30.110`. Earlier +`shiva` is the iLO, `Saruman` is the hypervisor at `10.0.30.20`. Earlier revisions of this repository treated `shiva` as the hypervisor itself. ## Accessories diff --git a/docs/network.md b/docs/network.md index 48716a86..5c9ef7f8 100644 --- a/docs/network.md +++ b/docs/network.md @@ -324,7 +324,7 @@ though not the only one — ImaginationLAN has two host-scoped passes to [#764](https://github.com/Gerrrt/HomeLab/issues/764). So the firewall is no longer the only thing between a Hicks workstation and the metric and log stores. - `10.0.30.110` keeps its explicit pass for `Saruman`'s Alloy agent, which now + `10.0.30.20` keeps its explicit pass for `Saruman`'s Alloy agent, which now presents `Saruman`'s own token. - **ImaginationLAN is reached entire**, on every protocol and port, by decision: [ADR-0031](adr/0031-narrow-hicks-to-a-named-list-on-winterfell-and-leave-the-lab-open.md) @@ -421,7 +421,7 @@ Televisions and consoles. Internet only. share through it. With 4533 and 13378, seven exist from Hicks and Winterfell. **Two more are from ImaginationLAN, and both exist, which makes nine.** `Allow NFS from Saruman to smaug`, - `10.0.30.110 → 10.0.40.30:2049`, lets the hypervisor mount `erebor/iso` as + `10.0.30.20 → 10.0.40.30:2049`, lets the hypervisor mount `erebor/iso` as its ISO store for Packer ([ADR-0072](adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md), [#446](https://github.com/Gerrrt/HomeLab/issues/446)). It was created on @@ -486,7 +486,7 @@ Where things get broken on purpose. | --- | --- | --- | --- | --- | --- | --- | | morpheus | `10.0.30.1` | `02:26:26:xx:xx:xx` | HP ProDesk 600 G4 Mini | FreeBSD 16.0 | Rack U15 | Firewall | | shiva | `10.0.30.10` | `94:57:a5:xx:xx:xx` | HPE iLO 4 (DL360 Gen9 BMC)[^Shiva] | iLO 2.82 | Rack U3 | Out-of-band management | -| Saruman | `10.0.30.110` | `14:02:ec:xx:xx:xx` | HPE ProLiant DL360 Gen9[^Shiva] | Proxmox VE 9 | Rack U3 | Hypervisor | +| Saruman | `10.0.30.20` | `14:02:ec:xx:xx:xx` | HPE ProLiant DL360 Gen9[^Shiva] | Proxmox VE 9 | Rack U3 | Hypervisor | | gandalf | `10.0.30.30` | `90:2e:16:xx:xx:xx` | Lenovo ThinkCentre M80q | Proxmox VE 9 | Rack U18 | Range hypervisor, off between sessions. Its Intel AMT, the name `ifrit`, is disabled ([ADR-0106](adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md)) | | alexander | `10.0.30.40` | `bc:24:11:xx:xx:xx` | KVM guest on `Saruman` | Ubuntu 26.04 LTS | Rack U3 | Lab observability | | phoenix | `10.0.30.70` | `bc:24:11:xx:xx:xx` | KVM guest on `Saruman` | Ubuntu 26.04 LTS | Rack U3 | Deployment host | @@ -680,7 +680,7 @@ Where things get broken on purpose. address set out of the node's own subnet, so `10.0.30.0/24` reached the API, SSH, VNC, the SPICE proxy and the migration range through a rule nobody wrote, underneath a `DROP` policy that looked closed. The `local_network` - alias is pinned to `10.0.30.110` so that the four rules above are the only + alias is pinned to `10.0.30.20` so that the four rules above are the only way in. Hicks reaches `8006`, `8007` and `22`; `phoenix` reaches `8006`; nothing else on this segment reaches the hypervisor at all. - `fenrir` is at `10.0.30.90`, the next free decade, built 2026-09-30 as the @@ -695,23 +695,22 @@ Where things get broken on purpose. path `fenrir` needs is intra-segment — its Alloy pushes to `alexander` — so it adds no firewall rule. - `Saruman` runs an Alloy agent and is the one host on this segment with a path - into Winterfell: a single pass, `10.0.30.110 → 10.0.99.20` on 9090 and 3100 + into Winterfell: a single pass, `10.0.30.20 → 10.0.99.20` on 9090 and 3100 TCP, unlogged and above the ADR-0014 tripwire. The hypervisor's own telemetry only; guests get no such rule (ADR-0007, as amended by #88). Past the rule, the ingest proxy wants `Saruman`'s agent token (#182), so the pass lets the host try and the token is what gets it served. The push is TLS under the estate CA (#764), so the token is not readable at the firewall that forwards it. -- **`Saruman` is the one fixed address that sits inside a DHCP pool.** Every - other static in the estate lives below `.100`; this one is at `.110`, and the - ImaginationLAN pool runs `.100–.200`. Until 2026-08-30 there was no - reservation for it either, so Kea could have leased the same address to - another device. There is one now, and it holds *because* the server is Kea: - `reservations-in-subnet` is true and `reservations-out-of-pool` is unset, so - reservations are consulted on every allocation. Under ISC dhcpd, whose - binaries are still on the box, the same reservation would not reliably - protect an in-pool address. Moving `Saruman` below `.100` is the fix that - does not depend on that. +- **`Saruman` is at `.20`, below the pool, like every other static.** Until + 2026-11-07 it was at `.110`, the one fixed address in the estate inside a + DHCP pool (ImaginationLAN's runs `.100–.200`). Only Kea's + reservation kept that address from being leased to another device, and only + because Kea consults reservations on every allocation. ISC dhcpd, whose + binaries are still on the box, would not have. The move, in + [`build-the-playground.md`](runbooks/build-the-playground.md) §3 + ([#421](https://github.com/Gerrrt/HomeLab/issues/421)), took that dependency + away before the attack VM starts taking leases from the same pool. - A second hypervisor, `gandalf`, carries the attack tooling and the deliberately-vulnerable targets. It is installed (Proxmox VE 9, 2026-11-07) and has no guests yet. It sits on this segment single-homed, on @@ -728,8 +727,7 @@ Where things get broken on purpose. anywhere, which is what makes a `172.30.30.x` source in a firewall block a leak reporting itself. The attack VM takes a lease from the pool like any other guest. The build is - [`build-the-playground.md`](runbooks/build-the-playground.md), and `Saruman` - moves to `10.0.30.20` as part of it. + [`build-the-playground.md`](runbooks/build-the-playground.md). - **The WireGuard peers live on `172.31.0.0/24`, and it is routed rather than translated** diff --git a/docs/roadmap.md b/docs/roadmap.md index 8b3e9124..2b6180e2 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -283,7 +283,8 @@ Gated on the domain, on a household observation, or on something to publish. `gandalf`.** Gated on #414 and the SOC: an attack VM pointed at an uninstrumented estate teaches nothing. Both are met. The host is installed as of 2026-11-07: Proxmox VE 9, the `range` pool and both bridges, the - runbook's §1 and §2. Next is §3, moving `Saruman` to `.20`, then §4–§9. + runbook's §1 and §2. §3 ran the same night: `Saruman` is at `.20`. Next is + §4, closing `gandalf`'s management plane, then §5–§9. The name is [ADR-0106](adr/0106-name-the-range-host-gandalf-and-leave-ifrit-to-its-disabled-amt.md)'s. → [runbook](runbooks/build-the-playground.md) diff --git a/docs/runbooks/add-monitored-device.md b/docs/runbooks/add-monitored-device.md index 744b775f..5592ce8a 100644 --- a/docs/runbooks/add-monitored-device.md +++ b/docs/runbooks/add-monitored-device.md @@ -183,7 +183,7 @@ on the **ImaginationLAN** interface: | Action | Source | Destination | Ports | Log | | --- | --- | --- | --- | --- | -| pass, TCP | `10.0.30.110` | `10.0.99.20` | 9090, 3100 | **off** | +| pass, TCP | `10.0.30.20` | `10.0.99.20` | 9090, 3100 | **off** | Place it **above** the ADR-0014 tripwire (`vlan30 net → Internal_Segments`, pass + log). Below it, every push from `Saruman` is a logged pass on the diff --git a/docs/runbooks/build-the-jumpbox.md b/docs/runbooks/build-the-jumpbox.md index 5c6e5875..15bd2f53 100644 --- a/docs/runbooks/build-the-jumpbox.md +++ b/docs/runbooks/build-the-jumpbox.md @@ -254,7 +254,7 @@ IN ACCEPT -source 10.0.30.70 -p tcp -dport 8006 -log nolog install -d -m 700 ~/.config/proxmox umask 077 cat > ~/.config/proxmox/phoenix.env <<'EOT' -PROXMOX_URL=https://10.0.30.110:8006/api2/json +PROXMOX_URL=https://10.0.30.20:8006/api2/json PROXMOX_TOKEN_ID=phoenix@pve!builder PROXMOX_TOKEN_SECRET= EOT diff --git a/docs/runbooks/build-the-lab-domain.md b/docs/runbooks/build-the-lab-domain.md index 9ffe7790..e328f905 100644 --- a/docs/runbooks/build-the-lab-domain.md +++ b/docs/runbooks/build-the-lab-domain.md @@ -673,7 +673,7 @@ ImaginationLAN*, mapping each guest's MAC to its address. > anyway. The reservation is there so the address is recorded where a reader > looks for it, and so the protection does not depend on the pool never moving — > the reasoning [`build-the-playground.md`](build-the-playground.md) already -> writes out, and the mistake `10.0.30.110` is still an apology for. +> writes out, and the mistake `10.0.30.110` was an apology for until `Saruman` moved to `.20` on 2026-11-07. > > Leave the scope's DNS setting alone. Reserved clients receive the interface > address like everyone else; the DC addresses are set inside Windows, which is diff --git a/docs/runbooks/build-the-lab-templates.md b/docs/runbooks/build-the-lab-templates.md index a4f2568f..241906e4 100644 --- a/docs/runbooks/build-the-lab-templates.md +++ b/docs/runbooks/build-the-lab-templates.md @@ -124,7 +124,7 @@ pveum acl list | grep phoenix **Then make `phoenix` verify the API's certificate.** Packer and `scripts/packer-smoke.sh` both check TLS, because the token travels in a header, -and a guest on VLAN 30 answering for `10.0.30.110` would collect it otherwise. +and a guest on VLAN 30 answering for `10.0.30.20` would collect it otherwise. The attack VM shares that segment. **First, check what `Saruman` actually serves.** Its API certificate should @@ -136,11 +136,11 @@ and naming only `10.0.0.208`, the host's address before it moved. On `Saruman`: ```bash -echo | openssl s_client -connect 10.0.30.110:8006 2>/dev/null | openssl x509 -noout -issuer -ext subjectAltName +echo | openssl s_client -connect 10.0.30.20:8006 2>/dev/null | openssl x509 -noout -issuer -ext subjectAltName ``` The issuer must be `PVE Cluster Manager CA`, and the names must include -`10.0.30.110`. If either is wrong, keep a copy of any custom certificate, +`10.0.30.20`. If either is wrong, keep a copy of any custom certificate, regenerate the node's own from `/etc/hosts` (check the host's line there first, because this reads it), and stop serving the custom one. Restarting `pveproxy` drops open web-UI sessions and touches no guest: @@ -285,7 +285,7 @@ as trustworthy as that list, so the list is written here, once, with care. `phoenix` cannot: ```bash - make install-agent-collectors AGENT=root@10.0.30.110 ARGS='--only iso-store-state' + make install-agent-collectors AGENT=root@10.0.30.20 ARGS='--only iso-store-state' ``` The installer starts one run straight away. That run reads every ISO, so diff --git a/docs/runbooks/build-the-nas.md b/docs/runbooks/build-the-nas.md index 6acf6f1a..4a83612d 100644 --- a/docs/runbooks/build-the-nas.md +++ b/docs/runbooks/build-the-nas.md @@ -250,7 +250,7 @@ first row here on ImaginationLAN. `golem`'s `2049` pass is ImaginationLAN's othe | Winterfell (99) | `tcp` `10.0.99.20` → `10.0.40.30` port `22` | `Allow SSH to smaug` | **above** *Block access to CasaBonita* | | Hicks (50) | `tcp` `vlan50 net` → `10.0.40.30` port `13378` | `Allow 13378 to smaug` | **above** *Block access to CasaBonita* — §6.5 | | Hicks (50) | `tcp` `vlan50 net` → `10.0.40.30` port `445` | `Allow SMB to smaug` | **above** *Block access to CasaBonita* — §5 | -| ImaginationLAN (30) | `tcp` `10.0.30.110` → `10.0.40.30` port `2049` | `Allow NFS from Saruman to smaug` | **above** *Block access to CasaBonita* on `igc0.30` — §5b | +| ImaginationLAN (30) | `tcp` `10.0.30.20` → `10.0.40.30` port `2049` | `Allow NFS from Saruman to smaug` | **above** *Block access to CasaBonita* on `igc0.30` — §5b | **Four, where [ADR-0016](../adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md) wrote three.** The Hicks pass is one rule per port rather than one rule @@ -679,7 +679,9 @@ there. Have the TrueNAS UI and console shell, the pfSense UI, a shell on > rather than the UI: `erebor/iso`, lz4, atime off, dedup off, POSIX ACLs, > a 500 GiB quota, no snapshot task. Record size reads `1M`. > `pippin` is uid `3003`, gid `3002`. The share admits `10.0.30.110` alone, -> with maproot `pippin`/`pippin`. +> with maproot `pippin`/`pippin`. (`Saruman` moved to `10.0.30.20` on +> 2026-11-07, and the share and the rule moved with it: +> [`build-the-playground.md`](build-the-playground.md) §3.) > > `Allow NFS from Saruman to smaug` was read from `morpheus` with > `pfctl -sr -vv`. On `igc0.30` it printed directly above *Block access to @@ -709,8 +711,8 @@ there. Have the TrueNAS UI and console shell, the pfSense UI, a shell on | | | | --- | --- | | Dataset | `erebor/iso`: not snapshotted, not backed up, 500 GiB quota | -| Export | NFSv4, to `10.0.30.110` alone, maproot `pippin` | -| Rule | ImaginationLAN (30): `10.0.30.110 → 10.0.40.30:2049/tcp`, `Allow NFS from Saruman to smaug` | +| Export | NFSv4, to `10.0.30.20` alone, maproot `pippin` | +| Rule | ImaginationLAN (30): `10.0.30.20 → 10.0.40.30:2049/tcp`, `Allow NFS from Saruman to smaug` | | On `Saruman` | `fstab` mount at `/mnt/smaug-iso`, Proxmox `dir` storage `smaug-iso`, content `iso`, `is_mountpoint` | 1. **The dataset.** It exists (above). Finish it from the console shell, and @@ -744,7 +746,7 @@ there. Have the TrueNAS UI and console shell, the pfSense UI, a shell on | --- | --- | | Path | `/mnt/erebor/iso` | | Description | `ISO store for Saruman (ADR-0072)` | - | Authorized hosts | `10.0.30.110` | + | Authorized hosts | `10.0.30.20` | | Maproot User / Group | `pippin` / `pippin` | | Mapall | empty | @@ -753,7 +755,7 @@ there. Have the TrueNAS UI and console shell, the pfSense UI, a shell on and a squash to `nobody` refuses the write. 5. **The rule**, in the pfSense UI on **ImaginationLAN (30)**: pass, `tcp`, - source `10.0.30.110`, destination `10.0.40.30` port `2049`, description + source `10.0.30.20`, destination `10.0.40.30` port `2049`, description exactly **`Allow NFS from Saruman to smaug`**. Place it **directly above** the first block on that interface whose destination covers `10.0.40.0/24`. `build-the-backup-guest.md` §4 finds that block and names diff --git a/docs/runbooks/build-the-playground.md b/docs/runbooks/build-the-playground.md index bd8d6248..4a3406a6 100644 --- a/docs/runbooks/build-the-playground.md +++ b/docs/runbooks/build-the-playground.md @@ -43,7 +43,7 @@ decision and on this runbook. The machine is ready too. `gandalf`, a refurbished ThinkCentre M80q, was bought on 2026-10-05 and has its 32 GB and its SATA range disk ([ADR-0096](../adr/0096-fit-ifrits-range-disk-as-a-sata-ssd-in-the-tinys-bay.md)) -fitted. **§1 and §2 were done on 2026-11-07** +fitted. **§1, §2 and §3 were done on 2026-11-07** ([#421](https://github.com/Gerrrt/HomeLab/issues/421)), and the parts as fitted are in [`hardware.md`](../hardware.md). The node is named `gandalf`, and `ifrit` names its disabled Intel AMT @@ -231,50 +231,130 @@ protection does not depend on the pool never moving. ## 3. Move `Saruman` to `10.0.30.20` -ADR-0014 requires this "at the same time", and the reason is in -[`network.md`](../network.md): `10.0.30.110` is the one fixed address in the -estate that sits *inside* a DHCP pool. It holds today only because the server is -Kea and consults reservations on every allocation; under the ISC dhcpd binaries -still on the box it would not. - -This is the riskiest step in the build. Nothing scrapes `Saruman` by address — -its Alloy agent pushes — so the blast radius is one firewall rule and the -documents: - -1. Change the static on `Saruman` and its Kea reservation to `10.0.30.20`. -2. Edit the pass rule on the **ImaginationLAN** interface from - `10.0.30.110 → 10.0.99.20` to `10.0.30.20 → 10.0.99.20`, ports 9090 and - 3100, **logging off**, still above the ADR-0014 tripwire. It is documented in - [`add-monitored-device.md`](add-monitored-device.md); logging it makes the - agent's every push a hit on the tripwire. -3. Confirm the agent is still arriving before you move on — a broken rule looks - exactly like a host that is down. The hypervisor should still be in this - list: +> **Ran on 2026-11-07** ([#421](https://github.com/Gerrrt/HomeLab/issues/421)). +> `Saruman` is at `10.0.30.20`, and every path below was checked over the +> new address. The steps are kept as the record of how it was done, and they +> have the shape to follow if the hypervisor's address ever moves again. + +ADR-0014 requires this "at the same time". At `.110`, `Saruman` was the one +fixed address in the estate inside a DHCP pool, and the attack VM leases from +that pool. Kea's reservation kept the address safe only because Kea consults +reservations on every allocation. + +The runbook first listed two things that name the address: the Alloy pass +and the ISO store's pass and export. **There were six.** The NUT pass, the +host firewall's `local_network` alias, the API certificate, `phoenix`'s +endpoint and the Mac's `known_hosts` all named it as well. The alias is the +one with a security cost. Left at `.110`, it would put whatever leased +`.110` next into Proxmox's `management` set, as §4 explains, and that could +be the attack VM. + +**Add the `.20` paths before you move the address, and remove the `.110` +ones only after it moves.** That way nothing breaks at the cutover. + +1. **Before you start.** Check that `.20` is free: from `morpheus`, it should + not answer a ping and should have no ARP entry. Then, on `Saruman`, copy + `/etc/network/interfaces`, `/etc/hosts` and + `/etc/pve/firewall/cluster.fw` to `/root/`. Record a baseline: + - the `host` list in Loki (step 6); + - `ssh admin@10.0.99.1 'upsc -c mjolnir@localhost'`; + - `pvesm status --storage smaug-iso`; + - the running guests in `qm list`. +2. **The ImaginationLAN rules on `morpheus`.** Copy each of these rules + with source `10.0.30.20`, and place each copy directly under its original: + - the Alloy passes to `10.0.99.20`, on 9090 and 3100, with **logging + off**. They are documented in + [`add-monitored-device.md`](add-monitored-device.md). If you log them, + every push the agent makes counts as a tripwire hit; + - `Allow NUT from Saruman` to `10.0.30.1:3493`; + - `Allow NFS from Saruman to smaug` to `10.0.40.30:2049`. + + Every copy must sit above its block rule. `pfctl` matches the first rule + that fits, so check the order with + `pfctl -sr | grep -n "on igc0.30"`. On `smaug`, add `10.0.30.20` to the + `erebor/iso` share's authorized hosts. On `morpheus`, change `Saruman`'s + Kea reservation to `10.0.30.20`. +3. **Change the address.** Edit `/etc/hosts`, + `address` under `vmbr0` in `/etc/network/interfaces`, and + `local_network` in `/etc/pve/firewall/cluster.fw`. `/etc/pve` is the + cluster filesystem, so write the file with `cp`, not `sed -i`. Then: + + ```bash + env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin /usr/sbin/ifreload -a + ``` + + **The `env -i` is needed.** `ifreload` starts with + `#!/usr/bin/env python3`, and root's dotfiles put mise's Python first on + the PATH. That Python has neither Debian's `python3-systemd` nor + ifupdown2's library path, so a plain `ifreload -a` dies with + `ModuleNotFoundError` and changes nothing. Boot is not affected, because + `networking.service` runs with systemd's default PATH. + `ifquery vmbr0`, under the same `env -i`, reads the new stanza back + without applying it. + + **Any SSH session, and any Proxmox web Shell, connected to `.110` drops + at this point.** Restarting `pveproxy` also drops the web Shell. Use the + iLO console, or reconnect to `.20`. The host key is unchanged, and the + client says so: it reports the key as already known under another + address. +4. **Reissue the API certificate** from the new `/etc/hosts`: + `pvecm updatecerts --force && systemctl restart pveproxy`. `phoenix` + verifies TLS, so the certificate's names must include `10.0.30.20`: + + ```bash + echo | openssl s_client -connect 10.0.30.20:8006 2>/dev/null | openssl x509 -noout -issuer -ext subjectAltName + ``` + + Then check three more things: + - `qm list` shows the same running guests as before; + - after a minute, `tc filter show dev vmbr0 egress` shows the Zeek + mirror again; + - `pve-firewall localnet` reports `local_network: 10.0.30.20`. +5. **Change the clients.** In `~/.config/proxmox/phoenix.env` on + `phoenix`, change `PROXMOX_URL` and `PROXMOX_VE_ENDPOINT`, then check + the API over the new endpoint. Rerunning + [`provision-lab-guests.md`](provision-lab-guests.md) §1 will not do it, + because on a configured `phoenix` that block stops at + `already present`. This one only reads the file, prints only the status + code, and does not pass `-k`: + + ```bash + ( set -a; . ~/.config/proxmox/phoenix.env; set +a + printf 'header = "Authorization: PVEAPIToken=%s"\n' "${PROXMOX_VE_API_TOKEN}" \ + | curl -K - -fsS -o /dev/null -w 'API %{http_code}\n' "${PROXMOX_VE_ENDPOINT}api2/json/version" ) + ``` + + It must print `API 200`. A certificate that doesn't name `10.0.30.20` + fails here instead. On the Mac, run + `ssh-keygen -R 10.0.30.110` and update the host's entry in the SSH + config. Nothing on VLAN 99 can SSH to `Saruman`, so `prometheus` has no + `known_hosts` entry to update. +6. **Check every path over `.20`.** This list should still include the + hypervisor: ```bash curl -s http://localhost:3100/loki/api/v1/label/host/values | jq -r '.data[]' ``` - `host` is `constants.hostname` as the agent sees it, so read the value out of - the list rather than asserting one — see + `host` is `constants.hostname` as the agent sees it, so read the value + out of the list rather than assert one. See [`ship-firewall-logs.md`](ship-firewall-logs.md) for why that label means - something different on relayed logs. - **If the ISO store exists** ([ADR-0072](../adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md), - [`build-the-nas.md`](build-the-nas.md) §5b), it is a second rule and an - export, and both name `.110`: - - Edit `Allow NFS from Saruman to smaug` on ImaginationLAN to source - `10.0.30.20`. Its position does not change. - - On `smaug`, change the `erebor/iso` NFS share's authorized host to - `10.0.30.20`. - - On `Saruman`, `umount /mnt/smaug-iso && mount /mnt/smaug-iso`, then - `pvesm status --storage smaug-iso` must say `active`. -4. Update the address where the documents carry it: `hardware.md`, - `network.md`, `architecture.md`, `add-monitored-device.md`, - `replace-the-smart-storage-battery.md`, and the ADRs that quote the rule — - those get a note rather than an edit, per - [ADR-0001](../adr/0001-record-architecture-decisions.md). `roadmap.md` also - carries `.110`, in a Done entry, and that one is **left alone**: it records - what was true when that work landed. + something different on relayed logs. Also check: + - `pfctl -ss` shows states from `.20` to `10.0.99.20`; + - `upsc -c mjolnir@localhost` lists `10.0.30.20`; + - after `umount /mnt/smaug-iso && mount /mnt/smaug-iso`, `smaug-iso` is + `active`; + - `8006` answers from Hicks and is refused from `morpheus`. + + A broken rule looks exactly like a host that is down. +7. **Remove the `.110` paths.** Delete the four `.110` rules and remove + `.110` from the share, then run step 6 again. The copies were left with + `(.20)` at the end of their descriptions. +8. **The documents.** Wherever a current-state document names the address, + update it. ADRs that quote it get a note rather than an edit, per + [ADR-0001](../adr/0001-record-architecture-decisions.md). Dated records + are left alone: the changelog, `roadmap.md`'s Done entries and dated log + rows. --- @@ -336,7 +416,7 @@ than narrowed here; ADR-0017's Consequences record that it admits nothing. **This is the step that makes the difference between ADR-0014 being true and merely being written down.** Proxmox detects a "local network" from the node's own address, puts that CIDR in a `management` IP set, and gives the set five -RETURN rules in the host input chain. `Saruman` is `10.0.30.110/24`, so the +RETURN rules in the host input chain. `Saruman` is `10.0.30.20/24`, so the detected value was **the entire lab segment** — and with `policy_in: DROP` in place, every address on VLAN 30 still reached the GUI, SSH, the VNC range, the SPICE proxy and the migration range. ADR-0014's three rules were redundant @@ -357,7 +437,7 @@ enable: 1 policy_in: ACCEPT [ALIASES] -local_network 10.0.30.110 +local_network 10.0.30.20 ``` Use the node's own address, and check that `localnet` now reports it as user @@ -422,7 +502,7 @@ segment and is not Hicks, so it is the right prober; a second probe to a host that should answer keeps a broken `nc` from reading as a closed port: ```bash -ssh admin@10.0.99.1 'nc -z -w 3 10.0.30.110 8006; echo "8006 exit=$?"; nc -z -w 3 10.0.30.40 3100; echo "control exit=$?"' +ssh admin@10.0.99.1 'nc -z -w 3 10.0.30.20 8006; echo "8006 exit=$?"; nc -z -w 3 10.0.30.40 3100; echo "control exit=$?"' ``` Expect `8006 exit=1` and `control exit=0`. Then confirm the hypervisor's own @@ -444,7 +524,7 @@ skips this section — page nothing on their own. Install the collector that reads it ([#576](https://github.com/Gerrrt/HomeLab/issues/576)): ```bash -make install-agent-collectors AGENT=root@10.0.30.110 ARGS='--only pve-firewall-state' +make install-agent-collectors AGENT=root@10.0.30.20 ARGS='--only pve-firewall-state' ``` and read it back on `prometheus`: diff --git a/docs/runbooks/build-the-sensor-guest.md b/docs/runbooks/build-the-sensor-guest.md index b50dd119..c181682c 100644 --- a/docs/runbooks/build-the-sensor-guest.md +++ b/docs/runbooks/build-the-sensor-guest.md @@ -93,7 +93,7 @@ ls /sys/class/net/vmbr1/brif ifquery --check -a ``` -- `vmbr0` must still show `10.0.30.110/24`. +- `vmbr0` must still show `10.0.30.20/24`. - `vmbr1` must show **no address at all**, not even an `fe80::`. - `addr_gen_mode` must read `1`. - `brif` stays empty until §2's VM starts. @@ -306,18 +306,18 @@ Both ship through the agent-collector installer. **Run it from a checkout on Hicks**, which can SSH to `Saruman` as root. Run from the checkout on `Saruman` itself, the installer SSHes to -`root@10.0.30.110` from `10.0.30.110`. That login is from VLAN 30, so it fires +`root@10.0.30.20` from `10.0.30.20`. That login is from VLAN 30, so it fires the critical `SshLoginFromUnexpectedSubnet`, which clears ten minutes later. That is what happened on the first build. Also, `Saruman`'s own host key is not -in root's `known_hosts` there: compare `ssh-keyscan -t ed25519 10.0.30.110` +in root's `known_hosts` there: compare `ssh-keyscan -t ed25519 10.0.30.20` against `/etc/ssh/ssh_host_ed25519_key.pub` before adding it. ```bash -make install-agent-collectors AGENT=root@10.0.30.110 ARGS='--only zeek-mirror' +make install-agent-collectors AGENT=root@10.0.30.20 ARGS='--only zeek-mirror' ``` ```bash -make install-agent-collectors AGENT=root@10.0.30.110 ARGS='--only zeek-mirror-state' +make install-agent-collectors AGENT=root@10.0.30.20 ARGS='--only zeek-mirror-state' ``` The first installs `scripts/zeek-mirror.sh` as `/usr/local/bin/homelab-zeek-mirror` diff --git a/docs/runbooks/fit-the-saruman-ssds.md b/docs/runbooks/fit-the-saruman-ssds.md index 84f85fae..73ce6067 100644 --- a/docs/runbooks/fit-the-saruman-ssds.md +++ b/docs/runbooks/fit-the-saruman-ssds.md @@ -101,7 +101,7 @@ both arrays — not assumed from either.** > still the *Before* column of every table here. `shiva` is the iLO, not the hypervisor. The host behind it is `Saruman` at -`10.0.30.110`; see [`../hardware.md`](../hardware.md). Both are on VLAN 30. +`10.0.30.20`; see [`../hardware.md`](../hardware.md). Both are on VLAN 30. ## Where each step runs, and why it matters @@ -112,7 +112,7 @@ it is. | Steps | Host | Who runs them | | --- | --- | --- | | 0, 1, 10, 11 | `prometheus`, `10.0.99.20`, VLAN 99 | the monitoring host — Prometheus and Alertmanager are here | -| 2–9 | `Saruman`, `10.0.30.110`, VLAN 30 | **you, from the Mac.** Nothing on VLAN 99 can reach this host | +| 2–9 | `Saruman`, `10.0.30.20`, VLAN 30 | **you, from the Mac.** Nothing on VLAN 99 can reach this host | Run the `Saruman` half inside `tmux`. A dropped SSH session during step 9's disk move leaves a half-mirrored disk, and that is a worse afternoon than a @@ -679,7 +679,7 @@ what [`build-the-lab-guest.md`](build-the-lab-guest.md) already assumes. > read instead by `scripts/collect-thin-pool-state.sh`, from `lvs` every ten > minutes on the host, into `ThinPoolNearlyFull` and `ThinPoolWillFillIn24h` > ([#538](https://github.com/Gerrrt/HomeLab/issues/538)). Installing it is -> `make install-agent-collectors AGENT=root@10.0.30.110 +> `make install-agent-collectors AGENT=root@10.0.30.20 > ARGS='--only thin-pool-state'`, and the check is > `homelab_thin_pool_data_percent{host="Saruman"}` — one series per pool. diff --git a/docs/runbooks/move-into-the-32u-rack.md b/docs/runbooks/move-into-the-32u-rack.md index be37b820..c3c06927 100644 --- a/docs/runbooks/move-into-the-32u-rack.md +++ b/docs/runbooks/move-into-the-32u-rack.md @@ -331,7 +331,7 @@ hours, so it will not fire inside a normal window. | Order | Host | How | You should see | | --- | --- | --- | --- | -| 1 | `Saruman` and its guests | The Proxmox UI at `https://10.0.30.110:8006` from the Mac on Hicks (`10.0.30.20` once [`build-the-playground.md`](build-the-playground.md) §3 has run), *node → Shutdown*. It stops its guests first | The iLO at `10.0.30.10` shows the server off | +| 1 | `Saruman` and its guests | The Proxmox UI at `https://10.0.30.20:8006` from the Mac on Hicks, *node → Shutdown*. It stops its guests first | The iLO at `10.0.30.10` shows the server off | | 2 | `smaug` | The TrueNAS UI, *Power → Shut Down* | The tower's power light goes out. `erebor` is exported on the way down | | 3 | `trinity` | `ssh rabbit@10.0.99.40 sudo systemctl poweroff` | The ProDesk's light goes out. **AdGuard goes with it**: from here, the house cannot resolve outside names | | 4 | `morpheus` | The pfSense console on the KVM, option *Halt system* | The house loses internet, DNS and DHCP | diff --git a/docs/runbooks/provision-lab-guests.md b/docs/runbooks/provision-lab-guests.md index 3cf5a7a8..d4de7552 100644 --- a/docs/runbooks/provision-lab-guests.md +++ b/docs/runbooks/provision-lab-guests.md @@ -86,7 +86,7 @@ if grep -qE '^(PROXMOX_VE_|TF_VAR_state_passphrase)' "$E"; then echo "already present" elif cp -p "$E" "$E.bak-445"; then ( set -a; . "$E"; set +a; umask 077 - printf 'PROXMOX_VE_ENDPOINT=https://10.0.30.110:8006/\nPROXMOX_VE_API_TOKEN=%s=%s\nTF_VAR_state_passphrase=%s\n' \ + printf 'PROXMOX_VE_ENDPOINT=https://10.0.30.20:8006/\nPROXMOX_VE_API_TOKEN=%s=%s\nTF_VAR_state_passphrase=%s\n' \ "$PROXMOX_TOKEN_ID" "$PROXMOX_TOKEN_SECRET" "$(openssl rand -base64 32)" >> "$E" ) chmod 600 "$E"; grep -o '^[A-Za-z_]*=' "$E" ( set -a; . "$E"; set +a; echo "passphrase length ${#TF_VAR_state_passphrase}" diff --git a/docs/runbooks/replace-the-smart-storage-battery.md b/docs/runbooks/replace-the-smart-storage-battery.md index cf4606f8..b36a7dc5 100644 --- a/docs/runbooks/replace-the-smart-storage-battery.md +++ b/docs/runbooks/replace-the-smart-storage-battery.md @@ -28,7 +28,7 @@ lid comes off — not after.** > end. `shiva` is the iLO, not the hypervisor. The host behind it is `Saruman` at -`10.0.30.110`; see [`../hardware.md`](../hardware.md). The pack is internal to +`10.0.30.20`; see [`../hardware.md`](../hardware.md). The pack is internal to the ProLiant DL360 Gen9 and is not hot-swappable, so fitting it means taking `Saruman` down. diff --git a/docs/runbooks/schedule-maintenance.md b/docs/runbooks/schedule-maintenance.md index 12e4ca8f..1ddbd549 100644 --- a/docs/runbooks/schedule-maintenance.md +++ b/docs/runbooks/schedule-maintenance.md @@ -242,7 +242,7 @@ The estate's `GuestServiceUnhealthy` warns after fifteen minutes warns when an agent stops running the check, and `GuestServiceStateStale` when the collector stops writing. It requires `qm`, so it installs on the hypervisor only, from the Mac, because VLAN 99 cannot reach VLAN 30 on port 22: -`make install-agent-collectors AGENT=root@10.0.30.110 ARGS='--only guest-service-state'`. +`make install-agent-collectors AGENT=root@10.0.30.20 ARGS='--only guest-service-state'`. **`drift-check` is the collector that belongs to another repository.** `Gerrrt/Lemmiwinks/.claude/tools/drift-check` reads the wiki's machine-checkable diff --git a/docs/runbooks/shut-down-on-the-ups.md b/docs/runbooks/shut-down-on-the-ups.md index 9bc9d5d2..963561e0 100644 --- a/docs/runbooks/shut-down-on-the-ups.md +++ b/docs/runbooks/shut-down-on-the-ups.md @@ -405,7 +405,7 @@ the existing *Allow internet* rule. | Action | Pass | | Interface | ImaginationLAN | | Protocol | TCP | -| Source | Single host or alias, `10.0.30.110` | +| Source | Single host or alias, `10.0.30.20` | | Destination | Single host or alias, `10.0.30.1` | | Destination port range | From `3493` to `3493` | | Description | `Allow NUT from Saruman` | @@ -553,7 +553,7 @@ waits on its logged-in monitors in step 5: ssh admin@10.0.99.1 'upsc -c mjolnir@localhost' ``` -It should list `10.0.30.110` and `::1`. The firewall's own monitor connects +It should list `10.0.30.20` and `::1`. The firewall's own monitor connects over IPv6 loopback. ### 3.6 Read the guest shutdown policy @@ -728,7 +728,7 @@ there this loop would read `---` before anything halted. ```bash while :; do - nc -z -G 2 -w 2 10.0.30.110 22 >/dev/null 2>&1 && r=up || r='---' + nc -z -G 2 -w 2 10.0.30.20 22 >/dev/null 2>&1 && r=up || r='---' printf '%s saruman=%s\n' "$(date -u +%H:%M:%S)" "$r" sleep 2 done | tee ~/ups-fsd-saruman.log diff --git a/docs/security.md b/docs/security.md index 8333e1a1..7cb5f143 100644 --- a/docs/security.md +++ b/docs/security.md @@ -355,7 +355,7 @@ televisions' `bilbo` and the workstations' credential are revoked apart. The same pass puts an SMB login prompt in front of everything on Hicks, the corporate laptop included, as `443` already puts the TrueNAS one. **One more is from ImaginationLAN, and it exists.** -`10.0.30.110 → 10.0.40.30:2049`, `Allow NFS from Saruman to smaug`, lets the +`10.0.30.20 → 10.0.40.30:2049`, `Allow NFS from Saruman to smaug`, lets the hypervisor mount `erebor/iso`, the ISO store Packer builds templates from ([ADR-0072](adr/0072-put-the-iso-store-on-smaug-over-nfs-to-saruman-alone.md)). It was created on 2026-10-01. `Saruman` mounts the share through it, and @@ -514,7 +514,7 @@ and write the metric and log stores, and delete logs, without a credential. That is exactly the failure ADR-0002 predicted when it recorded that "a compromised workstation reaches Winterfell". The firewall narrowed who "anything" was: since 2026-09-02 Hicks reaches `10.0.99.20` on `3000` only, -and `10.0.30.110` on ImaginationLAN has one explicit pass for `Saruman`'s +and `10.0.30.20` on ImaginationLAN has one explicit pass for `Saruman`'s agent. But a control that depends on one un-reviewed rule ordering is not authentication. diff --git a/packer/variables.pkr.hcl b/packer/variables.pkr.hcl index a7c4638c..5102bdff 100644 --- a/packer/variables.pkr.hcl +++ b/packer/variables.pkr.hcl @@ -28,7 +28,7 @@ variable "proxmox_token_secret" { # Verified. Saruman's API certificate is signed by the cluster's own CA # (/etc/pve/pve-root-ca.pem), which build-the-lab-templates.md §2 installs in # phoenix's trust store. Skipping verification would hand the token to any -# guest on VLAN 30 that answered for 10.0.30.110, and the attack VM shares +# guest on VLAN 30 that answered for 10.0.30.20, and the attack VM shares # that segment. Set true only to debug, never to build. variable "proxmox_insecure_skip_tls_verify" { type = bool diff --git a/scripts/collect-iso-store-state.sh b/scripts/collect-iso-store-state.sh index d31de79e..00999166 100755 --- a/scripts/collect-iso-store-state.sh +++ b/scripts/collect-iso-store-state.sh @@ -3,7 +3,7 @@ # Whether the ISOs on `smaug-iso` are the ones this repository expects (#440, # ADR-0072). # -# THE GAP. The ISO store is an NFSv4 export of `erebor/iso` to `10.0.30.110` +# THE GAP. The ISO store is an NFSv4 export of `erebor/iso` to `10.0.30.20` # alone, and NFS with sec=sys authenticates by address. Anything on VLAN 30 # that took `Saruman`'s address could replace an installer, and every template # Packer built from it afterwards would carry the change. ADR-0072 named the @@ -45,7 +45,7 @@ set -uo pipefail # placed on the share, from `sha256sum` on `Saruman`, cross-checked against # the vendor's published hash where one exists (build-the-lab-templates.md # §2b). Changing it means re-running `make install-agent-collectors -# AGENT=root@10.0.30.110 ARGS='--only iso-store-state'`, because the host has +# AGENT=root@10.0.30.20 ARGS='--only iso-store-state'`, because the host has # no checkout of this repository. EXPECTED="$(cat <<'LIST' # Ubuntu 26.04.1 live server. releases.ubuntu.com SHA256SUMS, with a good diff --git a/scripts/collect-pve-firewall-state.sh b/scripts/collect-pve-firewall-state.sh index 478945c5..83fed5bf 100755 --- a/scripts/collect-pve-firewall-state.sh +++ b/scripts/collect-pve-firewall-state.sh @@ -9,7 +9,7 @@ # and the ways it goes off again are ordinary: a `pve-firewall stop` while # debugging a guest that cannot reach the API, an upgrade that rewrites # /etc/pve/firewall, or a rebuild that skips §4 the way the first build did. -# The check that would notice, `nc -z 10.0.30.110 8006` from off the segment, is +# The check that would notice, `nc -z 10.0.30.20 8006` from off the segment, is # something a person does once. # # WHY RULE COUNTS AS WELL AS THE SWITCH. "Enabled with zero rules" and @@ -134,7 +134,7 @@ enable: 1 policy_in: ACCEPT [ALIASES] -local_network 10.0.30.110" +local_network 10.0.30.20" check "a disabled rule is not counted" count_rules 1 \ "[RULES] |IN ACCEPT -source 10.0.50.0/24 -p tcp -dport 8006 @@ -158,7 +158,7 @@ enable: 1 policy_in: ACCEPT [ALIASES] -local_network 10.0.30.110" +local_network 10.0.30.20" check "REJECT refuses too" parse_policy_drop 1 \ "[OPTIONS] policy_in: REJECT" diff --git a/scripts/zeek-mirror.sh b/scripts/zeek-mirror.sh index 54c1c4cf..f45d8d35 100755 --- a/scripts/zeek-mirror.sh +++ b/scripts/zeek-mirror.sh @@ -6,7 +6,7 @@ # WHY tc AND NOT OPEN VSWITCH. #437 was filed saying port mirroring needs OVS. # It does not: a Linux bridge port is a netdev, and a `clsact` qdisc with a # `matchall` filter and a `mirred` action copies whatever enters it to another -# netdev. Converting `vmbr0` would have meant moving 10.0.30.110 — the +# netdev. Converting `vmbr0` would have meant moving the hypervisor's address — the # management plane ADR-0014's host firewall guards — onto an OVSIntPort, and # putting a bridge that speaks VLAN tags under the one segment built to hold an # attacker. ADR-0068 records the comparison. diff --git a/stacks/media/README.md b/stacks/media/README.md index 3b470c83..3060d86c 100644 --- a/stacks/media/README.md +++ b/stacks/media/README.md @@ -97,7 +97,7 @@ every television would have to trust, and a second thing to be down. | Prometheus, on `22` | The fourth rule — `10.0.99.20 → 10.0.40.30:22`, inert until [`build-the-nas.md`] §6.2 switches SSH on for the backup pull, as `frodo` with one key and read access to `erebor/apps` ([ADR-0045]) | | A Hicks phone, on `4533` | `50 → 10.0.40.30:4533`, `Allow 4533 to smaug`, for Navidrome — **created 2026-09-22**, ahead of the service and of the 13378 pass, so it is the fifth that exists; 445 is the sixth, and 13378 the seventh (§6.6) | | A Hicks workstation, on `445` | `50 → 10.0.40.30:445`, `Allow SMB to smaug`, to mount the `media` share as `samwise` — **created 2026-09-23** by [`build-the-nas.md`] §5's *Workstations* steps, and mounted from a Hicks workstation ([ADR-0051]) | -| `Saruman`, on `2049` | `10.0.30.110 → 10.0.40.30:2049`, `Allow NFS from Saruman to smaug`, to mount `erebor/iso` as the ISO store. Not the media stack's, and listed because it is a way into this host. **Created 2026-10-01** by [`build-the-nas.md`] §5b, and mounted by `Saruman` ([ADR-0072]) | +| `Saruman`, on `2049` | `10.0.30.20 → 10.0.40.30:2049`, `Allow NFS from Saruman to smaug`, to mount `erebor/iso` as the ISO store. Not the media stack's, and listed because it is a way into this host. **Created 2026-10-01** by [`build-the-nas.md`] §5b, and mounted by `Saruman` ([ADR-0072]) | | Everything else on the estate | Not at all — default deny | [ADR-0012] asks for a named off-host consumer before a port is published, and @@ -119,7 +119,7 @@ for a boundary it is not. The NFS export on `2049` is not one of those three media ports, and it differs in kind. A device on CasaBonita can open the port, but the export -itself admits `10.0.30.110` alone, so the share answers only `Saruman`. The +itself admits `10.0.30.20` alone, so the share answers only `Saruman`. The host scope is enforced twice, by the firewall rule and by the export, and only the export applies on the segment. diff --git a/stacks/sensitive/glance/pages/homelab.yml b/stacks/sensitive/glance/pages/homelab.yml index f16d4a82..e465e99b 100644 --- a/stacks/sensitive/glance/pages/homelab.yml +++ b/stacks/sensitive/glance/pages/homelab.yml @@ -131,7 +131,7 @@ color: 349 89 72 links: - title: Proxmox · Saruman - url: https://10.0.30.110:8006 + url: https://10.0.30.20:8006 icon: /assets/icons/proxmox.svg - title: TrueNAS · smaug url: https://10.0.40.30