How to setup an AD lab in Azure

Rich
12 min readDec 24, 2023

--

TL;DR IaC & explanation of how to setup an AD lab in Azure with 2 DCs & 1 domain workstation.

Welcome to Part X of our Cheatsheet Series!

Part I: Mimikatz cheatsheet

Part II: Set-Acl cheatsheet

Part III: Get-Acl cheatsheet

Part IV: Enumerating AD cheatsheet

Part V: Windows reverse shells cheatsheet

Part VI: MS Graph PowerShell cheatsheet

Part VII: Hash cracking cheatsheet

Part VIII: The Credential Theft Shuffle

Part IX: SACLs & Querying Collected Logs

Part X: Setting up a simple AD lab in Azure

Edit to add: I cleaned this project up and made it user friendly after originally posting this. The new version is here. It is menu driven, hence can be used without knowing PowerShell or Azure. There’s also functions to start & stop the lab and Cleanup.ps1 that will remove everything from Azure once the lab is complete.

Background

Someone asked a couple question on Reddit regarding issues they were having following a paid course. Here at test.local we believe that information wants to be free, and should be. This is a big part of why we have never paywalled these howtos, and have no intention to.

It recently came to our attention though that some are charging good money for classes on how to do this stuff. This site for example is charging $19 for a course on how to setup an AD lab in Azure.

I am currently in a college class on Cloud Security with a heavy focus on Azure, hence we had been getting into parts of Azure other than just AAD recently. Therefore the questions on Reddit peaked my interest.

We already put this project on GitHub here, and let the poster on Reddit know. This is just the writeup with an explanation.

Microsoft wants you to try out Azure

It would seem that 15 years ago or so a very smart Marketing Gal at Microsoft pointed out to ‘The Powers That Be’ that it was in their best interest to give away evaluation copies of their products. It is in their best interest to have techies screwing around with their products at home and learning the ins & outs of them. This is because the next time that techie’s boss asks what the company should use that techie will say “We already know and are experienced with deploying, maintaining, and securing VMs in Microsoft Azure. Additionally we already have X techs certified on it.”

As someone who vividly remembers the bad old days of downloading pirated copies of Windows Server 2003 just so they could learn how to do their damn job I really appreciate that Microsoft Marketing Gal. I hope she got promoted ahead of peers.

Infrastructure as Code (IaC)

The point of all that is that Microsoft now happily gives anyone $200 in free Azure credits. This is more than enough to spin up and run this AD lab for free in Azure. We have created, ran, and tested out a few labs already since starting this college class and we still have $189.01 remaining.

The trick is to not leave your VMs running and delete them pretty quickly after you confirm everything works. This is a non-issue if you IaC everything. IaC means that the value lies in your configs, not in the VMs themselves.

Admin notes

The only requirements for this are that:

  • You have an Azure subscription
  • You have PowerShell
  • You have a basic understanding of what subnets, private IPs, DNS servers, and FW ACLs, etc are

We are hardcoding a couple things:

  • The Resource Group is ADLab
  • The VMs are DC1, DC2, and MemberServer
  • The local admin account on all VMs is ADLabLocalAdmin (N/A on DCs once they become DCs)
  • The Domain Admin is ADLabAdmin
  • The Domain is ADLab.local
  • The password to all is MySuperSecurePassword00!!

We also hardcoded a few other things like the Virtual Network name, Subnet name, Public IP name, etc. If you don’t like our names then feel free to change them. Just ensure that you change every instance in the code, otherwise you’ll end up getting an error.

On a sidenote, there is a reason I picked different sizes for the two DC VMs. Azure has a quota on “Total Regional Cores”, aka how many vCPU cores you can have. I’m sure there’s a way to pay more and increase that, but I kept it free and simply limited the second DC’s cores.

Let’s lab it up

This is more of a glorified batch file that a polished function. It works though, we tested it out.

On a sidenote, apparently one of the issues the Reddit poster had was because the course didn’t mention that you have to allow the ports that a DC uses through the NSG in Azure.

Install-Module -Name Az -Repository PSGallery -Force
Update-Module -Name Az -Force
Connect-AzAccount

Install-Module -Name PSReadline
Install-Module -Name Az.Tools.Predictor
Enable-AzPredictor -AllSession

New-AzResourceGroup -Name "ADLab" -Location "East US"

# - - Create & setup networking on the "client" - -

