I wanted to revisit this topic for a while because the previous guide I wrote about setting up Azure Active Directory Domain Services (AADDS) with Terraform is outdated. However, the article still attracts around 100 visitors per month. People also keep downloading the deprecated Terraform module I created. Time to set things right!

With v2.69.0 of the official Terraform azurerm provider released, the active_directory_domain_service resource is now available. In this post, I’ll briefly walk you through the required steps of setting up AADDS. See also the official Microsoft documentation for more details.

I also published the code to a sample GitHub repo.

What Are Azure Active Directory Domain Services?

Bringing traditional Active Directory Domain Services (AD DS) to the cloud, typically required to set up, secure, and maintain domain controllers (DCs). Azure Active Directory Domain Services (AADDS or Azure AD DS) is a Microsoft-managed solution, providing a subset of traditional AD DS features without the need to self-manage DCs. One such service that requires AD DS features is Azure Virtual Desktop (AVD).

Prerequisites

Before getting started, you need the following things:

Service Principal

First, create the service principal for the Domain Controller Services published application. In public Azure, the ID is 2565bd9d-da50-47d4-8b85-4c97f669dc36. For other clouds the value is 6ba9a5d4-8456-4118-b521-9c5ca10cdf84.

resource "azuread_service_principal" "aadds" {
  application_id = "2565bd9d-da50-47d4-8b85-4c97f669dc36"
}

If the service principal already exists, the following error occurs:

Error: A resource with the ID "11111111-1111-1111-1111-111111111111" already exists

  with azuread_service_principal.aadds,
  on aadds.tf line 2, in resource "azuread_service_principal" "aadds":
   2: resource "azuread_service_principal" "aadds" {

To be managed via Terraform, this resource needs to be imported into the State. Please see the resource documentation for "azuread_service_principal" for more
information.

Import the service principal with the following command:

terraform import azuread_service_principal.aadds 11111111-1111-1111-1111-111111111111

Note that 11111111-1111-1111-1111-111111111111 is the Object ID and not the Application ID.

Microsoft.AAD Resource Provider Registration

To use AADDS, register the Microsoft.AAD resource provider:

resource "azurerm_resource_provider_registration" "aadds" {
  name = "Microsoft.AAD"
}

If the provider is already registered, you can import it into Terraform with the following command:

terraform import azurerm_resource_provider_registration.aadds /subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.AAD

DC Admin Group and User

Next, create an Azure AD group for users administering the AADDS domain and add an admin.

resource "azuread_group" "dc_admins" {
  display_name     = "AAD DC Administrators"
  description      = "AADDS Administrators"
  members          = [azuread_user.dc_admin.object_id]
  security_enabled = true
}

resource "random_password" "dc_admin" {
  length = 64
}

resource "azuread_user" "dc_admin" {
  user_principal_name = "[email protected]"
  display_name        = "AADDS DC Administrator"
  password            = random_password.dc_admin.result
}

Resource Group

Add the resource group for AADDS resources:

resource "azurerm_resource_group" "aadds" {
  name     = "aadds-rg"
  location = "Switzerland North"
}

Network Resources

Add the virtual network and subnet next.

resource "azurerm_virtual_network" "aadds" {
  name                = "aadds-vnet"
  location            = azurerm_resource_group.aadds.location
  resource_group_name = azurerm_resource_group.aadds.name
  address_space       = ["10.0.0.0/16"]
}

resource "azurerm_subnet" "aadds" {
  name                 = "aadds-snet"
  resource_group_name  = azurerm_resource_group.aadds.name
  virtual_network_name = azurerm_virtual_network.aadds.name
  address_prefixes     = ["10.0.0.0/24"]
}

To lock down access to the managed domain, add the following network security group. The AllowRD and AllowPSRemoting rules allow the Azure platform to monitor, manage, and update the managed domain:

resource "azurerm_network_security_group" "aadds" {
  name                = "aadds-nsg"
  location            = azurerm_resource_group.aadds.location
  resource_group_name = azurerm_resource_group.aadds.name

  security_rule {
    name                       = "AllowRD"
    priority                   = 201
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "CorpNetSaw"
    destination_address_prefix = "*"
  }

  security_rule {
    name                       = "AllowPSRemoting"
    priority                   = 301
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "5986"
    source_address_prefix      = "AzureActiveDirectoryDomainServices"
    destination_address_prefix = "*"
  }
}

resource azurerm_subnet_network_security_group_association "aadds" {
  subnet_id                 = azurerm_subnet.aadds.id
  network_security_group_id = azurerm_network_security_group.aadds.id
}

When you enable secure LDAP, it’s recommended to create an additional security rule to restrict inbound LDAPS access to specific IP addresses to protect the managed domain from brute force attacks:

security_rule {
  name                       = "AllowLDAPS"
  priority                   = 401
  direction                  = "Inbound"
  access                     = "Allow"
  protocol                   = "Tcp"
  source_port_range          = "*"
  destination_port_range     = "636"
  source_address_prefix      = "<Authorized LDAPS IPs>"
  destination_address_prefix = "*"
}

AADDS Managed Domain

Finally, deploy the AADDS managed domain:

resource "azurerm_active_directory_domain_service" "aadds" {
  name                = "aadds"
  location            = azurerm_resource_group.aadds.location
  resource_group_name = azurerm_resource_group.aadds.name

  domain_name           = "aadds.example.com"
  sku                   = "Standard"

  initial_replica_set {
    subnet_id = azurerm_subnet.aadds.id
  }

  notifications {
    additional_recipients = ["[email protected]", "[email protected]"]
    notify_dc_admins      = true
    notify_global_admins  = true
  }

  security {
    sync_kerberos_passwords = true
    sync_ntlm_passwords     = true
    sync_on_prem_passwords  = true
  }

  depends_on = [
    azuread_service_principal.aadds,
    azurerm_resource_provider_registration.aadds,
    azurerm_subnet_network_security_group_association.aadds,
  ]
}

Run terraform apply to deploy everything. It takes an hour to complete. Please share your thoughts with me in the comments or on Twitter!