
How to Deploy Active Directory (AD) Domain Controller (DC) Virtual Machine (VM) in Azure with Terraform
Thousands of companies worldwide rely on Active Directory for user authentication; these companies commonly implement a few domain controllers in Azure, running on a Virtual Machine (VM) to authenticate the user and applications running on the cloud.
In this story, we will deploy two AD DCs. The first one, DC1, will create a new AD Domain Forest in the cloud, and the second one, DC2, will join this new AD Domain.
These examples will help you to create different scenarios, such as AD Domain on the cloud, hybrid AD deployments when you join existing AD on-premises, create test environments on the cloud, etc.
The code can be used to deploy one or more AD DCs, with a little code editing.
Note: for hybrid scenarios, make sure that you set the on-premises DNS on the NIC of new VMs and that there are network routes in place, or the machines will not be able to join the existing AD Domain.
Active Directory in Cloud Environments
This story is part of my Active Directory in Cloud Environments series.
I have been deploying Active Directory in AWS, Azure, GCP, and OCI cloud environments for +10 years. I have been using AD since Microsoft launched the public beta in 1999, so this is one of my favorite subjects to write about.
- How to Deploy AWS Directory Service using Terraform
- How to Deploy Active Directory (AD) Domain Controller (DC) Virtual Machine (VM) in Azure with Terraform (this story)
- How to Deploy Azure Active Directory (AD) Domain Services with Terraform
- Automating Microsoft AD and DNS with Terraform & KopiCloud AD API
- How to Create and Manage AD Users with Terraform
1. Requirements
To deploy AD DCs in Azure VMs, we will need the following:
- Define the Azure Provider
- Create a Resource Group
- Create a VNET
- Create a Subnet
- Create NSG (Network Security Group) for Client Machines to AD Domain Controllers.
- Create NSG (Network Security Group) for Communications between Domain Controllers.
- Create a NIC (Network Card) in this Subnet
- Create the Virtual Machine to Create a New AD Forest and Domain
- Create the Virtual Machine to Join an Existing Domain
2. Prerequisites
Before creating our Azure Virtual Machine, we will need an Azure SPN (Service Principal) to execute our Terraform code (check step 1 of this story if you need help creating an SPN).
3. Defining the Azure Provider
First, we will define Azure authentication variables.
We will use a Service Principal with a Client Secret. Check the link below for more info about Azure authentication for Terraform: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
variable "azure_subscription_id" {
type = string
description = "Azure Subscription ID"
}
variable "azure_client_id" {
type = string
description = "Azure Client ID"
}
variable "azure_client_secret" {
type = string
description = "Azure Client Secret"
}
variable "azure_tenant_id" {
type = string
description = "Azure Tenant ID"
}Then, we will configure Terraform and the Azure provider:
# Define Terraform provider
terraform {
required_version = "~> 1.3"
}
# Configure the Azure provider
provider "azurerm" {
features {}
environment = "public"
subscription_id = var.azure-subscription-id
client_id = var.azure-client-id
client_secret = var.azure-client-secret
tenant_id = var.azure-tenant-id
}4. Creating a Resource Group
In this step, we will create an Azure Resource Group, where we will store all the Azure resources created by our Terraform code.
We will start defining the location variable:
# azure region variable
variable "location" {
type = string
description = "Azure region for the resource group"
default = "north europe"
}And then the code to create the resource group:
resource "azurerm_resource_group" "network-rg" {
name = "network-rg"
location = var.location
}5. Creating a VNET and Subnet
Now, we will create the variables to configure the network:
variable "network-vnet-cidr" {
type = string
description = "The CIDR of the network VNET"
}
variable "network-subnet-cidr" {
type = string
description = "The CIDR for the network subnet"
}And then the code to create the VNET and the Subnet:
# Create the network VNET
resource "azurerm_virtual_network" "network-vnet" {
name = "network-vnet"
address_space = [var.network-vnet-cidr]
resource_group_name = azurerm_resource_group.network-rg.name
location = azurerm_resource_group.network-rg.location
}
# Create a subnet for VM
resource "azurerm_subnet" "ad-subnet" {
name = "vm-subnet"
address_prefixes = [var.ad-subnet-cidr]
virtual_network_name = azurerm_virtual_network.network-vnet.name
resource_group_name = azurerm_resource_group.network-rg.name
}6. Defining Active Directory Variables
In this step, we will define variables used to configure the Active Directory.
# active directory domain name
variable "ad_domain_name" {
type = string
description = "This variable defines the name of Active Directory domain, for example kopicloud.local"
}
# active directory domain NetBIOS name
variable "ad_domain_netbios_name" {
type = string
description = "This variable defines the NETBIOS name of Active Directory domain, for example kopicloud"
}
# active directory domain mode
variable "ad_domain_mode" {
type = string
description = "This variable defines the mode of Active Directory domain and forest functional level"
default = "WinThreshold" # Windows Server 2016
}
# local administrator username
variable "ad_admin_username" {
type = string
description = "The username associated with the local administrator account on the virtual machine"
}
# local administrator password
variable "ad_admin_password" {
type = string
description = "The password associated with the local administrator account on the virtual machine"
}
# active directory database path
variable "ad_database_path" {
type = string
description = "The active directory database path"
default = "C:/Windows/NTDS"
}
# active directory sysvol path
variable "ad_sysvol_path" {
type = string
description = "The active directory SYSVOL path"
default = "C:/Windows/SYSVOL"
}
# active directory log path
variable "ad_log_path" {
type = string
description = "The active directory log path"
default = "C:/Windows/NTDS"
}
# active directory safe mode administrator password
variable "ad_safe_mode_administrator_password" {
type = string
description = "The active directory safe mode administrator password"
}7. Defining Operating System Variables
Here, we will define the Windows_xxxx_SKU variable used to create the AD DC VMs.
# Windows Server 2022 SKU used to build VMs
variable "windows_2022_sku" {
type = string
description = "Windows Server 2022 SKU used to build VMs"
default = "2022-Datacenter"
}
# Windows Server 2019 SKU used to build VMs
variable "windows_2019_sku" {
type = string
description = "Windows Server 2019 SKU used to build VMs"
default = "2019-Datacenter"
}
# Windows Server 2016 SKU used to build VMs
variable "windows_2016_sku" {
type = string
description = "Windows Server 2016 SKU used to build VMs"
default = "2016-Datacenter"
}8. Create an Availability Set for Domain Controllers
And now, we will create an Availability Set for Virtual Machines so Azure can deploy the AD DCs in different racks in their data centers.
resource "azurerm_availability_set" "dc-availability-set" {
name = "dc-availability-set"
resource_group_name = azurerm_resource_group.network-rg.name
location = azurerm_resource_group.network-rg.location
platform_fault_domain_count = 3
platform_update_domain_count = 5
managed = true
}9. Defining Variables for AD DC1 VM
In this step, we will define the variables we will use to deploy the first VM.
# Azure virtual machine settings #
variable "dc1_vm_size" {
type = string
description = "Size (SKU) of the virtual machine to create"
default = "Standard_B2s"
}
variable "dc1_license_type" {
type = string
description = "Specifies the BYOL type for the virtual machine. Possible values are 'Windows_Client' and 'Windows_Server' if set"
default = null
}
# Azure virtual machine storage settings #
variable "dc1_delete_os_disk_on_termination" {
type = string
description = "Should the OS Disk (either the Managed Disk / VHD Blob) be deleted when the Virtual Machine is destroyed?"
default = "true" # Update for your environment
}
variable "dc1_delete_data_disks_on_termination" {
description = "Should the Data Disks (either the Managed Disks / VHD Blobs) be deleted when the Virtual Machine is destroyed?"
type = string
default = "true" # Update for your environment
}
# Active Directory Configuration #
# domain controller 1 name
variable "ad_dc1_name" {
type = string
description = "This variable defines the name of AD Domain Controller 1"
}
# domain controller 1 private ip address
variable "ad_dc1_ip_address" {
type = string
description = "This variable defines the private ip address of AD Domain Controller 1"
}10. Creating Variables for AD DC VMs
In this step, we will create two local variables for configuring NICs and bootstrapping scripts.
- The dns_servers variable contains the list of DNS servers. In this example, we use the IPs of the two domain controllers. However, if you need to join the on-premises AD domain, you may need to update this variable.
- The dc_servers variable contains the list of AD DC servers. In this example, we use the IPs of the two domain controllers. You should use more or fewer domain controllers in your environment.
locals {
dns_servers = [ var.ad_dc1_ip_address, var.ad_dc2_ip_address ]
dc_servers = [ var.ad_dc1_ip_address, var.ad_dc2_ip_address ]
}11. Creating the NIC for AD DC1 VM
Here, we will create the NIC for the first domain controller. There are two important points here:
- Assigning a Static IP address to the Domain Controller
- Configuring the DNS server properly
resource "azurerm_network_interface" "dc1-nic" {
name = "${var.ad_dc1_name}-nic"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
internal_dns_name_label = var.ad_dc1_name
dns_servers = local.dns_servers
ip_configuration {
name = "${var.ad_dc1_name}-ip-config"
subnet_id = azurerm_subnet.network-subnet.id
private_ip_address_allocation = "Static"
private_ip_address = var.ad_dc1_ip_address
}
}12. Creating the Virtual Machine for AD DC1 VM
In this code, we will launch a Windows Server 2022 virtual machine. However, we can change the operating system to Windows 2019 or 2016 using the SKU variables defined in step #7.
# DC1 virtual machine
resource "azurerm_windows_virtual_machine" "dc1-vm" {
name = "${var.ad_dc1_name}-vm"
computer_name = "${var.ad_dc1_name}-vm"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
availability_set_id = azurerm_availability_set.dc-availability-set.id
size = var.dc1_vm_size
admin_username = var.ad_admin_username
admin_password = var.ad_admin_password
license_type = var.dc1_license_type
network_interface_ids = [azurerm_network_interface.dc1-nic.id]
os_disk {
name = "${var.ad_dc1_name}-vm-os-disk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = var.windows_2022_sku
version = "latest"
}
enable_automatic_updates = true
provision_vm_agent = true
}13. Installing Active Directory in AD DC1 VM and Creating an AD Domain Controller for a New AD Forest and Domain
Finally, we will install the Active Directory on our virtual machine using PowerShell.
This code creates an AD domain controller for a new AD Forest and Domain.
First, we will define the inline script to install the prerequisites and set up the domain controller.
# Local variables for DC1
locals {
dc1_fqdn = "${var.ad_dc1_name}.${var.ad_domain_name}"
dc1_prereq_ad_1 = "Import-Module ServerManager"
dc1_prereq_ad_2 = "Install-WindowsFeature AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools"
dc1_prereq_ad_3 = "Install-WindowsFeature DNS -IncludeAllSubFeature -IncludeManagementTools"
dc1_prereq_ad_4 = "Import-Module ADDSDeployment"
dc1_prereq_ad_5 = "Import-Module DnsServer"
dc1_install_ad_1 = "Install-ADDSForest -DomainName ${var.ad_domain_name} -DomainNetbiosName ${var.ad_domain_netbios_name} -DomainMode ${var.ad_domain_mode} -ForestMode ${var.ad_domain_mode} "
dc1_install_ad_2 = "-DatabasePath ${var.ad_database_path} -SysvolPath ${var.ad_sysvol_path} -LogPath ${var.ad_log_path} -NoRebootOnCompletion:$false -Force:$true "
dc1_install_ad_3 = "-SafeModeAdministratorPassword (ConvertTo-SecureString ${var.ad_safe_mode_administrator_password} -AsPlainText -Force)"
dc1_shutdown_command = "shutdown -r -t 10"
dc1_exit_code_hack = "exit 0"
dc1_powershell_command = "${local.dc1_prereq_ad_1}; ${local.dc1_prereq_ad_2}; ${local.dc1_prereq_ad_3}; ${local.dc1_prereq_ad_4}; ${local.dc1_prereq_ad_5}; ${local.dc1_install_ad_1}${local.dc1_install_ad_2}${local.dc1_install_ad_3}; ${local.dc1_shutdown_command}; ${local.dc1_exit_code_hack}"
}And then, we will use a virtual machine extension to install and configure AD. The “CustomScriptExtension” will execute the inline PowerShell defined above.
resource "azurerm_virtual_machine_extension" "dc1-vm-extension" {
depends_on=[azurerm_windows_virtual_machine.dc1-vm]
name = "${var.ad_dc1_name}-vm-active-directory"
virtual_machine_id = azurerm_windows_virtual_machine.dc1-vm.id
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.9"
settings = <<SETTINGS
{
"commandToExecute": "powershell.exe -Command \"${local.dc1_powershell_command}\""
}
SETTINGS
}14. Defining Variables for AD DC2 VM
In this step, we will define the variables we will use to deploy the second VM.
# Azure virtual machine settings #
variable "dc2_vm_size" {
type = string
description = "Size (SKU) of the virtual machine to create"
default = "Standard_B2s"
}
variable "dc2_license_type" {
type = string
description = "Specifies the BYOL type for the virtual machine. Possible values are 'Windows_Client' and 'Windows_Server' if set"
default = null
}
# Azure virtual machine storage settings #
variable "dc2_delete_os_disk_on_termination" {
type = string
description = "Should the OS Disk (either the Managed Disk / VHD Blob) be deleted when the Virtual Machine is destroyed?"
default = "true" # Update for your environment
}
variable "dc2_delete_data_disks_on_termination" {
description = "Should the Data Disks (either the Managed Disks / VHD Blobs) be deleted when the Virtual Machine is destroyed?"
type = string
default = "true" # Update for your environment
}
# Active Directory Configuration #
# domain controller 2name
variable "ad_dc2_name" {
type = string
description = "This variable defines the name of AD Domain Controller 2"
}
# domain controller 2private ip address
variable "ad_dc2_ip_address" {
type = string
description = "This variable defines the private IP address of AD Domain Controller 2"
}15. Creating the NIC for AD DC2 VM
Here, we will create the NIC for the second domain controller. There are two important points here:
- Assigning a Static IP address to the Domain Controller
- Configuring the DNS server properly, in particular, as the right DNS server is required to join the server to the existing AD Domain
resource "azurerm_network_interface" "dc2-nic" {
name = "${var.ad_dc2_name}-nic"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
internal_dns_name_label = var.ad_dc2_name
dns_servers = local.dns_servers
ip_configuration {
name = "${var.ad_dc2_name}-ip-config"
subnet_id = azurerm_subnet.network-subnet.id
private_ip_address_allocation = "Static"
private_ip_address = var.ad_dc2_ip_address
}
}16. Creating the Virtual Machine for AD DC2 VM
In this code, we will launch a Windows Server 2022 virtual machine. However, we can change the operating system to Windows 2019 or 2016 using the SKU variables defined in step #7.
# DC2 virtual machine
resource "azurerm_windows_virtual_machine" "dc2-vm" {
name = "${var.ad_dc2_name}-vm"
computer_name = "${var.ad_dc2_name}-vm"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
availability_set_id = azurerm_availability_set.dc-availability-set.id
size = var.dc2_vm_size
admin_username = var.ad_admin_username
admin_password = var.ad_admin_password
license_type = var.dc2_license_type
network_interface_ids = [azurerm_network_interface.dc2-nic.id]
os_disk {
name = "${var.ad_dc2_name}-vm-os-disk"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = var.windows_2022_sku
version = "latest"
}
enable_automatic_updates = true
provision_vm_agent = true
}17. Installing Active Directory in AD DC2 VM and Joining an Existing AD Domain
Finally, we will install the Active Directory on our virtual machine using PowerShell.
This code joins the AD DC2 VM to an existing AD Domain.
First, we will define the inline script to install the prerequisites, configure credentials to join the domain, and set up the domain controller.
# Local variables for DC2
locals {
dc2_fqdn = "${var.ad_dc2_name}.${var.ad_domain_name}"
dc2_prereq_ad_1 = "Import-Module ServerManager"
dc2_prereq_ad_2 = "Install-WindowsFeature AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools"
dc2_prereq_ad_3 = "Install-WindowsFeature DNS -IncludeAllSubFeature -IncludeManagementTools"
dc2_prereq_ad_4 = "Import-Module ADDSDeployment"
dc2_prereq_ad_5 = "Import-Module DnsServer"
dc2_credentials_1 = "$User = '${var.ad_admin_username}@${var.ad_domain_name}'"
dc2_credentials_2 = "$PWord = ConvertTo-SecureString -String ${var.ad_admin_password} -AsPlainText -Force"
dc2_credentials_3 = "$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord"
dc2_install_ad_1 = "Install-ADDSDomainController -DomainName ${var.ad_domain_name} -Credential $Credential -InstallDns:$true -CreateDnsDelegation:$false "
dc2_install_ad_2 = "-DatabasePath ${var.ad_database_path} -SysvolPath ${var.ad_sysvol_path} -LogPath ${var.ad_log_path} -NoRebootOnCompletion:$false -Force:$true "
dc2_install_ad_3 = "-SafeModeAdministratorPassword (ConvertTo-SecureString ${var.ad_safe_mode_administrator_password} -AsPlainText -Force) -CriticalReplicationOnly"
dc2_shutdown_command = "shutdown -r -t 10"
dc2_exit_code_hack = "exit 0"
dc2_powershell_command = "${local.dc2_prereq_ad_1}; ${local.dc2_prereq_ad_2}; ${local.dc2_prereq_ad_3}; ${local.dc2_prereq_ad_4}; ${local.dc2_prereq_ad_5}; ${local.dc2_credentials_1}; ${local.dc2_credentials_2}; ${local.dc2_credentials_3}; ${local.dc2_install_ad_1}${local.dc2_install_ad_2}${local.dc2_install_ad_3}; ${local.dc2_shutdown_command}; ${local.dc2_exit_code_hack}"
}After that, we will use a virtual machine extension to install and configure AD. The “CustomScriptExtension” will execute the inline PowerShell defined above.
resource "azurerm_virtual_machine_extension" "dc2-vm-extension" {
depends_on=[azurerm_windows_virtual_machine.dc2-vm]
name = "${var.ad_dc2_name}-vm-active-directory"
virtual_machine_id = azurerm_windows_virtual_machine.dc2-vm.id
publisher = "Microsoft.Compute"
type = "CustomScriptExtension"
type_handler_version = "1.9"
settings = <<SETTINGS
{
"commandToExecute": "powershell.exe -Command \"${local.dc2_powershell_command}\""
}
SETTINGS
}18. Creating an NSG (Network Security Group) for Client Machines to AD Domain Controllers
We must open many ports to communicate between client machines and AD Domain Controllers, particularly if our machines are located in different subnets or VNETs.
We will start creating the NSG for the client-to-DC communication:
## Active Directory NSG for Clients ##
# Create the security group for AD Users
resource "azurerm_network_security_group" "active-directory-client-nsg" {
name = "active-directory-client-nsg"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
}And then, we will add the Inbound rules required. In the code above, we will create a security rule for EACH domain controller:
## Inbound Rules ##
# Port 53 DNS UDP
resource "azurerm_network_security_rule" "udp_53_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS UDP - DC${count.index+1} Inbound"
description = "AD 53 DNS UDP - DC${count.index+1} Inbound"
priority = (150 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 88 Kerberos TCP
resource "azurerm_network_security_rule" "tcp_88_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos TCP - DC${count.index+1} Inbound"
description = "AD 88 Kerberos TCP - DC${count.index+1} Inbound"
priority = (160 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 135 RPC TCP
resource "azurerm_network_security_rule" "tcp_135_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 135 RPC TCP - DC${count.index+1} Inbound"
description = "AD 135 RPC TCP - DC${count.index+1} Inbound"
priority = (170 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "135"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 389 LDAP TCP
resource "azurerm_network_security_rule" "tcp_389_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP TCP - DC${count.index+1} Inbound"
description = "AD 389 LDAP TCP - DC${count.index+1} Inbound"
priority = (180 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 445 SMB TCP
resource "azurerm_network_security_rule" "tcp_445_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 445 SMB TCP - DC${count.index+1} Inbound"
description = "AD 445 SMB TCP - DC${count.index+1} Inbound"
priority = (190 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "445"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 49152-65535 TCP
resource "azurerm_network_security_rule" "tcp_49152-65535_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 TCP - DC${count.index+1} Inbound"
description = "AD 49152-65535 TCP - DC${count.index+1} Inbound"
priority = (200 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 49152-65535 UDP
resource "azurerm_network_security_rule" "udp_49152-65535_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 UDP - DC${count.index+1} Inbound"
description = "AD 49152-65535 UDP - DC${count.index+1} Inbound"
priority = (210 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Allow ping AD Domain Controllers
resource "azurerm_network_security_rule" "icmp_client_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD Ping to DC${count.index+1} Inbound"
description = "AD Ping to DC${count.index+1} Inbound"
priority = (220 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Icmp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}After that, we will add the Outbound rules required for EACH domain controller:
## Outbound Rules ##
# Port 53 DNS UDP
resource "azurerm_network_security_rule" "udp_53_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS UDP - DC${count.index+1} Outbound"
description = "AD 53 DNS UDP - DC${count.index+1} Outbound"
priority = (150 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 88 Kerberos TCP
resource "azurerm_network_security_rule" "tcp_88_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos TCP - DC${count.index+1} Outbound"
description = "AD 88 Kerberos TCP - DC${count.index+1} Outbound"
priority = (160 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 135 RPC TCP
resource "azurerm_network_security_rule" "tcp_135_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 135 RPC TCP - DC${count.index+1} Outbound"
description = "AD 135 RPC TCP - DC${count.index+1} Outbound"
priority = (170 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "135"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 389 LDAP TCP
resource "azurerm_network_security_rule" "tcp_389_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP TCP - DC${count.index+1} Outbound"
description = "AD 389 LDAP TCP - DC${count.index+1} Outbound"
priority = (180 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 445 SMB TCP
resource "azurerm_network_security_rule" "tcp_445_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 445 SMB TCP - DC${count.index+1} Outbound"
description = "AD 445 SMB TCP - DC${count.index+1} Outbound"
priority = (190 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "445"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 49152-65535 TCP
resource "azurerm_network_security_rule" "tcp_49152-65535_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 TCP - DC${count.index+1} Outbound"
description = "AD 49152-65535 TCP - DC${count.index+1} Outbound"
priority = (200 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 49152-65535 UDP
resource "azurerm_network_security_rule" "udp_49152-65535_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 UDP - DC${count.index+1} Outbound"
description = "AD 49152-65535 UDP - DC${count.index+1} Outbound"
priority = (210 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Allow ping AD Domain Controllers
resource "azurerm_network_security_rule" "icmp_client_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-client-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD Ping to DC${count.index+1} Outbound"
description = "AD Ping to DC${count.index+1} Outbound"
priority = (220 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Icmp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}19. Creating an NSG (Network Security Group) for Communication Between AD Domain Controllers
We must open many ports to communicate between AD Domain Controllers, particularly if our machines are located in different subnets, VNETs, or on-prem. However, if both AD domain controllers are located on the same subnet, this NSG is not required.
We will start creating the NSG for the client-to-DC communication:
# Create the security group for AD Domain Controllers
resource "azurerm_network_security_group" "active-directory-dc-nsg" {
name = "active-directory-dc-nsg"
location = azurerm_resource_group.network-rg.location
resource_group_name = azurerm_resource_group.network-rg.name
}And then, we will add the Inbound rules required. In the code above, we will create a security rule for EACH domain controller:
## Inbound Rules ##
# Port 53 DNS TCP
resource "azurerm_network_security_rule" "tcp_53_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS TCP - DC${count.index+1} Inbound"
description = "AD 53 DNS TCP - DC${count.index+1} Inbound"
priority = (100 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 53 DNS UDP
resource "azurerm_network_security_rule" "udp_53_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS UDP - DC${count.index+1} Inbound"
description = "AD 53 DNS UDP - DC${count.index+1} Inbound"
priority = (110 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 88 Kerberos TCP
resource "azurerm_network_security_rule" "tcp_88_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos TCP - DC${count.index+1} Inbound"
description = "AD 88 Kerberos TCP - DC${count.index+1} Inbound"
priority = (120 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 88 Kerberos UDP
resource "azurerm_network_security_rule" "udp_88_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos UDP - DC${count.index+1} Inbound"
description = "AD 88 Kerberos UDP - DC${count.index+1} Inbound"
priority = (130 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 123 W32Time UDP
resource "azurerm_network_security_rule" "udp_123_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 123 W32Time UDP - DC${count.index+1} Inbound"
description = "AD 123 W32Time UDP - DC${count.index+1} Inbound"
priority = (140 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "123"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 135 RPC TCP
resource "azurerm_network_security_rule" "tcp_135_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 135 RPC TCP - DC${count.index+1} Inbound"
description = "AD 135 RPC TCP - DC${count.index+1} Inbound"
priority = (150 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "135"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 137-138 NetLogon UDP
resource "azurerm_network_security_rule" "udp_137-138_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 137-138 NetLogon UDP - DC${count.index+1} Inbound"
description = "AD 137-138 NetLogon UDP - DC${count.index+1} Inbound"
priority = (160 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "137-138"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 139 NetLogon TCP
resource "azurerm_network_security_rule" "tcp_139_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 139 NetLogon TCP - DC${count.index+1} Inbound"
description = "AD 139 NetLogon TCP - DC${count.index+1} Inbound"
priority = (170 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "139"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 389 LDAP TCP
resource "azurerm_network_security_rule" "tcp_389_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP TCP - DC${count.index+1} Inbound"
description = "AD 389 LDAP TCP - DC${count.index+1} Inbound"
priority = (180 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 389 LDAP UDP
resource "azurerm_network_security_rule" "udp_389_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP UDP - DC${count.index+1} Inbound"
description = "AD 389 LDAP UDP - DC${count.index+1} Inbound"
priority = (190 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 445 SMB TCP
resource "azurerm_network_security_rule" "tcp_445_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 445 SMB TCP - DC${count.index+1} Inbound"
description = "AD 445 SMB TCP - DC${count.index+1} Inbound"
priority = (200 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "445"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 464 Kerberos Authentication TCP
resource "azurerm_network_security_rule" "tcp_464_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 464 Kerberos Authentication TCP - DC${count.index+1} Inbound"
description = "AD 464 Kerberos Authentication TCP - DC${count.index+1} Inbound"
priority = (210 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "464"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 464 Kerberos Authentication UDP
resource "azurerm_network_security_rule" "udp_464_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 464 Kerberos Authentication UDP - DC${count.index+1} Inbound"
description = "AD 464 Kerberos Authentication UDP - DC${count.index+1} Inbound"
priority = (220 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "464"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 636 LDAP SSL TCP
resource "azurerm_network_security_rule" "tcp_636_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 636 LDAP SSL TCP - DC${count.index+1} Inbound"
description = "AD 636 LDAP SSL TCP - DC${count.index+1} Inbound"
priority = (230 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "636"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 3268-3269 LDAP GC TCP
resource "azurerm_network_security_rule" "tcp_3268-3269_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 3268-3269 LDAP GC TCP - DC${count.index+1} Inbound"
description = "AD 3268-3269 LDAP GC TCP - DC${count.index+1} Inbound"
priority = (240 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3268-3269"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 49152-65535 TCP
resource "azurerm_network_security_rule" "tcp_49152-65535_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 TCP - DC${count.index+1} Inbound"
description = "AD 49152-65535 TCP - DC${count.index+1} Inbound"
priority = (250 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Port 49152-65535 UDP
resource "azurerm_network_security_rule" "udp_49152-65535_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 49152-65535 UDP - DC${count.index+1} Inbound"
description = "AD 49152-65535 UDP - DC${count.index+1} Inbound"
priority = (260 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "49152-65535"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}
# Allow ping AD Domain Controllers
resource "azurerm_network_security_rule" "icmp_dc_inbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD Ping to DC${count.index+1} Inbound"
description = "AD Ping to DC${count.index+1} Inbound"
priority = (270 + count.index)
direction = "Inbound"
access = "Allow"
protocol = "Icmp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = local.dns_servers[count.index]
destination_address_prefix = "*"
}After that, we will add the Outbound rules required for EACH domain controller:
## Outbound Rules ##
# Port 53 DNS TCP
resource "azurerm_network_security_rule" "tcp_53_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS TCP - DC${count.index+1} Outbound"
description = "AD 53 DNS TCP - DC${count.index+1} Outbound"
priority = (100 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 53 DNS UDP
resource "azurerm_network_security_rule" "udp_53_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 53 DNS UDP - DC${count.index+1} Outbound"
description = "AD 53 DNS UDP - DC${count.index+1} Outbound"
priority = (110 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 88 Kerberos TCP
resource "azurerm_network_security_rule" "tcp_88_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos TCP - DC${count.index+1} Outbound"
description = "AD 88 Kerberos TCP - DC${count.index+1} Outbound"
priority = (120 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 88 Kerberos UDP
resource "azurerm_network_security_rule" "udp_88_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 88 Kerberos UDP - DC${count.index+1} Outbound"
description = "AD 88 Kerberos UDP - DC${count.index+1} Outbound"
priority = (130 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "88"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 123 W32Time UDP
resource "azurerm_network_security_rule" "udp_123_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 123 W32Time UDP - DC${count.index+1} Outbound"
description = "AD 123 W32Time UDP - DC${count.index+1} Outbound"
priority = (140 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "123"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 135 RPC TCP
resource "azurerm_network_security_rule" "tcp_135_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 135 RPC TCP - DC${count.index+1} Outbound"
description = "AD 135 RPC TCP - DC${count.index+1} Outbound"
priority = (150 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "135"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 137-138 NetLogon UDP
resource "azurerm_network_security_rule" "udp_137-138_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 137-138 NetLogon UDP - DC${count.index+1} Outbound"
description = "AD 137-138 NetLogon UDP - DC${count.index+1} Outbound"
priority = (160 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "137-138"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 139 NetLogon TCP
resource "azurerm_network_security_rule" "tcp_139_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 139 NetLogon TCP - DC${count.index+1} Outbound"
description = "AD 139 NetLogon TCP - DC${count.index+1} Outbound"
priority = (170 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "139"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 389 LDAP TCP
resource "azurerm_network_security_rule" "tcp_389_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP TCP - DC${count.index+1} Outbound"
description = "AD 389 LDAP TCP - DC${count.index+1} Outbound"
priority = (180 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 389 LDAP UDP
resource "azurerm_network_security_rule" "udp_389_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 389 LDAP UDP - DC${count.index+1} Outbound"
description = "AD 389 LDAP UDP - DC${count.index+1} Outbound"
priority = (190 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "389"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 445 SMB TCP
resource "azurerm_network_security_rule" "tcp_445_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 445 SMB TCP - DC${count.index+1} Outbound"
description = "AD 445 SMB TCP - DC${count.index+1} Outbound"
priority = (200 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "445"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 464 Kerberos Authentication TCP
resource "azurerm_network_security_rule" "tcp_464_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 464 Kerberos Authentication TCP - DC${count.index+1} Outbound"
description = "AD 464 Kerberos Authentication TCP - DC${count.index+1} Outbound"
priority = (210 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "464"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 464 Kerberos Authentication UDP
resource "azurerm_network_security_rule" "udp_464_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 464 Kerberos Authentication UDP - DC${count.index+1} Outbound"
description = "AD 464 Kerberos Authentication UDP - DC${count.index+1} Outbound"
priority = (220 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "464"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
}
# Port 636 LDAP SSL TCP
resource "azurerm_network_security_rule" "tcp_636_dc_outbound" {
depends_on = [azurerm_resource_group.network-rg]
count = length(local.dns_servers)
network_security_group_name = azurerm_network_security_group.active-directory-dc-nsg.name
resource_group_name = azurerm_resource_group.network-rg.name
name = "AD 636 LDAP SSL TCP - DC${count.index+1} Outbound"
description = "AD 636 LDAP SSL TCP - DC${count.index+1} Outbound"
priority = (230 + count.index)
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "636"
source_address_prefix = "*"
destination_address_prefix = local.dns_servers[count.index]
8. Creating the Input Definition Variables File
In the last step, we will create the input definition variables file “terraform.tfvars” and add the following code. The input variables let us customize aspects of Terraform modules without altering the source code.
# Common Variables #
location = "northeurope"
# Authentication #
azure-tenant-id = "complete-this"
azure-subscription-id = "complete-this"
azure-client-id = "complete-this"
azure-client-secret = "complete-this"
# Network #
network-vnet-cidr = "10.128.0.0/16"
network-vm-subnet-cidr = "10.128.1.0/24"
network-bastion-subnet-cidr = "10.128.2.0/24"}20. Creating the Input Definition Variables File
In the last step, we will create the input definition variables file “terraform.tfvars” and add the following code. The input variables let us customize aspects of Terraform modules without altering the source code.
# Common Variables #
location = "northeurope"
# Authentication #
azure-tenant-id = "complete-this"
azure-subscription-id = "complete-this"
azure-client-id = "complete-this"
azure-client-secret = "complete-this"
# Network #
network-vnet-cidr = "10.129.0.0/16"
network-vm-subnet-cidr = "10.129.1.0/24"
# Active Directory #
ad_domain_name = "kopicloud.local"
ad_domain_netbios_name = "kopicloud"
ad_admin_username = "kopiadmin"
ad_admin_password = "L30M3ss110"
ad_safe_mode_administrator_password = "R3c0v3ryAcc3ssM0d3"
# Domain Controllers #
ad_dc1_name = "kopi-dev-dc1"
ad_dc1_ip_address = "10.129.1.11"
dc1_vm_size = "Standard_B2s"
ad_dc2_name = "kopi-dev-dc2"
ad_dc2_ip_address = "10.129.1.12"
dc2_vm_size = "Standard_B2s"21. Deployment Notes:
To deploy the code:
- Clone the repo to your computer
- Move the files “vm-dc2-main.tf” and “vm-dc2-output.tf” outside the folder
- Execute “terraform init”
- Execute “terraform apply”
- When execution is complete and the DC1 is running, copy the files “vm-dc2-main.tf “, and “vm-dc2-output.tf” back to the folder
- Execute “terraform apply” to create the DC2
Note: if you execute both DC1 and DC2 together, DC2 will fail to join the domain, as the setup of the AD domain takes several minutes.
The complete code to deploy the Active Directory (AD) Domain Controller (DC) Virtual Machine (VM) in Azure with Terraform is here → https://github.com/KopiCloud/terraform-azure-active-directory-dc-vm
And that’s all, folks. If you liked this story, please show your support by 👏 for this story. Thank you for reading!