[string]$userName = 'ADLabLocalAdmin'
[string]$userPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)

New-AzVM -ResourceGroupName "ADLab" -Name "MemberServer" -Location "East US" -Image Win2019Datacenter -Size "Standard_B1s" -VirtualNetworkName "ADLabVN" -SubnetName "ADLabSubnet" -Credential $credObject

#Create a public IP
New-AzPublicIpAddress -Name "ADLabPIP" -ResourceGroupName "ADLab" -AllocationMethod Static -Location "East US"
$PIP = Get-AzPublicIpAddress -Name ADLabPIP
$vnet = Get-AzVirtualNetwork -Name "ADLabVN" -ResourceGroupName "ADLab"
$subnet = Get-AzVirtualNetworkSubnetConfig -Name "ADLabSubnet" -VirtualNetwork $vnet
$VM = Get-AzVm -Name "MemberServer" -ResourceGroupName "ADLab"
$NIC = Get-AzNetworkInterface -ResourceGroupName "ADLab" -Name "MemberServer"
$NIC | Set-AzNetworkInterfaceIpConfig -Name "MemberServer" -PublicIPAddress $PIP -Subnet $subnet
$NIC | Set-AzNetworkInterface

Update-AzVm -ResourceGroupName "ADLab" -VM $VM

#Set the client's DNS (Check the DC VM's private IP, 192.168.1.5 was mine)
$PublicNIC = Get-AzNetworkInterface -ResourceGroupName ADLab -Name MemberServer
$PublicNIC.DnsSettings.DnsServers.Add("192.168.1.5")
$PublicNIC.DnsSettings.DnsServers.Add("192.168.1.7")
$PublicNIC.DnsSettings.DnsServers.Add("8.8.8.8")
Set-AzNetworkInterface -NetworkInterface $PublicNIC

# - - Create & setup networking on the DC - -

[string]$userName = 'ADLabAdmin'
[string]$userPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credObject2 = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)

#Create DC1
New-AzVM -ResourceGroupName "ADLab" -Name "DC1" -Location "East US" -Image Win2019Datacenter -Size "Standard_B2s" -VirtualNetworkName "ADLabVN"-SubnetName "ADLabSubnet" -Credential $credObject2

#All ports that a DC uses in the NSG
$RGname="ADLab"
$rulename="Allow_DC_Ports"
$nsgname="DC1"
# Get the NSG resource
$nsg = Get-AzNetworkSecurityGroup -Name $nsgname -ResourceGroupName $RGname
# Add the inbound security rule.
$nsg | Add-AzNetworkSecurityRuleConfig -Name $rulename -Description "Allow app port" -Access Allow `
-Protocol * -Direction Inbound -Priority 3891 -SourceAddressPrefix "*" -SourcePortRange * `
-DestinationAddressPrefix * -DestinationPortRange (53, 88, 123, 389, 445, 464, 636, 3268, 3269, '49152–65535')
# Update the NSG.
$nsg | Set-AzNetworkSecurityGroup

#Set the DC1 NIC to a static IP (Check your VM's private IP after it's created & set the private IP to that)
$PrivateDCIP = (Get-AzNetworkInterface -ResourceGroupName ADLab -Name DC1).IpConfigurations.PrivateIpAddress
$DCNIC = Get-AzNetworkInterface -ResourceGroupName ADLab -Name DC1
$DCNIC.IpConfigurations[0].PrivateIpAddress = $PrivateDCIP
$DCNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$DCNIC.DnsSettings.DnsServers.Add("127.0.0.1")
$DCNIC.DnsSettings.DnsServers.Add("192.168.1.7")
$DCNIC.DnsSettings.DnsServers.Add("8.8.8.8")
Set-AzNetworkInterface -NetworkInterface $DCNIC

# - - Create & setup networking on DC2

[string]$userName = 'ADLabAdmin'
[string]$userPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credObject3 = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)

#Create DC2
New-AzVM -ResourceGroupName "ADLab" -Name "DC2" -Location "East US" -Image Win2019Datacenter -Size "Standard_DS1_v2" -VirtualNetworkName "ADLabVN"-SubnetName "ADLabSubnet" -Credential $credObject3

