# GOAD

This is my internal notes for getting [GOAD - Game of Active Directory](https://github.com/Orange-Cyberdefense/GOAD) up and running on Proxmox.

## GOAD install (Proxmox + Ludus)

### Install essentials
```
apt update && apt install curl sudo tmux -y
curl -s https://ludus.cloud/install | bash
```

Server may reboot a few times and you'll lose SSH connectivity temporarily.

#### Check install status
```
ludus-install-status
```

At the end of the install you'll get an API key.  Write it down!  You'll need it for the next step.

### Make an admin user
```
export LUDUS_API_KEY='ROOT.xxx'
ludus user add --name "Brian Johnson" --userid brian --admin --url https://127.0.0.1:8081
```

#### Get that user's Proxmox creds
```
export LUDUS_API_KEY='brian.xxx'
ludus user creds get
```

### Download GOAD
```
sudo apt install python3.11-venv  
export LUDUS_API_KEY='brian.123@xxxx'
git clone https://github.com/Orange-Cyberdefense/GOAD.git ~/GOAD

```

### Install Ludus
```
git clone https://gitlab.com/badsectorlabs/ludus ~/ludus
cd ~/ludus/templates
```

#### Build VM templates - 4 at a time
Note: before you do, edit `~/GOAD/extensions/ws01/providers/ludus/config.yml` and put `win10-22h2-x64-enterprise-template` as the extension instead of the Win10 template name that comes by default.  Here's what the file will then look like:

```
- vm_name: "-GOAD-WS01"
    hostname: "-WS01"
    template: win10-22h2-x64-enterprise-template
    vlan: 10
    ip_last_octet: 31
    ram_gb: 4
    cpus: 2
    windows:
      sysprep: true
```

Now you can build the templates:

```
# Add the templates you need for a full GOAD load:
cd ~/ludus/templates
ludus templates add -d ubuntu-22.04-x64-server
ludus templates add -d win10-22h2-x64-enterprise
ludus templates add -d win2016-server-x64
ludus templates add -d win2019-server-x64
ludus templates build -p 4

# Check the template list
ludus templates list

# Example output of templates list
+------------------------------------+-------+
|              TEMPLATE              | BUILT |
+------------------------------------+-------+
| debian-11-x64-server-template      | TRUE  |
| debian-12-x64-server-template      | TRUE  |
| kali-x64-desktop-template          | TRUE  |
| win11-22h2-x64-enterprise-template | TRUE  |
| win2022-server-x64-template        | TRUE  |
| ubuntu-22.04-x64-server-template   | TRUE  |
| win10-22h2-x64-enterprise-template | TRUE  |
| win2016-server-x64-template        | TRUE  |
| win2019-server-x64-template        | TRUE  |
+------------------------------------+-------+
```

Note: normally you can run `ludus templates status` to check status of the template install, but this will fail if you're building multiple templates at once with the `-p` flag.

### Install GOAD
```
cd ~/GOAD
./goad.sh -p ludus 

# Check the install
check

# Set lab to GOAD/GOAD-Light/NHA/SCCM/etc  
set_lab GOAD

# Set the first three octets of the range (optional)
set_ip_range 10.3.10

# Install
install
```

### Install the GOAD extensions (optional)
```
install_extension elk
install_extension exchange
install_extension ws01
install_extension wazuh
```

### Check range status

```
ludus range config get
```

```
ludus range status --user GOAD[plus numbers/digits that appear after the VMs in your lab
```

### Snapshot VMs (optional)
See [this guide](https://docs.ludus.cloud/docs/snapshots/), but in general you could snapshot the whole range with:

```
ludus snapshots create <snapshot-name>
```

### Redeploy a range
You can follow these steps to redeploy a range:

#### Redeploy just a specific VM
...one that might be having license issues (where 180 count is not resetting, for example):

```
ludus range deploy --limit GOAD0ffddd-GOAD-SRV01 --user GOAD0ffddd-GOAD-SRV01
````

Watch logs:
```
ludus range logs -f --user GOAD0ffddd 
```

More information about adding a single VM to a range [here](https://docs.ludus.cloud/docs/tags/#adding-a-single-vm-to-a-range-then-configuring-it)

#### Redeploy the WHOLE range

```
# Run GOAD shell and specify Ludus as provider
cd ~/GOAD
./goad.sh -p ludus

# List instances
list

# "Use" the one that needs redeployment
load xxx-goad-ludus (whatever the range name is)

# Kill it with fire (you'll be asked to confirm the destruction)!
destroy

# Keep tabs on its death
status
```

After "death" I found a problem (discussed [here](https://discord.com/channels/1216801602518782042/1421653839630700755) where even after rebuilding the range, the Windows VMs thought they only had a few days left of licensing (despite doing `slmgr /rearm` and rebooting).

So I trashed all the templates with:
```
ludus templates rm -n ubuntu-22.04-x64-server-template
ludus templates rm -n win10-22h2-x64-enterprise-template
ludus templates rm -n win2016-server-x64-template
ludus templates rm -n win2019-server-x64-template
```

Then rebuild the templates:
```
# Readd the templates (sanity check)
cd ~/ludus/templates
ludus templates add -d ubuntu-22.04-x64-server
ludus templates add -d win10-22h2-x64-enterprise
ludus templates add -d win2016-server-x64
ludus templates add -d win2019-server-x64

# Build the templates
ludus templates build -p 4

# Check status on builds
ludus templates list

```

Before redeploying, I've found that the `/root/.ssh/known_hosts` file needs to have its previous GOAD hosts cleared out so that you don't get SSH key/security issues on next deployment.  In those cases, I do this (assuming 10.3.10.x is my old subnet):

```
# Backup first just in case
cp /root/.ssh/known_hosts /root/.ssh/known_hosts.bak

# Remove all offending entries
for ip in $(seq 1 254); do
  ssh-keygen -f "/root/.ssh/known_hosts" -R "10.3.10.$ip"
done
```

### Troubleshooting

#### Elasticsearch reset
I had an issue where Elasticsearch dashboard was all jacked up with messages like:

```
“Failed to retrieve privileges”
“License is not available”
“Kibana server is not ready yet”
```

#### Reset Elasticsearch passwords
```
/usr/share/elasticsearch/bin/elasticsearch-setup-passwords interactive
```
#### Test the new password
```
curl -u elastic:NewPass2026!!! http://10.3.10.50:9200
````

#### Update Kibana config
In `/etc/kibana/kibana.yml` add:

```
elasticsearch.hosts: ["http://10.3.10.50:9200"]
elasticsearch.username: "elastic"
elasticsearch.password: "NewPass2026!!!"
```
#### Restart services
```
sudo systemctl restart elasticsearch
sudo systemctl restart kibana
```

#### Monitor logs
```
sudo journalctl -u kibana -f
```

#### Check that dashboard are actually working
```
Elasticsearch accessible at http://10.3.10.50:9200
Kibana working at http://10.3.10.50:5601
```

### Installing/reinstalling just certain parts of a GOAD install
From the author:
```
Yes you can retry certain parts. Use `>provision_extension wazuh` to relaunch only wazuh provision once instance is selected.
```

#### Wazuh manual agent install
Sometimes when installing/reinstalling goad, one or more servers complain that the Wazuh installation fails (typically SRV01).  So I'll jump into that machine's VNC console in Proxmox and do this:

```
invoke-webrequest https://packages.wazuh.com/4.x/windows/wazuh-agent-4.8.2-1.msi -outfile c:\tmp\wazuh-agent
```

!!!alert
Sometimes the *THE-EYRIE* box (the one that Wazuh always fails to install for me, aka *SRV01*) is stupid and won't even process the `invoke-webrequest` command.  In those instances I hopped onto the DC and downloaded the MSI to `\\kingslanding\NETLOGON\` so that *THE-EYRIE* can see it.
!!!

There was also one time I had to do the next step, which is complete the manual install:

```
msiexec.exe /i c:\tmp\wazuh-agent /q WAZUH_MANAGER=ip.of.wazuh.box WAZUH_REGISTRATION_SERVER=ip.of.wazuh.box
```

Then I just run the GOAD install command again:

```
install
```

#### Wazuh server default password
Per [this issue](https://github.com/Orange-Cyberdefense/GOAD/issues/433), if you need to get the various Wazuh passwords, login at the console (username: *localuser*, password: *password*) and then check the end of this file for the default Wazuh install password:
```
/opt/wazuh/wazuh-install-output.txt
```

#### Winlogbeat install
Sometimes Windows systems don't get the `winlogbeat.zip` properly so I manually visit them from VNC console and install via PowerShell:

```
invoke-webrequest https://artifacts.elastic.co/downloads/beats/winlogbeat/winlogbeat-7.17.6-windows-x86_64.zip -outfile "c:\program files\elastic\winlogbeat\winlogbeat.zip"
````

### Debian router config

#### iptables rules
I use a [Hetzner](/software/hetzner) server to setup two networks in my GOAD environment.  One is configured with an extra external IP where the firewall terminates, and that LAN is 192.168.1.x.  So in order to then have an Apache Guacamole server traverse from 192.168.1.x I like to back up the current iptables config and then modify a rule to allow traffic:

```
# Update and install iptables-persistent module
sudo apt update
sudo apt install iptables-persistent

# Backup current config
sudo iptables-save > 2025-xx-xx-iptables.original

# Punch in rules to allow 192.168.1.x to talk to the GOAD network of 10.3.10.x
sudo iptables -I LUDUS_DEFAULTS 2 -s 192.168.1.111/32 -d 10.3.0.0/16 -m comment --comment "192.168.1.111/32 IP to range forward rule" -j ACCEPT
sudo iptables -I LUDUS_DEFAULTS 3 -s 192.0.2.49/32 -d 10.3.0.0/16 -m comment --comment "opnsense to range forward rule" -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 10.3.10.0/24 ! -d 198.51.100.0/24 -o ens18 -j MASQUERADE

# Make rules persistent
sudo netfilter-persistent save

# Save the rules one more time
sudo iptables-save > 2025-xx-xx-iptables.golden
```

#### Block LAN to WAN traffic
I had a few instances where Hetzner was complaining about scanning activity from inside my GOAD network.  I could never figure out if it was indeed coming from students, but had this rule ready to block all GOAD-to-Internet traffic with this iptables rule if necessary:

```
# Block 10.3.10.x from hitting the Internet
sudo iptables -I LUDUS_USER_RULES 1 -s 10.3.10.0/24 -j DROP

# Remove rule when the coast is clear
sudo iptables -D LUDUS_USER_RULES -s 10.3.10.0/24 -j DROP
```

### Extend Windows licenses
You can extend them (I think) up to 3 times for a total of 540 days.  When the countdown is running low on a Windows system, do this from cmd prompt:

```
slmgr /rearm
```

Check license expiration:

```
slmgr /xpr
```

More information about the technical bits behind license re-arm procedures, read [this](https://discord.com/channels/1216801602518782042/1216801603290534012/1339612177413312652).

## Install (AWS)

### Create AWS IAM user and assign permissions
1. In your AWS console, go under **IAM > User** and create a user (I called mine *GOAD*).  
2. In the *Permissions policies* area choose **Add permissions > Add permissions** then click **Attach Policies Directly** and choose **AdministratorAccess** from the list.

!!!danger Danger
I get it - this is giving the *GOAD* user too much permission.  But the AWS account I'm using is used for nothing but GOAD testing, so I don't care too much about this instance getting pwned.  
!!!

### Install aws cli
More info on the [AWS docs]()
```
sudo apt update
sudo apt install zip -y
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
```

### Configure aws cli
```
aws configure
```
**When asked about region name, put `eu-west3`.**

Once you go through the prompts, you'll end up with a `~/.aws/credentials` file that looks something like this:

```
[default]
aws_access_key_id = xxx
aws_secret_access_key = xxx
```

Copy and paste this section again, but change the header name from `[default]` to `[goad]` so the file ends up looking like this:

```
[default]
aws_access_key_id = xxx
aws_secret_access_key = xxx

[goad]
aws_access_key_id = xxx
aws_secret_access_key = xxx
```

### Install terraform
Up-to-date instructions [here](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli).

### Install GOAD
```
sudo apt install python3.12-venv -y
git clone https://github.com/Orange-Cyberdefense/GOAD.git ~/goad
./goad.sh -p aws
```

A bunch of stuff will install and then you'll end with a screen like this:

```
   _____   _____          _____ 
  / ____| / ||| \   /\   |  __ \
 | |  __||  |||  | /  \  | |  | |
 | | |_ ||  |||  |/ /\ \ | |  | |
 | |__| ||  |||  / /__\ \| |__| |
  \_____| \_|||_/________\_____/
    Game Of Active Directory
      Pwning is coming

Goad management console type help or ? to list commands

[*] goad config file not found, create file /home/packie/.goad/goad.ini
[-] provisioner method local is not allowed for provider aws 
[*] automatic changing provisioner method local to default for this provider : remote
[*] Start Loading default instance
[*] lab instances :
[-] No instance found, change your config and use install to create a lab instance 

GOAD/aws/remote/192.168.56.X > 
```

### Deploy a range
From the `GOAD/aws/remote/192.168.56.X >` prompt, set the range to what you want, i.e.:

```
set_lab GOAD-LIGHT
install
# You'll be asked "Create lab with these settings?" Say yes/no.
```

Right before kicking off deployment, you'll get a screen like this.  Take note of username/password information for future reference, and/or review the creds in `~/goad/ad/NAME-OF-GOAD-INSTALL/providers/NAME-OF-PROVIDER/windows.tf `

```
Changes to Outputs:
  + ubuntu-jumpbox-ip       = (known after apply)
  + ubuntu-jumpbox-username = "goad"
  + vm-config               = {
      + dc01  = {
          + ami                = "ami-03440f0d88fea1060"
          + domain             = "sevenkingdoms.local"
          + instance_type      = "t2.medium"
          + name               = "dc01"
          + password           = "xxx"
          + private_ip_address = "192.168.56.10"
          + windows_sku        = "2019-Datacenter"
        }
      + dc02  = {
          + ami                = "ami-03440f0d88fea1060"
          + domain             = "north.sevenkingdoms.local"
          + instance_type      = "t2.medium"
          + name               = "dc02"
          + password           = "xxx"
          + private_ip_address = "192.168.56.11"
          + windows_sku        = "2019-Datacenter"
        }
      + srv02 = {
          + ami                = "ami-03440f0d88fea1060"
          + domain             = "north.sevenkingdoms.local"
          + instance_type      = "t2.medium"
          + name               = "srv02"
          + password           = "xxx"
          + private_ip_address = "192.168.56.22"
          + windows_sku        = "2019-Datacenter"
        }
    }
  + windows-vm-username     = "goadmin"
```

### Share the range with a third party (like Coursestack)
If you want to share these AMIs with a third party (like Coursestack), first shutdown the range:

At the `GOAD-Light/aws/remote/192.168.56.X (be5faa-goad-light-aws)` type **stop** and hit **Enter**.  You can then type **status** a few times and wait until everything has a status of `stopped`.  

Then in AWS console, right-click your instance and choose **Image and templates > Create image.**  Give it a name and description.  Leave the other options as a default (like *Image description*, the ticked *Reboot instance* box and *Tag image and snapshots together* box).

Once you do this, head to the **AMI** area of AWS console and you should see your new AMIs.  Wait for them to move from status of *Pending* to *Available*.  Then right click each AMI, choose **Edit AMI permissions**.  At the next screen, click **Add account ID** and add your third party AWS account.  Then click **Save changes** at the bottom of the screen.  

Follow the same process under the **Snapshots** section (edit permissions and add the third party AWS account).

### Troubleshooting
As of me going through this process on Jan 5, 2026, there's an open [issue](https://github.com/Orange-Cyberdefense/GOAD/issues/472) where deployment will fail with something like:

```
Error: creating EC2 Instance: InvalidParameterCombination: The specified instance type is not eligible for Free Tier. For a list of Free Tier instance types, run 'describe-instance-types' with the filter 'free-tier-eligible=true'.
│       status code: 400, request id: 7662731b-13dd-48c0-a8a1-19b787fb03c5
│ 
│   with aws_instance.goad-vm-jumpbox,
│   on jumpbox.tf line 15, in resource "aws_instance" "goad-vm-jumpbox":
│   15: resource "aws_instance" "goad-vm-jumpbox" {
│ 
╵
╷
│ Error: creating EC2 Instance: AuthFailure: Not authorized for images: [ami-03440f0d88fea1060]
│       status code: 400, request id: 8d91b727-65ed-45d4-8db6-db67769f9b13
│ 
│   with aws_instance.goad-vm["dc02"],
│   on windows.tf line 59, in resource "aws_instance" "goad-vm":
│   59: resource "aws_instance" "goad-vm" {
│ 
╵
╷
│ Error: creating EC2 Instance: AuthFailure: Not authorized for images: [ami-03440f0d88fea1060]
│       status code: 400, request id: dee92afa-b139-410b-a323-2fcfd219a317
│ 
│   with aws_instance.goad-vm["srv02"],
│   on windows.tf line 59, in resource "aws_instance" "goad-vm":
│   59: resource "aws_instance" "goad-vm" {
│ 
╵
╷
│ Error: creating EC2 Instance: AuthFailure: Not authorized for images: [ami-03440f0d88fea1060]
│       status code: 400, request id: f9fd06be-53a4-4e20-9122-b7ba31558380
│ 
│   with aws_instance.goad-vm["dc01"],
│   on windows.tf line 59, in resource "aws_instance" "goad-vm":
│   59: resource "aws_instance" "goad-vm" {
│ 
╵
[-] Providing error stop 

```

The issue is that the AMI numbers are outdated:

```
aws ec2 describe-images \
        --region eu-west-3 \
        --owners "amazon" \
        --filters "Name=name,Values=Windows_Server-2019-English-Full-Base*" \
        --query 'Images[*].[ImageId,Name,CreationDate]' \
        --output table
-----------------------------------------------------------------------------------------------------------
|                                             DescribeImages                                              |
+-----------------------+----------------------------------------------------+----------------------------+
|  ami-02a45af02cb289e37|  Windows_Server-2019-English-Full-Base-2025.10.15  |  2025-10-17T06:33:35.000Z  |
|  ami-0a4c1700182f3bc09|  Windows_Server-2019-English-Full-Base-2025.11.12  |  2025-11-12T23:01:57.000Z  |
|  ami-0a92dbc1c111021cd|  Windows_Server-2019-English-Full-Base-2025.09.10  |  2025-09-12T04:00:23.000Z  |
+-----------------------+----------------------------------------------------+----------------------------+
```

In my case I'm using AWS as the provider and *GOAD-LIGHT* as the lab, so I did:

```
nano ~/goad/ad/GOAD-Light/providers/aws/windows.tf 
```

Then I replaced every instance of `ami-03440f0d88fea1060` with `ami-0fc132be18e175019`.

Then, back at the `GOAD/aws/remote/192.168.56.X >` prompt:

```
destroy
# Follow prompts/confirmations to nuke the existing half-baked install

# update the instance files to pick up on the changes to the windows.tf file
update_instance_files

# reinstall
install
```

After that if you *still* have a problem, and you're using a freshly-created AWS account, you may need to click into **Billing and Cost Management** header, and where it says *Your free plan account does not get charged*, click **Upgrade plan** and follow the prompts to enable a proper AWS paid account.  Once I did that and destroyed the existing instance, ran `update_instance_files` and then the `install` command, everything ran like a champ!
