diff --git a/docs/changelog.md b/docs/changelog.md index ebd8c5bd..a85f1253 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -90,6 +90,24 @@ docstring gives: it is a record, not a claim about now. collector renders nothing when neither column is a percent. - `architecture.md`'s row for the host now names `ingest-proxy`. +- **`eden` audited after its #1443 rebuild.** The stack matched + `stacks/bloodhound/compose.yaml`: five containers, each at its pinned tag + and digest. `8443` answered `403` to anything outside Hicks, and Postgres was + not published. No collection zip or graph export lay outside + `/srv/bloodhound-data`. Patching was current, the leaf runs to 2029, and + Alloy was shipping with nothing dropped. Five things came of it: + - Two plaintext copies of `secrets/bloodhound.sops.yaml` were in `/tmp`, + left by `sops edit` runs that did not exit cleanly. They were deleted. + After a `make secrets-edit`, look for them. + - `eden`'s open `sshd` and `garnet`'s `NOPASSWD` sudo are now an accepted + row in `security.md`. + - The Ubuntu template turns X11 forwarding off. `eden` keeps it on until + it is next cloned from a rebuilt 901. + - `build-the-bloodhound-guest.md` §2 now says Docker's first `up` resets + the data directories' modes. + - The admin login is still the first-start `BLOODHOUND_ADMIN_PASSWORD`, + never changed in the UI. Left as it is for now. + ## 2026-11-12 - **#1473 is deployed on `alexander` and verified.** This completes the #1430 diff --git a/docs/runbooks/build-the-bloodhound-guest.md b/docs/runbooks/build-the-bloodhound-guest.md index 85baf22a..216176c0 100644 --- a/docs/runbooks/build-the-bloodhound-guest.md +++ b/docs/runbooks/build-the-bloodhound-guest.md @@ -180,6 +180,17 @@ sudo install -d -m 0750 -o 0 -g 0 /srv/bloodhound-data/work df -h /srv/bloodhound-data ``` +These modes are what the directories have until the first `up`, not after +it. Each is the root of a named volume, and when a volume is first mounted +empty, Docker copies the image's directory, mode included, onto it. So after +§6, `postgres` is `1777` (the `postgres` image's `/var/lib/postgresql`) and +`work` is `0755`. That is expected. The data itself is in +`postgres/18/docker`, which Postgres creates `0700`, and it is checked there: + +```bash +sudo stat -c '%a %u %n' /srv/bloodhound-data/postgres/18/docker # 700 999 +``` + `qm guest cmd 141 get-fsinfo` on `Saruman` should list `/` and `/srv/bloodhound-data`. diff --git a/docs/security.md b/docs/security.md index 03233755..f7fa2e52 100644 --- a/docs/security.md +++ b/docs/security.md @@ -22,6 +22,7 @@ What this network is actually built to survive: | Someone on the lab segment fetching a Velociraptor client package | **Scoped by address, and accepted.** `odin`'s `caddy` service (container `soc-agent-msi`) serves the two agent MSIs `ansible/` installs, and the two Debian packages `garuda` installs, on `8448` ([#1068](https://github.com/Gerrrt/HomeLab/issues/1068), [ADR-0092](adr/0092-enrol-garuda-in-odins-soc-with-debian-packages-staged-on-odin.md)). Its Caddyfile answers the six domain addresses, `.50`–`.55`, and `garuda`'s `.62`, and refuses everyone else, Hicks's route from VLAN 50 included. The Velociraptor MSI and the Velociraptor `.deb` each carry the client config: the server URL, its CA, and the enrolment nonce. Whoever holds either can enrol a rogue client, which is why #864 took the MSI out of SYSVOL. The residual: an address on this segment can be spoofed, and any of the seven can fetch either package. What the nonce buys is noise in Velociraptor's client list, the same class as the Wazuh enrolment password. Rotating it means a new server config, a rebuilt MSI with the role's pin moved, and the staged `.deb` deleted so that `scripts/stage-agent-msis.sh` builds it again. The two Wazuh packages carry no secret | | An OpenVAS plugin on `garuda` reaching a non-target | **Fenced at layer 2** ([ADR-0093](adr/0093-run-openvas-on-garuda-on-demand-scoped-to-the-domain-by-nftables.md)). OpenVAS scans the domain's six only. While a scan runs, the scanner runs in a network namespace whose one route to the segment is a macvlan, `scan0` at `10.0.30.63`, behind an nftables `netdev` egress hook that drops every frame — IP, ARP (`send_arp_request`) and raw (`send_frame`) — to anything but the six. So the AF_PACKET paths that bypass an IP filter are caught here. Proved: a GVM scan of an out-of-scope host raised the egress drop counter with 0 open ports, and an `arping` from the namespace to an out-of-scope host was dropped while an in-scope host answered. `gvmd`, `notus`, `redis` and `mosquitto` stay on the host; only MQTT crosses to the namespace, on a veth that routes nowhere else | | Someone on the lab segment reaching the SOC's web UIs | **Scoped by address, and accepted.** The Wazuh dashboard (`443`) and the Velociraptor GUI (`8889`) on `odin` are published by the stack's `caddy` service (container `soc-agent-msi`), not by their own containers, and its Caddyfile answers Hicks's `10.0.50.0/24` and refuses everyone else ([#1139](https://github.com/Gerrrt/HomeLab/issues/1139)). Until then both answered all of VLAN 30, the six domain guests included, because traffic between VLAN 30 hosts never crosses pfSense — so no firewall rule could have narrowed them. WireGuard peers are not in the list. The residuals: Hicks is a whole /24 with no per-host source yet ([ADR-0031](adr/0031-narrow-hicks-to-a-named-list-on-winterfell-and-leave-the-lab-open.md)), and an address on VLAN 30 can be spoofed, so this narrows who reaches the login pages and does not replace them. And the hop from Caddy to Velociraptor skips certificate verification: the GUI's certificate is signed by the CA inside the gitignored `server.config.yaml` on `odin`, and the hop is container to container on the `soc` bridge. The dashboard hop is verified against the Wazuh generator's root | +| Someone on the lab segment reaching `eden`'s SSH | **Key-only, unfenced, and accepted.** `eden` holds the map of the weak domain's attack paths, and its `sshd` answers all of VLAN 30, the six domain guests included: the guest runs with `firewall=0` and no host firewall, and nothing in this repository fences `sshd` on any Linux lab guest. What bounds it is the key. `sshd` refuses passwords, and `garnet` takes two keys, the operator's and `phoenix`'s, which `tofu/` installs; `root`'s copies are command-restricted by cloud-init. `garnet`'s sudo is `NOPASSWD`, from the default user Proxmox's cloud-init makes on every Linux guest `tofu/` builds, and is accepted with it: on `eden` `garnet` is in the `docker` group, which is root already. A fence by source address, as the domain's six have for `phoenix`, would be its own decision for every Linux guest, not `eden`'s alone | | Someone on the lab segment reaching the lab's Grafana or BloodHound, or a detonation reaching `diabolos`'s UIs | **Scoped by address, as the SOC's are.** `alexander`'s Grafana (`3000`) is published by the lab stack's `caddy`, and `eden`'s BloodHound (`8443`) by the bloodhound stack's own `caddy`; each answers Hicks's `10.0.50.0/24` and refuses the rest of VLAN 30, the six domain guests included. `diabolos`, not yet built, has soc's fence for its dashboard and GUI. Until then Grafana and BloodHound answered all of VLAN 30, where BloodHound's graph is the domain's attack-path map ([#1428](https://github.com/Gerrrt/HomeLab/issues/1428)). The residuals are the SOC row's: Hicks is a whole /24, and an address on VLAN 30 can be spoofed for a one-way packet but not for the TLS session the UIs need | | A range target with a path out | It has none — `gandalf`'s targets sit on a bridge with no physical port, on `172.30.30.0/24`, which the firewall does not route and on which nothing has a default route at all ([ADR-0014](adr/0014-put-ifrit-on-imaginationlan-and-give-the-targets-no-route.md), [ADR-0017](adr/0017-buy-ifrit-for-iops-and-keep-the-range-disposable.md)) | | Someone with the trusted Wi-Fi key quietly joining | Kea's lease log reaches Loki; `UnknownDeviceOnTrustedSegment` fires the first time a MAC appears on VLAN 50 in seven days ([ADR-0019](adr/0019-read-device-joins-from-the-dhcp-server.md)) | diff --git a/packer/ubuntu.pkr.hcl b/packer/ubuntu.pkr.hcl index 94c04ae5..0d80145d 100644 --- a/packer/ubuntu.pkr.hcl +++ b/packer/ubuntu.pkr.hcl @@ -131,6 +131,10 @@ build { # Host keys and machine-id are per-guest. cloud-init regenerates the keys # on a clone's first boot; an empty machine-id is regenerated by systemd. "rm -f /etc/ssh/ssh_host_*", + # Ubuntu's sshd_config turns X11 forwarding on, and no clone runs a + # display. sshd takes the first value it reads, and the drop-ins are + # included before the main file's line, so this wins. + "printf 'X11Forwarding no\\n' > /etc/ssh/sshd_config.d/10-no-x11.conf", "cloud-init clean --logs --machine-id", ] }