Repository navigation
A Kali Purple analyst workstation (garuda) on Saruman, not a second SOC #921
Description
A Kali Purple guest on Saruman: a defender's analyst workstation, not a second SOC.
Where, and why there
Saruman, on ImaginationLAN. ADR-0007 splits the lab as "Saruman defends, ifrit attacks", and a defender's machine belongs on the defended side, beside the SOC on odin. It does not go on ifrit: the range is built so that its targets have no route (ADR-0014), and defensive tooling sitting inside the attack range would blur that split.
Proposed name: garuda, a free summon (unused in docs/, stacks/, tofu/, ansible/ and packer/ as of 2026-10-06). The address comes from provisioning, not from this issue.
What it is for, and what it is not
Kali Purple bundles a whole SOC. Most of that already runs here and is not duplicated:
| Purple bundles | Already here |
|---|---|
| Elastic SIEM | Wazuh on odin (ADR-0030) |
| Zeek | Zeek on fenrir, from the lab-bridge mirror (ADR-0068) |
| Suricata | Suricata on morpheus |
| Response tooling | Velociraptor on odin |
A second SIEM would split the telemetry and the RAM between two half-fed systems. Its SIEM and sensors stay off. What it adds:
- An analyst desktop: CyberChef, Wireshark, and the tools for working an alert that Wazuh raised.
- Vulnerability scanning of the lab (OpenVAS/GVM), pointed at the lab domain's guests and nothing outside VLAN 30.
- Case management (TheHive), if it earns its keep.
- The purple half of an exercise: attack from
ifrit's Kali (Build tpl-kali (VMID 902) on ifrit once it exists #790), then watch and respond from here. - The home of
dotfiles-Defense. It is the defensive distro, so the Defense role layer lands here, mirroring Offense onifrit's Kali. One on-demand VM per dotfiles OS layer on Saruman #920 is updated to say so.
Sizing and lifecycle
About 4 vCPU, 8 GB RAM, 80 GB thin on large_data (SSD). Saruman had 54.5 GiB of RAM free on 2026-10-06. Always on, like the rest of the domain, and decided 2026-10-06: it is not tagged on-demand, unlike #920's dotfiles VMs. So HypervisorGuestStopped covers it, and it counts against Saruman's RAM continuously (54.5 GiB was free on 2026-10-06). It is built like the rest of the lab, with a Packer template from phoenix (ADR-0074) and an OpenTofu guest (ADR-0076).
Open before building
- The OpenVAS scans' scope has to be written down (VLAN 30 only), and the scans will trip Suricata and Wazuh. That is useful as a detection test, but it needs a silence owned by this issue while a scan runs.
Does it join the lab domain?Decided 2026-10-06: no. It stays standalone beside the domain, likeodinand Run BloodHound CE where nothing attacks it #451's BloodHound. The reasoning is in the comments.
Activity
Gerrrt commentedon Oct 6, 2026
2026-10-06:
garudais always on, noton-demand. It runs continuously, like the domain's other guests. Only #920's dotfiles VMs stay on-demand. The body is corrected.Being a standing member of the estate means more than staying powered on:
HypervisorGuestStoppedcovers it, because it is not taggedon-demand, so a stoppedgarudais a finding.odin. The SOC watches its own analyst's machine.Gerrrt commentedon Oct 6, 2026
2026-10-06: correction.
garudais not a domain member. It is always on beside the domain, not in it. This replaces the assumption in the previous comment.ifritaim to take it over. A domain-joined analyst workstation would fall with it: Domain Admin over the domain would mean admin overgaruda, its case notes and its scan results. This is the separation real SOCs keep, with analyst machines on a tier the defended domain cannot administer.garudawould keep dropping out of the domain.odinis standalone for the same reason.Practising a domain-joined analyst workflow, if that's ever wanted, belongs on a separate domain-joined desktop guest, not on the machine that holds the evidence.
The other "always on" consequences in the previous comment stand: alerting, telemetry, Wazuh and Velociraptor enrolment, patching and backups.
fix(lab): address review on garuda's build (#921)
docs(lab): garuda's dotfiles clones live under ~/code/dotgibson (#921)
docs(lab): garuda's as-run log, and what the build corrected (#921)
Gerrrt commentedon Oct 10, 2026
2026-10-10: phase 1 built.
garudais up at10.0.30.62(VMID 162), with Purple's desktop and tools and thedotfiles-Defenselayer. Authored in #1116; the full as-run log is inbuild-the-analyst-workstation.md§11.Template 910 (
tpl-kali-saruman, ADR-0091)phoenix: 26m25s, then a-forcerebuild in 26m11s.packer-smoke.sh 910passed after each.The guest
tofu applyagainst the shared state: 2 added, 0 changed, 0 destroyed (theanalystpool and garuda).analyst, noton-demand, soHypervisorGuestStoppedcovers it.onboot: 1, Secure Boot keys off, 4 vCPU / 8 GiB.Purple, without its SOC
kali-desktop-xfce,kali-themes-purple, CyberChef 11.3.0, Wireshark/tshark 4.6.6 (non-root capture via thewiresharkgroup), Suricata 8.0.7, YARA, jq, and Docker 28.5.2 with Compose 2.40.3.suricata.serviceis disabled, and no Suricata, Zeek, Elastic, Kibana or Wazuh listener is up.zeek5.1.1 needslibc6 (< 2.38)and cannot be installed on current Rolling.fenriris the sensor; captures can be run through the pinnedzeek/zeekimage.dotfiles, in
~/code/dotgibson/dotfiles-Debianv0.1.59:bootstrap.shexit 0, using the Kali tier.dotfiles-Defensev1.0.132:bootstrap.shexit 0, and its probe confirmsdocker composeis available, sosiemupwould work.siemupis down, as ADR-0091 requires.zeek chainsaw hayabusa sigma-cli velociraptor vol log2timeline.py.core doctor(dotfiles-core 7.14.0): exit 0, nothing expected missing.Found on the way, filed separately
garuda, and the smoke clones were unaffected (1 in 3 first boots)./is 59.7 GB of the 80 GB disk, because the template's swap partition sits after/.Still open in phase 1
stacks/analyst(runbook §6).golembackup job (§8).ssh -t analyst@10.0.30.62 sudo passwd analyst.Phases 2 (Wazuh/Velociraptor enrolment) and 3 (OpenVAS) are unchanged.
feat(lab): garuda, a Kali Purple analyst workstation with dotfiles-De…
feat(lab): garuda's ingest token, and its own age key (#921) (#1144)
Gerrrt commentedon Oct 10, 2026
2026-10-10: phase 1 is built and reporting.
garudanow sends telemetry to the lab, is backed up, and passed runbook §9 apart from the desktop line. The as-run log is in §11 (#1149).§6, telemetry (#1144)
alexanderand garuda. The token arrived on stdin only, so it was never an argument, and the two hashes match.alexander's lab stack was pulled tomainand brought up: all healthy, no 401s.stacks/analystis up on garuda: Alloy and its socket proxy, both healthy.§8, collectors and backup
homelab_apt_upgrades_pending{host="garuda"} 0.162joinedgolem-nightly. A test snapshot backup ran in 9m03s, encrypted, and the guest's filesystem freeze and thaw were clean.odin(160) beforehand. The domain's six were not in it, and I left that as it was.§9, checks
homelab_guest_running 1andhomelab_guest_on_demand 0, soHypervisorGuestStoppedcovers it.up= 1 forgaruda-alloyandgaruda-metrics, and garuda's journal is in Loki.analyst-alloyandanalyst-docker-socket-proxyrun. SSH (22) is the only port open beyond loopback.core doctorexits 0.qm reboot 162brought everything above back with nothing typed (agent back in 41s).Earlier the same day, #1128 was fixed in place on garuda:
/is now 79 GB, with a 4 GB swapfile.Left for Garrett
ssh -t analyst@10.0.30.62 sudo passwd analyst, typed at the prompt.lightdm's account ships expired (1970-01-02), souser@968.servicefails on every boot and systemd readsdegraded.lightdmitself is running, and the desktop check will show whether that matters.Still open on #921: phase 2 (Linux Wazuh and Velociraptor enrolment), phase 3 (OpenVAS), #1127 (the first-boot cloud-init race) and #1128's template-side fix.
docs(lab): garuda is built — §6, §8 and §9 as run (#921) (#1149)
feat(soc): enrol garuda in odin's Wazuh and Velociraptor (ADR-0092) (#…
Gerrrt commentedon Oct 10, 2026
2026-10-10: phase 2 done.
garudais enrolled inodin's Wazuh and Velociraptor, the first Linux guest in either. The decision is ADR-0092, the change #1163, and the as-run is in #1164 (runbook §11).odinbeside the six's MSIs, and8448now answers.62.InRelease.odinover SSH.garuda, Active.odin'sauthd.pass.authd.passwas removed afterwards.velociraptor_clientis active with a connection toodin:8000..debcarries the enrolment nonce.WazuhAgentsNotConnectednow expects garuda's agent while garuda's Alloy is up.alexanderand inactive.Left for Garrett:
garudais listed. Aclients()query run from a second process on the server returned nothing, so I couldn't confirm it from here.Next: phase 3, OpenVAS.
docs(analyst): OpenVAS set up on garuda and its fence proved, as run
feat(analyst): fence OpenVAS at layer 2, in a network namespace (ADR-…
feat(analyst): OpenVAS on garuda, on demand, fenced to the domain's s…
Gerrrt commentedon Oct 11, 2026
All three phases are built, proven and merged. garuda is VMID 162 at 10.0.30.62 on Saruman: Kali Purple as a defender's analyst workstation, not a second SOC.
Phase 1 — the workstation (#1116, as-run #1149; in-place #1128 fix #1135)
analystpool, always on, not domain-joined.Phase 2 — SOC enrolment (#1163, as-run #1164, ADR-0092)
Phase 3 — OpenVAS (#1166, ADR-0093)
netdevegress filter dropping every frame — IP, ARP, raw — to anything but the six. Proved: out-of-scope arping/TCP/ICMP dropped, in-scope reachable.Decisions recorded along the way: not a second SIEM (ADR-0091), standalone not domain-joined, always-on, template 910 vs 902, packages staged on odin (ADR-0092), and the on-demand layer-2-fenced scanner (ADR-0093).
Left to Garrett (hands-on-console / UI, can't be done from here), tracked on their own:
Follow-ups spun out and closed separately: #1127 (Kali first-boot cloud-init), #1128 (template root growth). #1164 (phase-2 as-run) lands via auto-merge.
docs(lab): garuda's SOC enrolment as run, phase 2 (#1164)
Gerrrt commentedon Oct 14, 2026
§9
degradedstate resolved on garuda (162).The one failed unit left after the build —
user@968.service("User Manager for UID 968") — waslightdmfailing PAM because its shadow account-expiry was pinned to the epoch (1970-01-02):It's not lightdm-specific: ~14 Kali service accounts ship expired at the epoch (
lightdm _gvm inetsim mysql tcpdump Debian-snmp pcscd rtkit dnsmasq nm-openvpn nm-openconnect systemd-network systemd-timesync _dhcpcd). lightdm is just the only one that surfaces, because it's the only expired account systemd starts auser@<uid>.servicefor (it lingers a session for the greeter); the rest are pure daemons that never hit PAM'saccountpath.root,analyst,sshd,messagebusare clean. The greeter itself was always fine — only the systemd state was dirty.Fixed live on garuda — cleared the bogus expiry on every UID<1000 account that had it, then started the unit:
Now:
account expires: never,user@968active, 0 failed units,systemctl is-system-running→running.It's a template defect (910, and the shared 902/903 built from the same
packer/kali.pkr.hcl+ preseed), coming from Kali's install/postinst layer — our build touches no account expiry. Tracked for a proper fix at the template in #1194, so fresh clones aren't borndegraded.That clears the §9 degraded line. Remaining #921 console/UI items for you: §9 desktop visual check from Hicks, console password, Velociraptor GUI client check, and the GVM admin password change in the UI + deleting
/root/gvm-install.log(still present).fix(packer): clear epoch account-expiry on Kali service accounts so c…
Gerrrt commentedon Oct 14, 2026
Template fix for the §9
degradedstate is done and verified.The epoch account-expiry that made clones boot
degraded(lightdm'suser@<uid>.servicefailing PAM) is now fixed in the Kali preseed itself, not just live on garuda:a953afc), Kali template ships service accounts expired at the epoch → every clone boots 'degraded' (user@968/lightdm PAM) #1194 closed — alate_commandstep inpacker/kali/preseed.cfg.pkrtplclears the epoch expiry on exactly the affected service accounts.packer build -force→BUILD_EXIT=0,packer-smoke.sh 910PASS, and a fresh full-clone bootedsystem: running, 0 failed units, 0 accounts at epoch. Throwaway destroyed.So every future clone of 910 comes up clean. garuda (162) itself was already fixed live.
Sibling template 902 (ifrit's Kali, same preseed) carries the same defect until it's rebuilt; tracked in #1196 (tied to #790).
Remaining #921 console/UI items for Garrett are unchanged: §9 desktop visual check from Hicks, console password, Velociraptor GUI client check, and the GVM admin-password change in the UI + deleting
/root/gvm-install.log.docs(runbook): record the Kali template epoch-expiry fix in analyst §…
Gerrrt commentedon Oct 14, 2026
The §11 as-run write-up of the
degraded/epoch-expiry fix is merged — PR #1197 (squash6b0d72a). It records the root cause, the live fix on garuda, the preseedlate_commandtemplate fix, and the 910 rebuild + smoke + clean-boot clone verification, scoped so the guarantee applies to a rebuilt 910 (902 still owed, #1196).That closes out the
degradeditem in docs as well as in code. Remaining #921 console/UI items for you are unchanged: §9 desktop visual check from Hicks, console password, Velociraptor GUI client check, and the GVM admin-password change in the UI + deleting/root/gvm-install.log.