Deploy AD Domain Controller With Terraform on Azure

This blog post will show you how to deploy a single AD Domain Controller using Terraform.

Using a single Terraform deployment to deploy an AD domain controller is very handy and a time saver when we need to test something on a sandbox environment quickly.

The deployment is using a Windows Server 2022 latest build.

Components

To get the deployment done, we are using the following five configuration files. The most important one is the AD_Vars file that holds the AD and DC configuration.

FileDetails
main.tf Main code
AD_vars.tfAD configuration variables
output.tfOutput file
provider.tfAzure provider configuration
variables.tfTerraform configuration file
Deployment files

Note: The Domain Administrator password is auto-generated using the random module, and you will find it on terraform.tfstate file under the admin_password variable.

Main.tf


locals { 
  cmd01 = "Install-WindowsFeature AD-Domain-Services -IncludeAllSubFeature -IncludeManagementTools"
  cmd02 = "Install-WindowsFeature DNS -IncludeAllSubFeature -IncludeManagementTools"
  cmd03 = "Import-Module ADDSDeployment, DnsServer"
  cmd04 = "Install-ADDSForest -DomainName ${var.domain_name} -DomainNetbiosName ${var.domain_netbios_name} -DomainMode ${var.domain_mode} -ForestMode ${var.domain_mode} -DatabasePath ${var.database_path} -SysvolPath ${var.sysvol_path} -LogPath ${var.log_path} -NoRebootOnCompletion:$false -Force:$true -SafeModeAdministratorPassword (ConvertTo-SecureString ${var.safe_mode_administrator_password} -AsPlainText -Force)"
  powershell = "${local.cmd01}; ${local.cmd02}; ${local.cmd03}; ${local.cmd04}"

 
}


resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.location  
}

resource "azurerm_virtual_network" "vnet" {
  name                = var.vnet_name
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_subnet" "subnet" {
  name                 = var.subnet_name
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]
}



resource "azurerm_network_interface" "winosnic" {
  name                = "${var.winosprefix}-nic"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.winospublicip.id
  }

}

resource "azurerm_network_security_group" "nsg" {
  name                = var.vm_nsg
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

    security_rule {
    name                       = "RDP"
    priority                   = 101
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
    
  }
}

resource "azurerm_windows_virtual_machine" "winosvm" {
  name                            = "${var.winosprefix}-vm"
  resource_group_name             = azurerm_resource_group.rg.name
  location                        = azurerm_resource_group.rg.location
  size                            = "Standard_F2s_v2" 
  admin_username                  = "vmadmin"
  admin_password                  = random_password.set_password.result
  network_interface_ids = [
    azurerm_network_interface.winosnic.id,
  ]

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter-azure-edition"
    version   = "latest"
  }

  os_disk {
    storage_account_type = "Standard_LRS"
    caching              = "ReadWrite"
  }


}



resource "azurerm_public_ip" "winospublicip" {
  name                = "${var.winosprefix}-pubip"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  allocation_method   = "Dynamic"

}

resource "azurerm_network_interface_security_group_association" "securitygroup" {
    network_interface_id      = azurerm_network_interface.winosnic.id
    network_security_group_id = azurerm_network_security_group.nsg.id
}




resource "azurerm_virtual_machine_extension" "software" {
  depends_on=[azurerm_windows_virtual_machine.winosvm]

  name                       = "install-gpmc"
  virtual_machine_id         = azurerm_windows_virtual_machine.winosvm.id
  publisher                  = "Microsoft.Compute"
  type                       = "CustomScriptExtension"
  type_handler_version       = "1.9"
  auto_upgrade_minor_version = true

  settings = <<SETTINGS
    {
       "commandToExecute": "powershell.exe -Command \"${local.powershell}\""
 
   }
  SETTINGS
}


resource "random_password" "set_password" {
  length      = 20
  min_lower   = 1
  min_upper   = 1
  min_numeric = 1
  min_special = 1
  special     = true
}

AD_Vars.tf


variable "domain_name" {
  type        = string
  default = "ntweekly.com"
}

variable "dc_name" {
  type        = string
  default = "ntdc01"  
}


variable "domain_netbios_name" {
  type        = string
  default = "ntweekly"
}

variable "domain_mode" {
  type        = string
  default     = "WinThreshold" # Windows Server 2016 mode
}

variable "vm_admin_username" {
  type        = string
  default = "vmadmin"
}

variable "domain_admin_password" {
  type        = string
  default = ""
}

variable "database_path" {
  type        = string
  default     = "C:/Windows/NTDS"
}

variable "sysvol_path" {
  type        = string
  default     = "C:/Windows/SYSVOL"
}

variable "log_path" {
  type        = string
  default     = "C:/Windows/NTDS"
}

variable "safe_mode_administrator_password" {
  type        = string
  default = ""  
}

Output.tf

output "public_ip_address" {
  value = azurerm_windows_virtual_machine.winosvm.public_ip_address
}


output "admin_password" {
  sensitive = true
  value     =  azurerm_windows_virtual_machine.winosvm.admin_password
}

Provider.tf

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "3.47.0"
    }

   random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    }
  }



}


provider "azurerm" {
 features {
    key_vault {
      purge_soft_delete_on_destroy = true
    }
  }
}

Variables.tf

variable "rg_name" {
  type        = string
  default = "Server2022"
  description = "The prefix used for all resources in this example"

}

variable "location" {
  type        = string
  default = "southeastasia"
  description = "The Azure location where all resources in this example should be created"
}


variable "vm_username" {
  type = string
  default = "vmadmin"

}



variable "winosprefix" {
  type = string
  default = "Win2022"
  description = "The prefix which should be used for all resources in this example"
}


variable "vnet_name" {
  type = string
  default = "win_vnet"

}


variable "subnet_name" {
  type = string
  default = "win_vms"

}

variable "nic_name" {
  type = string
  default = "winnic"

}


variable "vm_nsg" {
  type = string
  default = "win_nsg"
}

When deploying, you will be asked to provide the safe_made password, which you can either type or add it as a variable.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.