#All ports that a DC uses in the NSG
$RGname="ADLab"
$rulename="Allow_DC_Ports"
$nsgname="DC2"
# Get the NSG resource
$nsg = Get-AzNetworkSecurityGroup -Name $nsgname -ResourceGroupName $RGname
# Add the inbound security rule.
$nsg | Add-AzNetworkSecurityRuleConfig -Name $rulename -Description "Allow app port" -Access Allow `
-Protocol * -Direction Inbound -Priority 3891 -SourceAddressPrefix "*" -SourcePortRange * `
-DestinationAddressPrefix * -DestinationPortRange (53, 88, 123, 389, 445, 464, 636, 3268, 3269, '49152–65535')
# Update the NSG.
$nsg | Set-AzNetworkSecurityGroup

#Set the DC2 NIC to a static IP (Check your VM's private IP after it's created & set the private IP to that)
$PrivateDCIP = (Get-AzNetworkInterface -ResourceGroupName ADLab -Name DC2).IpConfigurations.PrivateIpAddress
$DCNIC = Get-AzNetworkInterface -ResourceGroupName ADLab -Name DC2
$DCNIC.IpConfigurations[0].PrivateIpAddress = $PrivateDCIP
$DCNIC.IpConfigurations[0].PrivateIpAllocationMethod = "Static"
$DCNIC.DnsSettings.DnsServers.Add("127.0.0.1")
$DCNIC.DnsSettings.DnsServers.Add("192.168.1.5")
$DCNIC.DnsSettings.DnsServers.Add("8.8.8.8")
Set-AzNetworkInterface -NetworkInterface $DCNIC

# - - Start the VMs - -

Start-AzVM -ResourceGroupName ADLab -Name MemberServer
Start-AzVM -ResourceGroupName ADLab -Name DC1
Start-AzVM -ResourceGroupName ADLab -Name DC2

#Run Config the DC
#Run New Forest
#Run Config the Client
#Lasly, comment out the 'Join-ADDomain', add 'Get-WindowsFeature | Where-Object {$_.Name -like "RSAT"} | Install-WindowsFeature', and run 'Config the Client.ps1' again

#Lastly, RDP into the client
#username = ADLab\ADLabAdmin
#password = MySuperSecurePassword00!!
mstsc /v: $PIP.IpAddress

#Knock out whatever AD lab tasks you want, ideally using PowerShell_ISE instead of the GUI :P
#Don't forget to shutdown both VMs whenever you're done labbing!
#If you're extra paranoid, pull your public IP from your home RTR and set it the only allowed IP in the MemberServer's NSG rule for RDP access.
#I only left my VMs running for a few minutes at a time just to verify this setup worked.

As I said, more of a glorified batch file than anything. If you follow along with this I recommend that you run each VM’s creation & setup block by themselves, one at a time. Note down the private IP that Azure gives each VM by default after creating them so you can then use the DC’s private IPs as the DNS servers for each other and for the client.

This only creates one public IP address and gives it to the client. You access the lab by RDPing into the client. You can then RDP from there into the DCs, or just do everything in PowerShell, various RSAT tools, etc.

Configure the VMs

The DC1 and DC2 VMs aren’t DCs yet though. The above creates the VMs, networks them together, and gives you a way to RDP into the lab. You can configure them by running

  • Config the DC.ps1
  • New Forest.ps1
  • Config DC2.ps1
  • Config the Client.ps1

In that order. Be very careful running PS1s as

  • Everything executes as NT AUTHORITY\SYSTEM
  • It’s non-interactive
  • If it hangs for any reason then you are waiting roughly 2 hours for the API to timeout

I learned that last point the hard way after putting

Install-ADDSDomainController -DomainName “ADLab.local” -InstallDns -Credential $credObject

Into Config DC2.ps1 instead of

Install-ADDSDomainController -DomainName “ADLab.local” -InstallDns -Credential $credObject -SafeModeAdministratorPassword $SecureStringPassword -Confirm -Force

Which of course caused the non-interactive connection to hang while it waited for input that couldn’t be input [namely the value for the DSRM password & confirmation].

VM Config PS1s

It’s best to run these one command at a time, batch file style. Just comment out the following commands, run the thing, repeat once Azure confirms the result, and so on. The whole process will still finish within minutes, in less time than it’d take to RDP into the client, RDP into the other VMs as local admin, and setup stuff in the GUI.

All these PS1s are on our GitHub if you’d prefer to just download the entire project: https://github.com/EugeneBelford1995/Setup-a-simple-AD-lab-in-Azure

— — Config the DC.ps1 — -

Install-WindowsFeature AD-Domain-Services

