Usage

Bring the lab up, verify it, and find your way around once it’s running.


Table of contents

  1. Bring up the full lab
    1. Installer wrapper
    2. Lifecycle commands (already-created environment)
  2. Credentials
  3. Common commands
  4. RDP access to Windows machines
  5. Optional: Kali attacker box
  6. Fleet and Elastic Defend
    1. Adding Elastic Defend (Endpoint Protection)
    2. Switching protection modes
  7. Verifying the lab is healthy
  8. Provisioning logs
  9. Repository structure
  10. Customisation
    1. Sysmon config
    2. Adjust SIEM VM memory
  11. Notes

Bring up the full lab

Start the SIEM first — the Windows agents wait for the Fleet enrollment token (or, in Splunk mode, just retry the forwarder connection) before enrolling:

# 1. Start the SIEM
vagrant up siem

# 2. Wait until Kibana is available (check logs/credentials.txt for password)
#    → http://localhost:5601

# 3. Start the Windows endpoints — they auto-enroll with Fleet
vagrant up winserver win11

Or all at once (Windows agents will retry until the SIEM is ready, up to 10 min):

vagrant up

First run downloads the boxes (~5 GB Debian, ~8–15 GB each Windows box).

For Splunk or Wazuh instead of ELK, set ENABLE_SPLUNK=true or ENABLE_WAZUH=true on every vagrant up/vagrant provision call — see Splunk / Wazuh. Mutually exclusive with each other and with the ELK default.

Add ENABLE_VELOCIRAPTOR=true for live triage/hunting alongside whichever SIEM stack you picked — not mutually exclusive with anything above, see Velociraptor.

Installer wrapper

install.sh (Linux/macOS) and install.ps1 (Windows) wrap vagrant up with the right env vars, so you don’t have to remember the flag names:

./install.sh                              # ELK (default), no extras
./install.sh --siem splunk --guacamole    # Splunk + Guacamole
./install.sh --siem wazuh                 # Wazuh
./install.sh --debug                      # verbose Vagrant/provisioner output when something fails
./install.sh --destroy                    # vagrant destroy -f (all VMs)
./install.sh --destroy -- win11           # vagrant destroy -f win11 only
.\install.ps1                              # ELK (default), no extras
.\install.ps1 -Siem splunk -Guacamole      # Splunk + Guacamole
.\install.ps1 -Siem wazuh                  # Wazuh
.\install.ps1 -Debug                       # verbose Vagrant/provisioner output when something fails
.\install.ps1 -Destroy                     # vagrant destroy -f (all VMs)
.\install.ps1 -Destroy win11                # vagrant destroy -f win11 only

