diff --git a/SECURITY.md b/SECURITY.md index 299bef27..0e3b01e8 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -50,6 +50,7 @@ is a very different thing from an overlooked one. Full detail in | 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. | | The hypervisor's BMC shares a broadcast domain with the attack VM | **Accepted residual, not a fix in progress.** `shiva`, the iLO 4 at `10.0.30.10` on firmware 2.82, sits on ImaginationLAN, the segment [ADR-0014](docs/adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md) gives to `ifrit`'s Kali VM, and a BMC does not get patched the way a guest does. [ADR-0033](docs/adr/0033-keep-the-ilo-on-the-lab-segment.md) keeps it there on purpose: the iLO is part of the estate under attack, and a BMC compromise in the lab costs the lab. Moving it to Winterfell would put an end-of-line BMC beside the firewall's admin UI and open three more ports on the Hicks list to reach its console. Controls: the BMC's own hardening (IPMI-over-LAN and unused services off, a credential shared with nothing in the house), and the lab tripwire — anything the BMC initiates toward another segment is a logged pass and a `LabSegmentReachedInternalNetwork` alert. Both done 2026-09-09: IPMI-over-LAN, SSH and iLO Federation off on the BMC, its account's credential shared with nothing in the house, its security log read for a baseline; and the `10.0.30.10 → 10.0.99.20/udp` "return path" rule deleted from `morpheus` — it had been evaluated 7.4 million times and matched zero packets, because pf state carries the scrape's replies, and it was the BMC's only path to the monitoring host's syslog listener. The scrape failed once at the reload and has been clean since; the lab interface now passes nothing across a segment except `Saruman`'s two agent ports. | +| `smaug` ships no logs, and no absence rule can say so | **Accepted residual, not a fix in progress.** [ADR-0016](docs/adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md) reverses the monitoring direction for CasaBonita (40) so that nothing on that segment initiates upward: `prometheus` scrapes `10.0.40.30` over `99 → 40:9100`, and `smaug` runs no Alloy agent. Loki has no pull — ingest is a push or it is nothing — so the NAS ships no logs, and with the agent go its SMART attributes and its patch state for the same reason ([#483](https://github.com/Gerrrt/HomeLab/issues/483) owns that half). It is the one host in the estate whose compromise is invisible: Prometheus shows it alive and busy, and nothing shows what it did — on the segment whose stated assumption is that everything on it is already compromised. That is already true with nothing deployed on it: the blind spot is a property of the direction rather than of what runs there, and Jellyfin's network-facing indexer will land on a host that was invisible before it arrived. **Collecting the logs costs more than the gap.** A `40 → 99:3100` rule would be the first upward path in the estate, and it would hand a low-trust host write access to the unauthenticated ingest ports recorded above, so the two residuals would compound rather than one closing the other. What still watches the segment is the `igc0.40` tripwire and nothing else — Suricata runs on Skids and Degens, not on 40. **No new alert is warranted, and that is the second half of this row.** `TerminalSegmentReachedInternalNetwork` fires on a `pass` from a terminal VLAN toward `30`, `50`, `99` or the switch LAN, which catches a compromise *leaving*; anything that stays on CasaBonita or egresses to the internet leaves no record at all, and a rule cannot be written against a record that does not exist. Decided under [#255](https://github.com/Gerrrt/HomeLab/issues/255); the detail is in [`docs/security.md`](docs/security.md). | The switch is the honest gap, and it is a deliberate one. `neo` (10.7.7.2) is rotated and polling. Its firmware does not persist a deletion from the SNMP diff --git a/docs/network.md b/docs/network.md index 79c0a1dc..63142814 100644 --- a/docs/network.md +++ b/docs/network.md @@ -293,7 +293,7 @@ Televisions and consoles. Internet only. not exist yet and neither does `stacks/media`; what exists is a host on its address. **It does not change the *Reaches* column**, and that is the point ADR-0016 made in advance: nothing on this segment initiates anywhere, and the - three rules created that day all let a more trusted segment reach **in**. + four rules created that day all let a more trusted segment reach **in**. That is the direction this row records, and it is the one that is unchanged. - **Inbound is no longer nothing, and that is deliberate.** Since 2026-09-16 Hicks reaches `10.0.40.30` on `443` and `8096`, and `10.0.99.20` reaches it diff --git a/docs/security.md b/docs/security.md index 9709cb7e..3daf0cfe 100644 --- a/docs/security.md +++ b/docs/security.md @@ -215,16 +215,27 @@ Everything else — IoT, media, guest — gets internet and nothing more. That sentence is about what those segments *initiate*, and two decisions now reach into them without touching it. [ADR-0016](adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md) -writes three passes into CasaBonita for the NAS, and +writes passes into CasaBonita for the NAS and [ADR-0035](adr/0035-scope-the-99-to-20-rule-to-the-hue-bridge.md) one into -Skids — `10.0.99.40 → 10.0.20.104:80,443/tcp`, Home Assistant to the Hue -bridge, the one device on that segment with a local API — above the block -that has stood between 99 and 20 since the segments existed. Neither is -created yet; both wait on the host that would use them. The row ADR-0008 +Skids, and they are at different stages — this paragraph said neither was +created, and half of that stopped being true on 2026-09-16. **CasaBonita's +exist.** Hicks reaches `10.0.40.30` on `443` and `8096`, and `10.0.99.20` +reaches it on `9100` and `22`: four host- and port-scoped passes above *Block +access to CasaBonita*, verified in position with `pfctl` rather than read off +the web UI, where an appended rule looks present while matching nothing. +ADR-0016 wrote three and four exist, because the Hicks pass is split rather +than carrying a port list, and the `22` is inert — TrueNAS ships SSH disabled +([ADR-0040](adr/0040-run-truenas-on-smaug-and-keep-the-media-stack-in-this-repository.md)). +[`network.md`](network.md) holds the current list. **Skids' does not exist.** +`10.0.99.40 → 10.0.20.104:80,443/tcp` — Home Assistant to the Hue bridge, the +one device on that segment with a local API — still waits above the block that +has stood between 99 and 20 since the segments existed, on the host that would +use it ([#134](https://github.com/Gerrrt/HomeLab/issues/134)). The row ADR-0008 wrote as `99 → 20` is narrower than it read: one host to one device on two -ports, with the twenty other devices on Skids still unreachable from -anywhere, and the segment still initiating nothing. The tripwire below is the -check that the second half holds when the first lands. +ports, with the twenty other devices on Skids still unreachable from anywhere, +and both segments still initiating nothing. The tripwire below is the check +that the second half holds — waiting on Skids, and since 2026-09-16 actually +checking on CasaBonita. That sentence is now checked rather than asserted. Four **tripwire** rules sit below the block rules that stop each cross-segment path and above the @@ -246,6 +257,39 @@ against a firewall that logged only blocks, so it could not fire for any input firewall restore from a backup older than 2026-09-01 drops them silently; the restore runbook checks for them. +**On CasaBonita that tripwire is now the only control watching the segment**, +and since 2026-09-16 there is something on it worth watching. `smaug` at +`10.0.40.30` is scraped rather than pushing, by the same ADR-0016 decision that +keeps the segment terminal outward: it runs no Alloy agent, and with the agent +go its logs, its SMART attributes and its patch state, because every one of +those rides a push this host does not make. Loki has no pull — ingest is a push +or it is nothing — so the only rule that would centralise the logs is the +`40 → 99:3100` the ADR refused, and refusing it is the residual recorded in +[`SECURITY.md`](../SECURITY.md) +([#255](https://github.com/Gerrrt/HomeLab/issues/255) for the logs, +[#483](https://github.com/Gerrrt/HomeLab/issues/483) for the rest). Suricata +does not watch `igc0.40` either; it runs on Skids and Degens only. So the +tripwire is what is left, and it is worth being exact about what that buys: +`TerminalSegmentReachedInternalNetwork` fires on a `pass` from 10, 20 or 40 +toward 30, 50, 99 or the switch LAN, which is a compromise *leaving* the +segment. Anything `smaug` does that stays on CasaBonita, or goes straight out +to the internet, is not merely unalerted — it is unrecorded. + +**There is no alert for that and there cannot be one**, which is a decision and +not an omission. A rule reading "no logs from `smaug`" is an absence rule, and +an absence rule needs a declared expectation to measure the silence against — +`homelab_suricata_expected_interface` is that pattern, and `SuricataLogsStopped` +only became writable once somebody measured what a normal silence looks like: +22 days of stream, a worst gap of 75 minutes, nine hours chosen as seven times +it. Here the expected volume is zero and always was, so silence is the design +working and no threshold separates it from a compromise. The two +collection-completeness rules are no help for the same reason. +`JournalSourceStopped` and `LogEntriesDropped` are both keyed on an agent that +exists, and `check_loki_coverage.py` takes its denominator from the hosts Loki +is already hearing from — so a host that has never shipped a line is absent +from the question rather than failing it. The firewall and the tripwire are the +control. This paragraph is the record that there is nothing else. + Segmentation is doing more work here than it should have to. Prometheus and Loki publish unauthenticated ingest ports for `oracle`'s agent to use, so anything that can route to `10.0.99.20:9090` or `10.0.99.20:3100` can write to the metric