Infrastructure · Detection · Biira Bank

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.

Status
In active build
Area
Detection & infrastructure
Period
2025 to present
Role
Design, build, operate, document
Stack
pfSense802.1Q VLANsProxmox VEWindows Server 2025Wazuh 4.14SysmonGreenbone CEGroup PolicyKali LinuxAnsibleTailscaleCIS Benchmarks
Network architecture diagram: internet, pfSense, two switches and six VLAN security zones with firewall status, the hosts in each zone and their Wazuh agents
Current-state architecture on corp.biirabank.com. Switch 1 trunks to pfSense and to Switch 2, which carries every tagged VLAN to the Proxmox host; each zone shows its firewall status, its hosts and which of them report to the SIEM. Click to open full size.
6VLAN security zones
3 / 6Rulesets hardened
4Wazuh agents reporting
4CIS baselines recorded

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.

VLANZoneSubnetWhat lives there
10Management192.168.10.0/24pfSense, PVE01 (the hypervisor), ADM01 (the admin workstation), a lab Ubuntu box
20BlueTeam192.168.20.0/24SIEM01: Wazuh manager, indexer and dashboard. SCAN01: Greenbone vulnerability scanner
30RedTeam192.168.30.0/24KALI01, the attack host, as a Proxmox guest
40DevOps192.168.40.0/24APP01, two web applications behind Tailscale Serve; VAULT01 planned
50EnterpriseLAN192.168.50.0/24DC01, the corp.biirabank.com domain controller; WKS01, the first member workstation
60Monitoring192.168.60.0/24MON01, 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.com replacing the retired ad.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 default workers.dev hostname, 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-01 rule 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.
The build, chapter by chapter

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.

28 entries · 3 repositories

Network and platform

5 entries
Show all 5
  1. 01
    Network 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.

  2. 10
    Proxmox 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.

  3. 14
    Storage, 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.

  4. 05
    Remote 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.

  5. SSH
    SSH configuration and keys (opens on GitHub)

    Key management and a client configuration that gives every host a friendly name.

Firewall and segmentation

2 entries
Show all 2
  1. 13
    Firewall 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.

    In progressRead ↗PDF ↗
  2. 11
    Domain 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.

    In progressRead ↗PDF ↗

Detection, scanning and offence

7 entries
Show all 7
  1. 16
    SIEM01: 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.

  2. 19
    SCAN01: 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.

    In progressRead ↗PDF ↗
  3. 20
    Detection 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.

    Version oneRead ↗PDF ↗
  4. 21
    Find, 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.

    Version oneRead ↗PDF ↗
  5. 15
    KALI01: 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.

  6. 02
    The 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.

    Earlier buildRead ↗PDF ↗
  7. 03
    Observability 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.

    Earlier buildRead ↗PDF ↗

Identity and the public edge

3 entries
Show all 3
  1. ADR-001
    Public 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.

  2. 17
    Domain 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.

  3. 18
    Public 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 entries

Written for the first Ansible controller, which is being rebuilt as a Proxmox guest. The account design and role structure carry across.

Show all 5
  1. 04
    Automation platform (opens on GitHub)

    The Ansible controller, its inventory, and managing Linux and Windows hosts from one place.

    Earlier buildRead ↗PDF ↗
  2. 06
    Ansible service account (opens on GitHub)

    A dedicated automation identity with key-based access, kept separate from personal administrative accounts.

  3. 07
    Role-based Ansible architecture (opens on GitHub)

    Breaking monolithic playbooks into reusable roles, with the standards each role follows.

  4. 08
    Windows automation over WinRM (opens on GitHub)

    Bootstrapping Windows hosts so Ansible can manage them, and the password policy traps along the way.

  5. 09
    Ansible controller setup (opens on GitHub)

    Turning a plain Ubuntu VM into the controller: service accounts, static addressing, SSH keys and Windows reachability.

    Earlier buildRead ↗PDF ↗

Planning

1 entry
Show all 1
  1. 12
    Lab 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 entries

Problems met during the build, what caused them and what fixed them.

Show all 5
  1. T1
    Network infrastructure (opens on GitHub)

    pfSense and switching problems, and an emergency recovery procedure for losing the network.

  2. T2
    Remote access (opens on GitHub)

    Tailscale diagnostics and how to get back in when remote access fails.

  3. T3
    Ansible automation (opens on GitHub)

    Configuration, SSH key and playbook failures, with the checks that isolate each one.

  4. T4
    Windows integration (opens on GitHub)

    WinRM and Windows-side configuration failures when managing Windows from Ansible.

  5. T5
    System administration (opens on GitHub)

    Linux networking, authentication and package problems, and the diagnostic commands that found them.

Evidence

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.

Related writing

Notes from the same work.

All writing