--debug/-Debug sets VAGRANT_LOG=debug, Vagrant’s own verbose mode — useful when a provisioner fails and the logs/*.log file it wrote isn’t enough to see why (e.g. it died before writing anything, or the failure is in Vagrant itself, not the script). It also captures the whole session — this wrapper’s own output plus Vagrant’s — to logs/debug.log, overwritten fresh on every --debug run, so it’s ready to paste/share after a failure without having to re-run and copy the terminal by hand.

Both are thin wrappers — equivalent to setting the ENABLE_* env vars yourself and running vagrant up directly, shown throughout this page.

Lifecycle commands (already-created environment)

--start/--stop/--suspend/--resume/--reload (-Start/-Stop/ -Suspend/-Resume/-Reload on Windows) act on every VM currently defined in the Vagrantfile, in a fixed order — not on individual VMs, and independent of --siem/--guacamole:

  • Stop order (--stop/--suspend): win11winserversiem — workstation first, domain controller second, SIEM/log backend last.
  • Start order (--start/--resume/--reload): the reverse — siem first, so it’s ready before the Windows endpoints come up.
  • kali (if the environment was created with ENABLE_KALI=true) goes last on start/resume/reload, first on stop/suspend — it has no dependents.
./install.sh --stop      # halt win11, then winserver, then siem
./install.sh --start     # up siem, then winserver, then win11
./install.sh --suspend
./install.sh --resume
./install.sh --reload    # reload siem, then winserver, then win11
./install.sh --status    # vagrant status

--reload runs vagrant reload per VM (restart + re-run provisioners) — use it to re-apply a provisioner fix without a full --destroy/re-create.

If one VM in the sequence fails (or isn’t created), the wrapper warns and continues with the rest rather than aborting the whole sequence.

Credentials

After SIEM provisioning, credentials are written to logs/credentials.txt (ELK) or logs/splunk-credentials.txt (Splunk):

Username : elastic
Password : <auto-generated>
Kibana   : http://localhost:5601
Fleet    : http://localhost:18220

Common commands

vagrant status                  # Status of all VMs
vagrant ssh siem                # SSH into the SIEM
vagrant winrm winserver -c '…'  # Run a command on a Windows VM
vagrant halt                    # Shut down all VMs
vagrant destroy -f              # Destroy and start fresh
vagrant provision siem          # Re-run SIEM provisioning only

Provisioners are named, so individual steps can be re-run without redoing earlier ones:

vagrant provision winserver --provision-with ad-domain    # AD DS forest promotion only
vagrant provision win11 --provision-with domain-join      # Domain join only

ad-domain and domain-join reboot the VM (Vagrant’s reboot: true handles the wait). Set VAGRANT_MAX_REBOOT_RETRY_DURATION (seconds) generously — the default (120s) is too short for AD DS promotion:

VAGRANT_MAX_REBOOT_RETRY_DURATION=900 vagrant provision winserver --provision-with ad-domain

After AD DS promotion, winserver’s WinRM login is Administrator (not the local vagrant account, which stops being valid once the box becomes a Domain Controller — see Notes).

RDP access to Windows machines

VM Host port (VirtualBox) Username Password
Windows Server 2022 localhost:13389 vagrant vagrant
Windows 11 localhost:23389 vagrant vagrant
xfreerdp /v:localhost:13389 /u:vagrant /p:vagrant /dynamic-resolution

Optional: Kali attacker box

A Kali Linux VM (kalilinux/rolling, 192.168.56.100, 4 GB RAM, headless) for driving traffic/attacks against winserver/win11 to generate detections. Off by default — bring your own attack tooling; the only thing provisioned for you is xrdp (scripts/kali-xrdp-setup.sh), for graphical access to the desktop it already ships with.

ENABLE_KALI=true vagrant up kali
./install.sh --kali -- kali          # same thing, via the wrapper

kalilinux/rolling is a full desktop image (~10 GB after first boot) — no smaller official Kali box exists, and no strip-down runs on it.

Access:

vagrant ssh kali
ssh -p 32200 vagrant@localhost   # equivalent, straight from the host

For graphical tools, RDP into the existing XFCE desktop instead of opening the VirtualBox GUI window — FreeRDP-based clients (xfreerdp /dynamic-resolution, Remmina, …) negotiate dynamic resize properly, which VirtualBox’s own guest-display integration doesn’t do reliably headless:

xfreerdp /v:192.168.56.100 /u:vagrant /p:vagrant /dynamic-resolution
# or localhost:33389 from the host, same as vagrant ssh vs. -p 32200 above

xfce4-session allows only one session per user. If you open the VirtualBox GUI window for kali (logging vagrant into the console), RDP sessions as vagrant will fail to start a desktop until that console session is closed. Use one access method at a time.

Reach winserver/win11 from inside kali over the private network (192.168.56.20 / .30), not the other way around.

Fleet and Elastic Defend

After provisioning (ELK mode), both Windows VMs appear as enrolled agents in Fleet → Agents.

Adding Elastic Defend (Endpoint Protection)

  1. Go to Fleet → Agent Policies → Windows Endpoints
  2. Click Add integration
  3. Search for Elastic Defend
  4. Choose preset: EDR Complete
  5. Set protection mode:
    • Detect (monitor only) — logs threats, no blocking; use this to see detections without interference
    • Prevent (block) — actively blocks threats; use this to verify blocking behavior

Switching protection modes

While testing detections    →  Set Elastic Defend to Detect mode
After observing detections  →  Switch to Prevent mode to verify blocking

Changes apply to all enrolled agents within seconds via Fleet policy push.

Verifying the lab is healthy

bash tests/check-lab.sh         # SIEM health (ELK or, with ENABLE_SPLUNK=true, delegates to check-splunk.sh) + agent enrollment
bash tests/check-domain.sh      # minilab.local AD domain health + win11 domain membership
bash tests/check-telemetry.sh   # ELK only - is telemetry actually arriving, not just "is the collector up"

tests/check-lab.ps1 is the PowerShell equivalent of check-lab.sh, for running the same checks from a Windows host.

check-domain.sh verifies the domain using only nmap and native LDAP/Samba client tools (ldapsearch, nmblookup, smbclient) — see Installation to install them.

check-telemetry.sh queries Elasticsearch directly for recent document counts in each data stream from the telemetry work documented in TELEMETRY.md (Sysmon, PowerShell, Defender, DNS, Firewall, Security) — check-lab.sh only confirms the collector/agent side is up, not that any given source is actually producing events.

To generate that activity on demand instead of waiting for it to happen naturally, scripts/generate-telemetry.ps1 (not a provisioner — run it manually) fires 12 benign, self-cleaning actions (logon success/failure, process creation, PowerShell execution, DNS query, network connection, file/registry/service/scheduled-task/account/group changes), each printing the specific event it expects:

vagrant winrm winserver -c 'C:\vagrant\scripts\generate-telemetry.ps1'
vagrant winrm win11     -c 'C:\vagrant\scripts\generate-telemetry.ps1'

Provisioning logs

Each provisioning script writes a timestamped log to logs/ (Vagrant synced folder). After vagrant up check:

logs/
├── credentials.txt                 # Kibana/ES username and password (ELK mode)
├── splunk-credentials.txt          # Splunk admin username and password (ENABLE_SPLUNK=true)
├── elastic-version.txt             # Installed ELK version
├── fleet-enrollment-token.txt      # Token used by Windows agents to enroll (ELK mode)
├── fleet-policy-id.txt             # ID of the 'Windows Endpoints' policy (ELK mode)
├── elk-provision.log               # Full ELK provisioning output
├── splunk-provision.log            # Full Splunk provisioning output (ENABLE_SPLUNK=true)
├── winserver-baseline.log          # Sysmon + Defender/Firewall on WinServer
├── winserver-elastic-agent.log     # Elastic Agent enrollment on WinServer (ELK mode)
├── winserver-splunk-forwarder.log  # Splunk UF install on WinServer (ENABLE_SPLUNK=true)
├── win11-baseline.log              # Sysmon + Defender/Firewall on Win11
├── win11-elastic-agent.log         # Elastic Agent enrollment on Win11 (ELK mode)
└── win11-splunk-forwarder.log      # Splunk UF install on Win11 (ENABLE_SPLUNK=true)

Each log uses [OK] / [ERR] markers and includes connectivity checks.

Repository structure

MiniLab/
├── Vagrantfile
├── install.sh                      # Installer wrapper (Linux/macOS)
├── install.ps1                     # Installer wrapper (Windows)
├── logo/
│   └── logotipo.png
├── splunk/
│   ├── Splunk.License              # License (gitignored, optional - see Splunk docs)
│   └── downloads.list               # wget commands for the Splunk packages (gitignored)
├── scripts/
│   ├── elk-provision.sh                 # ELK 8.x + Fleet Server on siem (default)
│   ├── splunk-provision.sh              # Splunk Enterprise on siem (ENABLE_SPLUNK=true)
│   ├── wazuh-provision.sh               # Wazuh all-in-one on siem (ENABLE_WAZUH=true)
│   ├── winserver-baseline.ps1           # Sysmon + Defender/Firewall on WinServer
│   ├── winserver-audit-policy.ps1       # Category-level Security auditing on WinServer
│   ├── winserver-defender-telemetry.ps1 # Defender/Operational channel on WinServer
│   ├── winserver-dns-telemetry.ps1      # DNS-Server/Analytical + DNS-Client/Operational on WinServer
│   ├── winserver-powershell-transcription.ps1 # PowerShell Transcription on WinServer
│   ├── winserver-firewall-logging.ps1   # pfirewall.log on WinServer
│   ├── winserver-elastic-agent.ps1      # Elastic Agent enrollment on WinServer (default)
│   ├── winserver-splunk-forwarder.ps1   # Splunk UF on WinServer (ENABLE_SPLUNK=true)
│   ├── winserver-wazuh-agent.ps1        # Wazuh agent on WinServer (ENABLE_WAZUH=true)
│   ├── win11-baseline.ps1               # Sysmon + Defender/Firewall on Win11
│   ├── win11-audit-policy.ps1           # Category-level Security auditing on Win11
│   ├── win11-defender-telemetry.ps1     # Defender/Operational channel on Win11
│   ├── win11-dns-telemetry.ps1          # DNS-Client/Operational on Win11
│   ├── win11-powershell-transcription.ps1 # PowerShell Transcription on Win11
│   ├── win11-firewall-logging.ps1       # pfirewall.log on Win11
│   ├── generate-telemetry.ps1           # Manual, self-cleaning telemetry generator (not a provisioner)
│   ├── win11-elastic-agent.ps1          # Elastic Agent enrollment on Win11 (default)
│   ├── win11-splunk-forwarder.ps1       # Splunk UF on Win11 (ENABLE_SPLUNK=true)
│   ├── win11-wazuh-agent.ps1            # Wazuh agent on Win11 (ENABLE_WAZUH=true)
│   ├── ad-domain-setup.ps1              # Promotes winserver to a minilab.local DC
│   ├── domain-join.ps1                  # Joins win11 to minilab.local
│   ├── docker-setup.sh                  # Docker Engine + Compose on siem (ENABLE_GUACAMOLE=true)
│   ├── guacamole-setup.sh               # Deploys guacd + Guacamole webapp on siem (ENABLE_GUACAMOLE=true)
│   ├── velociraptor-provision.sh        # Velociraptor server on siem (ENABLE_VELOCIRAPTOR=true)
│   ├── winserver-velociraptor-agent.ps1 # Velociraptor client on WinServer (ENABLE_VELOCIRAPTOR=true)
│   ├── win11-velociraptor-agent.ps1     # Velociraptor client on Win11 (ENABLE_VELOCIRAPTOR=true)
│   ├── kali-xrdp-setup.sh               # xrdp for graphical access on Kali (ENABLE_KALI=true)
├── docs/                            # This site (GitHub Pages, served from /docs)
└── tests/
    ├── check-lab.sh                # SIEM health check (bash) - ELK, or delegates to check-splunk.sh/check-wazuh.sh
    ├── check-telemetry.sh          # ELK-only: is telemetry actually arriving per source, not just "collector up"
    ├── check-lab.ps1               # SIEM health check (PowerShell) - ELK only for now
    ├── check-splunk.sh             # Splunk-mode health check (ENABLE_SPLUNK=true)
    ├── check-wazuh.sh              # Wazuh-mode health check (ENABLE_WAZUH=true)
    ├── check-domain.sh             # AD domain health check (nmap + LDAP/Samba tools)
    └── check-guacamole.sh          # Guacamole health check (ENABLE_GUACAMOLE=true)

Customisation

Sysmon config

Both Windows baseline scripts download the olafhartong/sysmon-modular merged config. To change it, edit the $SysmonCfgUrl variable in scripts/winserver-baseline.ps1 / scripts/win11-baseline.ps1.

Adjust SIEM VM memory

In Vagrantfile, change vb.memory. Also update the Elasticsearch heap in elk-provision.sh:

# Rule: heap = 50% of VM RAM, 31 GB maximum
-Xms3g
-Xmx3g

Notes

  • Elasticsearch uses HTTP (no TLS) with authentication (elastic user). Do not expose these VMs to external networks.
  • Fleet Server runs on HTTP port 8220 (bound to all interfaces) with --fleet-server-insecure-http — appropriate for an isolated lab.
  • Logstash is installed and available on port 5044 for ingesting non-Agent sources, but Elastic Agent sends directly to Elasticsearch via Fleet.
  • The auto-generated elastic password is stored in logs/credentials.txt (gitignored).
  • Both baseline scripts enable PowerShell Script Block + Module logging (events 4103/4104, Microsoft-Windows-PowerShell/Operational) — needed to see offensive PowerShell TTPs (encoded/obfuscated/in-memory execution) beyond the literal command line Sysmon captures. ELK picks this up via the default Fleet windows integration; Splunk/Wazuh get an explicit inputs.conf/ossec.conf stanza from their respective agent scripts, since neither ships it by default.
  • Sysmon reaches all 3 SIEMs the same way: enabled by default for ELK (Fleet windows integration), explicit inputs.conf/ossec.conf stanza for Splunk/Wazuh (both were previously silently dropping it — see TELEMETRY.md).
  • Windows Defender Operational telemetry also reaches all 3 SIEMs (winserver/win11-defender-telemetry.ps1 + a matching inputs.conf/ossec.conf stanza) — this one needed ELK-side work too, since the Fleet windows integration ships that data stream disabled by default.
  • DNS telemetry (DNS-Server/Analytical on winserver only, DNS-Client/Operational on both) reaches all 3 SIEMs too. ELK needed the most novel fix here: no bundled integration ships a DNS data stream at all, so elk-provision.sh adds two package policies from Elastic’s winlog input package (“Custom Windows Event Logs”) instead of the usual “windows” integration shortcut.
  • winserver/win11-audit-policy.ps1 enable category-level Security auditing (Account Logon, Account Management, Detailed Tracking, DS Access, Logon/Logoff, Object Access, Policy Change, Privilege Use) plus command-line arguments on 4688 — none of this needed SIEM-side changes, it’s all the Security channel.
  • PowerShell Transcription (winserver/win11-powershell-transcription.ps1, C:\PSTranscripts) is collected by all 3 SIEMs too, but as files, not an event channel — ELK via Elastic’s log (Custom Logs, Deprecated but the only file-input package compatible with this lab’s pinned Kibana 8.x), Splunk via monitor://, Wazuh via <localfile log_format="syslog">.
  • Windows Firewall logging (winserver/win11-firewall-logging.ps1, default pfirewall.log path, 32767 KB cap) reaches all 3 SIEMs the same file-based way as PowerShell Transcription — this was the most expensive of the telemetry fixes, since none of the 3 SIEMs have a built-in shortcut for it.
  • winserver is the minilab.local Active Directory Domain Controller. Promoting a Windows box to a DC replaces its local SAM with the domain database, so the local vagrant account stops being a valid login afterward — winserver’s Vagrant WinRM identity is Administrator instead (same vagrant password; it becomes the domain Administrator/Domain Admin automatically). win11 keeps its own local vagrant account unaffected, since domain-joining a member computer doesn’t touch its local SAM.
  • The DSRM (Directory Services Restore Mode) recovery password is V4grant!2026 — separate from day-to-day credentials, only needed to boot a DC into recovery mode, just in case you broke something while playing. ;)

Splunk · Guacamole · Velociraptor


MiniLab SOC

This site uses Just the Docs, a documentation theme for Jekyll.