#Run commands & PS1s on Azure VMs
#Start-AzVM -ResourceGroupName "ADLab" -Name "DC1"
#Set-Location ".\CompTIA studying\Lab Domain Projects\00 Ideas\Setup AD lab in Azure"
#Invoke-AzVMRunCommand -VMName "DC1" -ResourceGroupName "ADLab" -CommandId "RunPowerShellScript" -ScriptPath ".\Config the DC.ps1"

— — New Forest.ps1 — -

#Store a password for DSRM
[string]$DSRMPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$SecureStringPassword = ConvertTo-SecureString $DSRMPassword -AsPlainText -Force

# Create New Forest, add Domain Controller
$DomainName = "ADLab.local"
$NetBIOSName = "ADLab"
Install-ADDSForest -CreateDnsDelegation:$false `
-DatabasePath "C:\Windows\NTDS" `
-DomainMode "WinThreshold" `
-DomainName $DomainName `
-DomainNetbiosName $NetBIOSName `
-ForestMode "WinThreshold" `
-InstallDns:$true `
-LogPath "C:\Windows\NTDS" `
-NoRebootOnCompletion:$false `
-SysvolPath "C:\Windows\SYSVOL" `
-Force:$true `
-SafeModeAdministratorPassword $SecureStringPassword

#Run commands & PS1s on Azure VMs
#Start-AzVM -ResourceGroupName "ADLab" -Name "DC1"
#Set-Location ".\CompTIA studying\Lab Domain Projects\00 Ideas\Setup AD lab in Azure"
#Invoke-AzVMRunCommand -VMName "DC1" -ResourceGroupName "ADLab" -CommandId "RunPowerShellScript" -ScriptPath ".\New Forest.ps1"

— — Config DC2.ps1 — -

#Store a password for DSRM
[string]$DSRMPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$SecureStringPassword = ConvertTo-SecureString $DSRMPassword -AsPlainText -Force

#Store the Domain Admin creds
[string]$userName = 'ADLabAdmin@ADLab.local'
[string]$userPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)

Get-WindowsFeature | Where-Object {$_.Name -like "*RSAT*"} | Install-WindowsFeature -IncludeAllSubFeature

Install-WindowsFeature AD-Domain-Services

Add-Computer -DomainName ADLab.local -Credential $credObject -restart -force

Install-ADDSDomainController -DomainName "ADLab.local" -InstallDns -Credential $credObject -SafeModeAdministratorPassword $SecureStringPassword -Confirm -Force

#Run commands & PS1s on Azure VMs
#Start-AzVM -ResourceGroupName "ADLab" -Name "DC2"
#Set-Location ".\CompTIA studying\Lab Domain Projects\00 Ideas\Setup AD lab in Azure"
#Invoke-AzVMRunCommand -VMName "DC2" -ResourceGroupName "ADLab" -CommandId "RunPowerShellScript" -ScriptPath ".\Config DC2.ps1"

— — Config the client.ps1 — -

[string]$userName = 'ADLabAdmin@ADLab.local'
[string]$userPassword = 'MySuperSecurePassword00!!'
# Convert to SecureString
[securestring]$secStringPassword = ConvertTo-SecureString $userPassword -AsPlainText -Force
[pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)

Add-Computer -DomainName ADLab.local -Credential $credObject -restart -force

Get-WindowsFeature | Where-Object {$_.Name -like "*RSAT*"} | Install-WindowsFeature -IncludeAllSubFeature

$TargetMachine = "MemberServer"
(Get-WmiObject -class "Win32_TSGeneralSetting" -Namespace root\cimv2\terminalservices -ComputerName $TargetMachine -Filter "TerminalName='RDP-tcp'").SetUserAuthenticationRequired(0)

#Run commands & PS1s on Azure VMs
#Start-AzVM -ResourceGroupName "ADLab" -Name "MemberServer"
#Set-Location ".\CompTIA studying\Lab Domain Projects\00 Ideas\Setup AD lab in Azure"
#Invoke-AzVMRunCommand -VMName "MemberServer" -ResourceGroupName "ADLab" -CommandId "RunPowerShellScript" -ScriptPath ".\Config the Client.ps1"

Important note regarding NLA

If you are like me and you forget to disable NLA in Group Policy as soon as you configured the VMs you will find yourself essentially locked out of MemberServer once it pulls Group Policy. This is because Windows domains enforce NLA by default. This is why I have the bit regarding Terminal Services at the end of Config the Client.ps1.

Alternatively one can simply disable NLA via

Invoke-AzVMRunCommand -VMName “MemberServer” -ResourceGroupName “ADLab” -CommandId “DisableNLA”

Make sure you go into Group Policy and disable NLA as soon as you RDP back in:

Computer Configuration \ Administrative Templates \ Windows Components \ Remote Desktop Services \ Remote Desktop Session Host \ Security -> Require user authentication for remote connections by using Network Level Authentication : Set to Disabled

Confirm the setup

Now one can RDP into the public IP that belongs to the MemberServer VM, and then knock out whatever AD related training they wanted to do.

Confirm Replication

There was one other thing in the summary of that paid course that I noticed on their website; confirming AD replication between the 2 DCs.

An easy way to do this from the MemberServer is

Get-ADReplicationPartnerMetadata -Target "ADLab.local" -Scope Domain | Select-Object LastReplicationSuccess, Server

Get-ADReplicationFailure -Target ADLab.local -Scope Domain | Out-GridView

Summary

We go over a lot of arcane Windows domain security stuff here at test.local, but up to now we have only done one walkthrough of how to setup an AD home lab, that one being using Hyper-V. We gave a bit of an overview of how we setup our main lab environment in the free version of ESXi here. The issue with that though is that while a version of VMware ESXi is free, the PowerCLI for it is read only.

I have been meaning to get a server rack and one or two more refurbished server blades eventually. This would allow us to easily run 3 different hypervisors, have DCs on each, add some redundancy to our home lab, and get some good practice doing IaC. I just haven’t brought myself to spend the money yet in this era of hyperinflation.

This was good practice with using Azure as a hypervisor. I learned something, most importantly to be very careful with the commands in the VM config PS1s. If you make an error then you’ll have to wait for the timeout.

On a final sidenote, the astute reader might be wondering why we used the Azure AD module for this instead of Microsoft Graph. The reason is because it works for now at least, it was intuitive and quick to get spun up, and the Reddit poster needed to get up & running and learning AD ASAP. If Microsoft kills off the Azure AD module in 2024 then I can learn how to create & manage Azure VMs using MS Graph then. We already made a cheatsheet on using MS Graph to manage AAD here.

I should really get back to that college paper now :(

Did I ever mention I only like writing if it’s technical writing?

References

PSCredential object: https://pscustomobject.github.io/powershell/howto/PowerShell-Create-Credential-Object/

New-AzVM -Image : https://learn.microsoft.com/en-us/powershell/module/az.compute/new-azvm?view=azps-11.1.0

Azure lab module: https://learn.microsoft.com/en-us/azure/lab-services/how-to-create-lab-powershell

Add/remove NICs from an Azure VM: https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-network-interface-vm

Add a second NIC to an Azure VM: https://learn.microsoft.com/en-us/azure/virtual-machines/windows/multiple-nics#add-a-nic-to-an-existing-vm

Setup static IP & DNS on an Azure NIC: https://learn.microsoft.com/en-us/powershell/module/az.network/set-aznetworkinterface?view=azps-11.1.0

Store DSRM password as a SecureString: https://learn.microsoft.com/en-us/powershell/module/addsdeployment/install-addsdomaincontroller?view=windowsserver2022-ps

Disable NLA via PowerShell and/or GPO: https://www.anyviewer.com/how-to/disable-network-level-authentication-2578.html

Add RSAT on Windows Server: https://woshub.com/install-rsat-feature-windows-10-powershell/

Setup a DC using PowerShell: https://onkelx.nl/2017/07/27/setup-first-domain-controller-using-powershell/

Check replication status, errors, etc: https://techcommunity.microsoft.com/t5/itops-talk-blog/powershell-basics-how-to-check-active-directory-replication/ba-p/326364

Azure AD module timeline: https://techcommunity.microsoft.com/t5/microsoft-entra-blog/azure-ad-change-management-simplified/ba-p/2967456

Disallowed usernames & passwords for VMs (Ctrl + F “adminUsername”) : https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/create-or-update?view=rest-compute-2023-09-01&tabs=HTTP#osprofile

Microsoft guidance on MS Graph vs Azure AD Graph: https://learn.microsoft.com/en-us/graph/migrate-azure-ad-graph-overview

--

--

Rich

I work various IT jobs & like Windows domain security as a hobby. Most of what’s here is my notes from auditing or the lab.