Enterprise Security Homelab
A six-VLAN, default-deny network for a fictional regional bank: pfSense, managed switching, a Proxmox hypervisor on a trunk port, a Windows Server 2025 domain controller, a Wazuh SIEM with four agents and its own detection rules, a Greenbone scanner whose identity is administrator on member machines and nothing on the domain, a first finding found, fixed and proved closed, and every firewall rule justified and mapped to a NIST control.

The scenario
Most homelabs are a pile of services on a flat network. This one is built and documented as the infrastructure of Biira Bank, a fictional state-chartered bank, because working to a named organisation forces the decisions a real environment forces: who needs to reach what, why a rule exists, what an auditor would ask, and what happens when something fails. Financial services was chosen deliberately. It is the sector where segmentation, least privilege, change control and evidence are least optional.
The domain is real, the segmentation is real, and the controls are tested rather than described. The sibling Enterprise IAM Lab is the same organisation’s identity estate and runs on top of this network.
Architecture
Internet enters through pfSense, which holds the .1 gateway on all six VLANs, so every packet that crosses a segment boundary is a firewall decision. A TP-Link managed switch carries the 802.1Q tags: Port 1 trunks to pfSense, Port 2 trunks to a second switch feeding the Proxmox VE hypervisor, and the remaining ports are single-VLAN access ports for physical hosts.
| VLAN | Zone | Subnet | What lives there |
|---|---|---|---|
| 10 | Management | 192.168.10.0/24 | pfSense, PVE01 (the hypervisor), ADM01 (the admin workstation), a lab Ubuntu box |
| 20 | BlueTeam | 192.168.20.0/24 | SIEM01: Wazuh manager, indexer and dashboard. SCAN01: Greenbone vulnerability scanner |
| 30 | RedTeam | 192.168.30.0/24 | KALI01, the attack host, as a Proxmox guest |
| 40 | DevOps | 192.168.40.0/24 | APP01, two web applications behind Tailscale Serve; VAULT01 planned |
| 50 | EnterpriseLAN | 192.168.50.0/24 | DC01, the corp.biirabank.com domain controller; WKS01, the first member workstation |
| 60 | Monitoring | 192.168.60.0/24 | MON01, Grafana and Prometheus, being rebuilt as a guest |
Proxmox sits on a trunk port with a single VLAN-aware Linux bridge, so a VM’s network placement is one tag on its virtual NIC. Moving the hypervisor from an access port to the trunk without locking myself out is written up in Proxmox on a Trunk Port.
What is built
- Default-deny segmentation on three of the six interfaces (MANAGEMENT, ENTERPRISELAN, REDTEAM). Every rule carries a business justification and a NIST SP 800-53 control mapping in a living rule register, with a change log that records tester, rollback and validation evidence. The register now has a ninth principle, learned the hard way: a saved rule is not an enforced rule until the filter is reloaded.
- Management-plane isolation, verified by before-and-after testing: VLAN 50 cannot reach the pfSense administrative interface on any address, while it keeps the DNS, NTP and internet access it legitimately needs.
- KALI01, the attack host, as a Proxmox guest on VLAN 30 with no standing path to any other segment, no ICMP-to-any, and a clean-install snapshot to roll back to after every exercise. Its containment is proven by a test from the host itself, and the runbook is explicit about what that test does and does not prove.
- SIEM01, Wazuh 4.14.7, rebuilt on dedicated hardware after the original host died. The failure became a role swap: the 16 GB machine that had been running Grafana went to the memory-hungry, data-bearing SIEM, and monitoring moves to a hypervisor guest. Written up in Standing Up Wazuh Twice.
- Four agents reporting: DC01, ADM01, PVE01 and WKS01, each with its own firewall rule on the interface its traffic actually arrives on, and Sysmon on the three Windows machines. Enrolling ADM01 exposed that its traffic was bypassing the firewall over Tailscale; the fix, and the evidence, are in the runbook.
- Detection engineering, with the evidence kept. A built-in rule fired at level 15 all day on PowerShell checking its own execution policy. It is now tuned by an exception that needs two conditions, so the rule still catches everything else, and the scanner’s 2,051 sign-in alerts per scan became a silent rule for SCAN01 and a level 12 alert for the same account from anywhere else. Archives are on, so a silenced alert still leaves its event on the record. The rules live in the repository and are deployed from it. Written up in The Alert That Cried Wolf.
- The first finding found, fixed and proved closed. A credentialed scan found TLS 1.0 and 1.1 on WKS01’s Remote Desktop. A scoped, reversible Group Policy removed them, each step was verified on the machine, and a third scan proved them gone from three directions. WMI stays closed to the scanner by decision, because it would let a stolen scan credential run commands everywhere. Written up in Find, Fix, Prove.
- SCAN01, a Greenbone Community Edition scanner on VLAN 20 with all four feeds loaded and 186,567 vulnerability tests, built as a VM rather than a container because a scanner stores administrative credentials for everything it scans. Nessus Essentials was the first choice and was ruled out on its own current terms: five addresses on a thirty-day licence cannot cover nine hosts, or sustain a loop of scanning, fixing and scanning again. Written up in A Scanner That Is Not a Domain Admin.
- A scan identity that is an administrator on member machines and nothing on the domain. One service account in one group, and a Group Policy that places that group in each machine’s local Administrators, linked to the member OUs only so the Domain Controllers OU sits structurally outside the grant. Four of the five ways to sign in to Windows are then denied to the account, leaving network logon alone, and the closed door is proven by a Remote Desktop session refused with the correct password.
- A forest rebuilt from an export,
corp.biirabank.comreplacing the retiredad.biira.online, by demoting and repromoting the same hardware rather than renaming it. A password typed back under a different keyboard layout locked the controller out after the promotion reboot, and the recovery is recorded as an incident rather than quietly fixed. - A public tier,
biirabank.com, served as Cloudflare Worker static assets and graded F to A+ by an independent header checker. Testing found the site still answering on its defaultworkers.devhostname, where none of the zone-level controls applied. - Four CIS baselines recorded before any hardening, one per host and operating system. They are the “before” half of a before-and-after, and the runbook explains why the four scores must not be read as a ranking.
- DC01, a Windows Server 2025 domain controller with AD-integrated forward and reverse DNS zones and a clean
dcdiag. It was the first agent. - Nightly Proxmox backups to a separate physical disk with a tested restore (NIST CP-9), and SHA256 verification of every installation image before use (SI-7).
- Remote access over Tailscale with WireGuard, with its ability to bypass per-interface rules recorded as a known, risk-accepted hardening item rather than ignored.
Decisions worth explaining
Rules are written for the auditor, not the firewall. A rule that works but cannot be justified is a finding waiting to happen. The rule register lists the principles auditors check against a rulebase and applies them to every rule: purpose, owner, least privilege, control mapping, review date.
Failure is treated as design input. Two hardware failures (a NIC, then the SIEM host) are documented as incidents with a decision record, not quietly fixed. The second produced a better topology than the original: the data-bearing service on dedicated hardware, the light, reproducible one on the hypervisor.
Measure before you harden. Every host’s CIS baseline was captured on enrolment and cannot be recreated later. A score on its own proves nothing; a score that moves proves the work happened, which is what an auditor is actually asking for.
Privilege is a chain, and every link should be removable. The scanner is an administrator of a machine only while the account is in the group, the group is in that machine’s Administrators list, and the machine is in the policy’s scope. Break any link and the access is gone. The retirement procedure was written at the same time as the grant, because the mechanism that adds the membership leaves it behind when the policy stops applying.
Silence the alert, never the evidence. Tuning is what keeps an alert queue readable, but by default Wazuh stores alerts and discards everything else, so a suppression also erased the event. Archives went on before the tuning was called finished.
Fix what matters here, not what scores highest. The first finding fixed was not the top of the report. It was the one that was real, belonged to this environment, could be fixed by policy and could be proved closed. Several higher scores came from files that ship with Windows and will never change.
Naming is a control. Every machine follows <ROLE><NN> (DC01, SIEM01, KALI01, APP01) and every screenshot follows <area>-<NN>-<subject>, so evidence can be traced to the configuration it proves. That is what CIS Control 12.4 and NIST AU-3 actually ask for.
What is next
- The last unmonitored host: a
DEV-01rule and a Wazuh agent on APP01, the first real rule on the DEVOPS interface. - Harden DC01 and ADM01 against their CIS baselines (both 26%), re-run the assessment and record the delta.
- Keep the remediation loop turning. TLS on WKS01 was the first finding closed; the next pass extends the same pattern to servers and the domain controller once they are assessed, starting with an unauthenticated scan of DC01, because there is no least-privilege way to credential-scan a domain controller.
- Track findings from open to closed in DefectDojo, now that the condition set for adopting it, three reports and one closed finding, has been met.
- A retention policy for the new archive index, sized from the growth measured on the first day rather than an estimate.
- Write the BLUETEAM, DEVOPS and MONITORING rulesets, which are still permissive. The BlueTeam one is now driven by the traffic SCAN01 actually needs, and a scanner has to cross every boundary the firewall exists to enforce.
- Close the scanner’s password debt: VAULT01 issuing a short-lived credential per scan, retiring the non-expiring password that is currently recorded as a tracked exception.
- Rebuild MON01 and ANS01 as Proxmox guests, and give Vault its own guest (VAULT01) rather than a share of APP01.
- Scope Tailscale to close hardening item H-02, and build PAW01, a dedicated administrative workstation, to close H-01.
- Rebuild the identity estate on the bank’s new public domain,
biirabank.com, with a new forest and a new Okta org, from a decision record this time. That work opens a Version 2 chapter here and in the IAM case study.
Everything written so far, with the source one click away.
Every chapter written so far, in the order a reader would want them. Each one opens on GitHub, where the full runbook, its evidence and a PDF copy live. Anything not marked is current.
- This projectnoble-antwi/enterprise-security-homelab
Every chapter of the build in Markdown and PDF, the evidence screenshots, the rule register and the decision records.
- The public tiernoble-antwi/biirabank-web
The bank’s public website: plain HTML and CSS on Cloudflare’s edge, no JavaScript, graded A+ for security headers.
biirabank.com ↗ - The identity estatenoble-antwi/enterprise-iam-lab
The organisation profile, the phase guides for Active Directory, Okta and conditional access, and the scripts that built the directory.
Network and platform
5 entriesShow all 5
- 01Network infrastructure and pfSense (opens on GitHub)
The six-VLAN design, the managed switch port map, DHCP per segment, outbound NAT and the first firewall aliases.
- 10Proxmox hypervisor on a trunk port (opens on GitHub)
Moving the hypervisor from an access port to a trunk without losing access, and one VLAN-aware bridge that places every guest by tag.
- 14Storage, backup, capacity and naming (opens on GitHub)
The storage layout, nightly backups with a tested restore, a container-versus-VM capacity plan, and the role-based naming scheme.
- 05Remote access with Tailscale (opens on GitHub)
Reaching every segment without port forwarding behind an ISP-controlled router, and why this replaced the original OpenVPN plan.
- SSHSSH configuration and keys (opens on GitHub)
Key management and a client configuration that gives every host a friendly name.
Firewall and segmentation
2 entriesShow all 2
- 13Firewall rulebase governance (opens on GitHub)
The principles an auditor checks, a rule register with a justification and control mapping per rule, the change log and the review cadence.
- 11Domain controller firewall access (opens on GitHub)
What each Active Directory port does, how pfSense evaluates rules, the aliases that keep them readable, and the target rule layout.
Detection, scanning and offence
7 entriesShow all 7
- 16SIEM01: rebuilding the SIEM (opens on GitHub)
Recovering from a dead SIEM host, the hardware role swap that improved the design, three agents, and four CIS baselines captured before any hardening.
- 19SCAN01: Greenbone and a least-privilege scan identity (opens on GitHub)
Rejecting Nessus on its current terms, three build failures recovered without data loss, and a scan identity that is administrator on members and nothing on the domain.
- 20Detection engineering: tuning and archives (opens on GitHub)
A level 15 false positive found and tuned without creating a blind spot, the two silent failures met writing the rule, and archives enabled so a suppressed alert still leaves an event on the record.
- 21Find, fix, prove: TLS on WKS01 (opens on GitHub)
The first full remediation loop: deprecated TLS found by a credentialed scan, removed by a scoped and reversible Group Policy change, and proven gone from three angles by a rescan.
- 15KALI01: building and containing the attack host (opens on GitHub)
Placing a deliberately hostile machine on the RedTeam segment, the containment test, and an honest account of what that test does not prove.
- 02The first Wazuh deployment (opens on GitHub)
The original all-in-one Wazuh build on Rocky Linux and the BlueTeam segment it lived on.
That host failed. Chapter 16 is the current SIEM.
- 03Observability with Grafana and Prometheus (opens on GitHub)
The monitoring stack on its own segment, and the inter-VLAN access it needed to see the rest of the lab.
Its hardware became SIEM01. Being rebuilt as a Proxmox guest.
Identity and the public edge
3 entriesShow all 3
- ADR-001Public domains and forest naming for the rebuild (opens on GitHub)
Why the forest is corp.biirabank.com rather than the public name, how sign-in suffixes hide it from users, and what the old domain’s retirement costs.
- 17Domain migration to corp.biirabank.com (opens on GitHub)
Demote and repromote instead of a forest rename, a lockout after the promotion reboot and its recovery, and a directory rebuilt from an export and verified against it.
- 18Public web presence and edge hardening (opens on GitHub)
Hosting biirabank.com on Cloudflare Workers, a default-hostname bypass found by testing and closed, strict TLS and security headers, and the F to A+ evidence.
Automation
5 entriesWritten for the first Ansible controller, which is being rebuilt as a Proxmox guest. The account design and role structure carry across.
Show all 5
- 04Automation platform (opens on GitHub)
The Ansible controller, its inventory, and managing Linux and Windows hosts from one place.
- 06Ansible service account (opens on GitHub)
A dedicated automation identity with key-based access, kept separate from personal administrative accounts.
- 07Role-based Ansible architecture (opens on GitHub)
Breaking monolithic playbooks into reusable roles, with the standards each role follows.
- 08Windows automation over WinRM (opens on GitHub)
Bootstrapping Windows hosts so Ansible can manage them, and the password policy traps along the way.
- 09Ansible controller setup (opens on GitHub)
Turning a plain Ubuntu VM into the controller: service accounts, static addressing, SSH keys and Windows reachability.
Planning
1 entryShow all 1
- 12Lab expansion roadmap (opens on GitHub)
How the Windows estate grows from one domain controller into something worth attacking and defending, what each machine teaches, and the resource budget.
Troubleshooting notes
5 entriesProblems met during the build, what caused them and what fixed them.
Show all 5
- T1Network infrastructure (opens on GitHub)
pfSense and switching problems, and an emergency recovery procedure for losing the network.
- T2Remote access (opens on GitHub)
Tailscale diagnostics and how to get back in when remote access fails.
- T3Ansible automation (opens on GitHub)
Configuration, SSH key and playbook failures, with the checks that isolate each one.
- T4Windows integration (opens on GitHub)
WinRM and Windows-side configuration failures when managing Windows from Ansible.
- T5System administration (opens on GitHub)
Linux networking, authentication and package problems, and the diagnostic commands that found them.
What it looks like when it is actually configured.
Screenshots from the build, named and captioned so each one can be traced to the configuration it proves.































