Hey everyone,

Today I want to share my experience with Terraform and Microsoft Azure. I was tasked with creating a new Active Directory Domain Controller in Azure with a VPN back to on-premises servers, in preparation to slowly stop using Active Directory. After last year, I can very easily see this becoming a more common request, and I’d like to cut down on how much time I spend creating infrastructure. Let’s briefly go over the project.

Project

I need a new domain controller hosted in Microsoft Azure that can talk back to my domain controllers that are on-premises, so that I can transition away from these physical servers because everyone has gotten used to being able to work anywhere at almost any time.

Requirements

  • A virtual machine running the latest Windows Server
  • A virtual network
  • A subnet on that virtual network that my virtual machine can connect to
  • A site-to-site VPN
  • A public IP address so that I can connect to this virtual machine remotely with my RMM tools

You also need to install Terraform and the Microsoft Azure CLI. Documentation to get you started on that is available here.

Let’s start

Once Terraform and Azure CLI are installed and you’ve logged into your Azure account selected your subscription, we can begin.

Before we start building infrastructure, the first thing you need is a resource group. Resource groups are containers for resources you create, that you would like to manage together. For this project, we’re going to use the same resource group for everything we need.

Create a file called main.tf and follow along with the code below.

# Configure the Azure provider
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 2.65"
    }
  }

  required_version = ">= 0.14.9"
}

provider "azurerm" {
  features {}
}

# Create a resource group
resource "azurerm_resource_group" "ex" {
  name     = "example-rg"
  location = "eastus2"
  tags = {
    purpose     = "an example"
    environment = "test"
  }
}

The above snippet did the following: 1. Defined which provider we needed, in this case we need azurerm so we can communicate with Azure. 2. We defined the features we want, in this case we don’t need anything extra. 3. We created our resource group with the reference name ex, and the name example-rg.

ex is only a reference name for Terraform, example-rg is the actual name of the resource group that you will see in Microsoft Azure. Let’s continue adding resources, starting with the network.

# Create a virtual network
resource "azurerm_virtual_network" "ex" {
  name                = "vnet1"
  address_space       = ["10.10.0.0/16"]
  location            = "eastus2"
  resource_group_name = azurerm_resource_group.ex.name
}

Now we need a subnet for our server(s) to use:

 Create a subnet
resource "azurerm_subnet" "ex" {
  name                 = "snet1"
  resource_group_name  = azurerm_resource_group.ex.name
  virtual_network_name = azurerm_virtual_network.ex.name
  address_prefixes     = ["10.10.100.0/24"]
}

Every server needs a network interface, and in our case, we need a public IP too:

# Create a public IP to assign to the virtual NIC to be used by the server.
resource "azurerm_public_ip" "ex" {
  name                = "pip1"
  resource_group_name = azurerm_resource_group.ex.name
  location            = azurerm_resource_group.ex.location
  sku                 = "Basic"
  sku_tier            = "Regional"
  allocation_method   = "Dynamic"
  tags = {
    environment = "Production"
  }
}

# Create a virtual network interface
resource "azurerm_network_interface" "ex" {
  name                = "nic1"
  location            = azurerm_resource_group.ex.location
  resource_group_name = azurerm_resource_group.ex.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.ex.id
    private_ip_address_allocation = "Static"
    private_ip_address            = "10.10.100.10"
    public_ip_address_id          = azurerm_public_ip.ex.id
  }
}

Don’t forget to create a network security group. I’ll leave this as an exercise for the reader.

Now we can finally configure our server:

# Create a virtual machine
resource "azurerm_windows_virtual_machine" "ex" {
  name                = "NewAzureServer1"  # Name of resource
  computer_name       = "AzureServer1"  # Hostname
  resource_group_name = azurerm_resource_group.ex.name
  location            = azurerm_resource_group.ex.location
  size                = "Standard_B2ms"  # The SKU of our server.
  admin_username      = "MyAdminAccount"
  admin_password      = "myverystrongpassword"
  network_interface_ids = [
    azurerm_network_interface.ex.id,
  ]
  patch_mode = "AutomaticByOS"

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
  # We want Windows Server 2022, with a GUI interface
  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-Datacenter"
    version   = "latest"
  }
}

If you were paying attention, you will have noticed we were able to create all these resources within the same resource group and in the same location with the following lines:

resource_group_name = azurerm_resource_group.ex.name
location            = azurerm_resource_group.ex.location

You’ll be seeing these lines a lot.

Go ahead and try running terraform apply now to create the resources defined so far.

Stopping here for a bit, we have a new Windows Server 2022 installation which you can log into over RDP. Go ahead and take this moment to log in and be briefly amused with what you’ve made install any remote management software you need.

We’re still missing our Site to Site VPN. Now here is where we need to be careful. If you go searching for “site to site Azure VPN” you will find many links to resources for help, but not all of them are equal. Some of them will have you set up something that is for enterprise deployments (re: costs lots of money).

Instead, I’m going to create a virtual network gateway, with a gateway subnet, and a public IP.

Let’s keep adding to main.tf.

# Create the gateway subnet
resource "azurerm_subnet" "sts" {
  name                 = "GatewaySubnet"
  resource_group_name  = azurerm_resource_group.ex.name
  virtual_network_name = azurerm_virtual_network.ex.name
  address_prefixes     = ["10.10.200.0/24"]
}

# Create a public IP to be used by the gateway
resource "azurerm_public_ip" "sts" {
  name                = "pip-vnetgw"
  resource_group_name = azurerm_resource_group.ex.name
  location            = azurerm_resource_group.ex.location
  sku                 = "Basic"
  sku_tier            = "Regional"
  allocation_method   = "Dynamic"
}

Pay attention here, I’ve used a new reference name above. This is because you cannot have more than one resource of the same type and same reference name. Here I use sts for “site-to-site”, because that is the purpose I’m creating these resources for.

Now let’s define our local (on-premises) gateway:

# Create a local gateway
resource "azurerm_local_network_gateway" "onprem" {
  name                = "office"
  resource_group_name = azurerm_resource_group.ex.name
  location            = azurerm_resource_group.ex.location
  gateway_address     = "a.b.c.d"  # Replace with the public IP Address of your office firewall
  address_space       = ["192.168.35.0/24"]  # Your desired subnet in the office
}

Now I’ll make the gateway to go with our gateway subnet and public IP.

NOTE: Creating a virtual network gateway can take between 30-60 minutes. In my experience it took 35 minutes. Make sure you’re okay with leaving your computer on for that long.

# Create a virtual network gateway
resource "azurerm_virtual_network_gateway" "sts" {
  name                = "vnetgw1"
  location            = azurerm_resource_group.ex.location
  resource_group_name = azurerm_resource_group.ex.name

  type     = "Vpn"
  vpn_type = "RouteBased"

  active_active = false
  enable_bgp    = false
  sku           = "Basic"
  generation    = "Generation1"

  ip_configuration {
    name                          = "vnetStsGatewayConfig"
    public_ip_address_id          = azurerm_public_ip.sts.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.sts.id
  }
}

Now we’ve got a site-to-site VPN. Let’s set up the connection to our office:

# Create a virtual network gateway connection
resource "azurerm_virtual_network_gateway_connection" "sts" {
  name                = "OfficeConnection1"
  location            = azurerm_resource_group.ex.location
  resource_group_name = azurerm_resource_group.ex.name

  type                       = "IPsec"
  virtual_network_gateway_id = azurerm_virtual_network_gateway.sts.id
  local_network_gateway_id   = azurerm_local_network_gateway.onprem.id

  shared_key = "TopSecretSharedKeyPleaseChange"
}

Run terraform apply here, and you’ll get a detailed plan of what’s to come. Once you’ve read the changes in your plan, go ahead and continue with deployment. In about 30-60 minutes you’ll have your new VPN, and you can configure it on your local firewall.

Firewall configuration

Depending on your firewall, you’ll have to look up a guide. In my case, I had a FortiGate firewall. I tried following this example by Fortinet but I ran into issues with not being able to send data to my server in Azure from my office.

What I did instead was go to the IPSec Tunnels page in my firewall and create a new Site-to-Site VPN and chose the options for FortiGate to FortiGate. I know this is not the actual scenario, but bear with me. I proceeded to fill in all the relevant information such as remote gateway, remote subnet, and shared secret.

After completing the tunnel, I edited the tunnel and converted it to a custom tunnel, then changed the options to what it should have been in the guide I previously linked.

I’m not sure why the inbound and outbound policies weren’t working when I created them, but having the wizard do it for me was a quick hack to get proper policies.

Conclusion

Now that you’ve come this far, you can enjoy your new servers in Azure.

Bear in mind there are a few things I did not go over, like how to properly secure your network in Azure. I will leave that as an exercise for you, the reader.

I hope you learned something new, thank you for reading!