Terraform -Setup Azure Landing Zone (IaC)

Automated configuration is faster than manual configuration. So in this article, we'll be setting up the Azure Landing zone using Terraform. What is Terraform? Terraform is a multi-cloud Infrastructure as a Code (IaC) tool used for building, managing, and scaling cloud infrastructure. It is an open-source tool developed by HashiCorp and uses a language called HashiCorp Configuration Language (HCL), which is easy to learn and use for automating tasks. This tool supports cloud service providers such as Azure, AWS, and Google Cloud, as well as private cloud providers such as VMware, CloudStack, and OpenStack.

Infrastructure as Code (IaC) is frequently referred to as programmable infrastructure. Versioning is the core of IaC, enabling reuse and sharing across environments for faster and efficient deployments.

The primary use of Terraform in Microsoft Azure is to automate the process of infrastructure provisioning and management. Using Terraform, we can simplify complex tasks, efficiently ensure the desired state configuration, and support future modifications.

Prerequisites:

  • Install Terraform and the Azure CLI.
  • Follow the detailed steps below for a perfect setup on Windows environment.

Install Terraform on your Windows Environment

Step 1: Download and unzip Terraform
Step 2: Add Terraform to your PATH variable
  • Open Control Panel > System and Security > System > Advanced system settings > Environment Variables.
  • In the System variables section, find the Path variable and double-click it.
  • Click New and add the directory containing the Terraform binary to the PATH variable.
  • Click OK to save your changes.

You have now successfully installed Terraform on Windows. Next, install the Azure CLI.

Install Azure CLI on your Windows Environment

Step 1: Open Windows PowerShell Run as administrator.

Step 2: Create a new file called installAzureCLI.ps1 and copy and insert the following script into it:

Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; 
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; 
rm .\AzureCLI.msi

Step 3: Run the installAzureCLI.ps1 file and wait for the installation to complete.

Step 4: Verify the installation by running the following command:

az -h

That's it! You have now successfully configured Terraform and Azure CLI.


Setup Azure Landing Zone:

Contents to Setup Azure Landing Zone:

  • Create Azure Resource Group
  • Create Azure Virtual Network
  • Create Subnet in Virtual Network
  • Create Azure Network Security Group
  • Configure Network Security Rules
  • Associate Network Security Group to Virtual Network Subnet
  • Create a Route Table
  • Creating the Route in Route Table which is present in Connectivity Subscription
  • Creating Storage Account
  • Creating and Configuring Private DNS Zone
  • Virtual Network Peeing to the Connectivity Subscription
  • Create IP Groups

Step 1: Create a new folder for Azure Landing Zone Setup

In folder create three files namely 

  • main.tf
  • variable.tf
  • locals.tf

Step 2: Copy the below codes and add it to their respective files.

Add the code to File: main.tf

/*=== Configure Azure Resource Providers ===*/
terraform {
    required_providers {
        azurerm = {
            source = "hashicorp/azurerm"
            version = "~> 2.65"
        }
    }
    required_version = ">= 1.1.0"
}
provider "azurerm" {
    features {}
}
provider "azurerm" {
    alias = "connectivity"
    subscription_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
    features {}
}
provider "azurerm" {
    alias = "identity"
    subscription_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
    features {}
}
data "azurerm_resource_group" "rgeusconnectivity" {
    provider = azurerm.connectivity
    name = "prd-az-connectivity-eus-rg"
}
resource "azurerm_resource_group" "rg" {
    name = var.resource_group_name
    location = var.location
    tags = {
        environment = "Production"
    }
}
/*=== Create and Configure Azure Virtual Network ===*/
resource "azurerm_virtual_network" "vnet" {
    name = var.vnetName
    location = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
    address_space = var.addressSpaceVnet
}
resource "azurerm_subnet" "subnet" {
    name = var.SubnetName
    resource_group_name = azurerm_resource_group.rg.name
    virtual_network_name = azurerm_virtual_network.vnet.name
    address_prefixes = var.addressPrefixesSnet
}
/*=== Create and Configure Azure Network Security Group Rules ===*/
resource "azurerm_network_security_group" "nsg" {
    name = var.nsgName
    location = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_network_security_rule" "nsgrules" {
    for_each = local.nsgrules
    name = each.key
    direction = each.value.direction
    access = each.value.access
    priority = each.value.priority
    protocol = each.value.protocol
    source_port_range = each.value.source_port_range
    destination_port_range = each.value.destination_port_range
    source_address_prefix = each.value.source_address_prefix
    destination_address_prefix = each.value.destination_address_prefix
    resource_group_name = azurerm_resource_group.rg.name
    network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_subnet_network_security_group_association" "nsgsnetassociation" {
    subnet_id = azurerm_subnet.subnet.id
    network_security_group_id = azurerm_network_security_group.nsg.id
}
/*=== Create Azure Route Table ===*/
resource "azurerm_route_table" "routetable" {
    name = var.routetableInfo
    location = azurerm_resource_group.rg.location
    resource_group_name = azurerm_resource_group.rg.name
    disable_bgp_route_propagation = false
    route {
        name = "to-firewall"
        address_prefix = "0.0.0.0/0"
        next_hop_type = "VirtualAppliance"
        next_hop_in_ip_address = var.nextHopinIPaddress
    }
}
/*=== Adding of Route to Connectivity ===*/
data "azurerm_route_table" "routeTable" {
    provider = azurerm.connectivity
    name = "prd-az-gateway-eus-route"
    resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
}
resource "azurerm_route" "route" {
    provider = azurerm.connectivity
    name = "to-Terraform"
    resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
    route_table_name = data.azurerm_route_table.routeTable.name
    address_prefix = "0.0.0.0/0"
    next_hop_type = "VirtualAppliance"
    next_hop_in_ip_address ="10.1.0.6"
}
/*=== Create Azure Storage Account ===*/
resource "azurerm_storage_account" "storageAccount" {
    name = var.storageAccountName
    resource_group_name = azurerm_resource_group.rg.name
    location = azurerm_resource_group.rg.location
    account_tier = var.account_tier
    account_replication_type = var.account_replication_type
    access_tier = var.accessTier
    min_tls_version = var.minTlsVersion
    account_kind = var.accountKind
}
/*==== Creation and Configuration of Private DNS Zone ===*/
data "azurerm_resource_group" "rgidentityeus" {
    provider = azurerm.identity
    name = "prd-azure-identity-eus-rg"
}
data "azurerm_resource_group" "rgidentityweu" {
    provider = azurerm.identity
    name = "prd-az-identity-weu-rg"
}
data "azurerm_virtual_network" "vnetidentityeus" {
    provider = azurerm.identity
    name = "prd-az-identity-eus-vnet"
    resource_group_name = data.azurerm_resource_group.rgidentityeus.name
}
data "azurerm_virtual_network" "vnetidentityweu" {
    provider = azurerm.identity
    name = "prd-az-identity-weu-vnet"
    resource_group_name = data.azurerm_resource_group.rgidentityweu.name
}

resource "azurerm_private_dns_zone" "privateDNSzone" {
    name = var.privateDNSzoneName
    resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink1" {
    name = "link-to-identity-eus"
    resource_group_name = data.azurerm_resource_group.rgidentityeus.name
    private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
    virtual_network_id = data.azurerm_virtual_network.vnetidentityeus.id
    registration_enabled = false
}

resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink2" {
    name = "link-to-identity-weu"
    resource_group_name = data.azurerm_resource_group.rgidentityweu.name
    private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
    virtual_network_id = data.azurerm_virtual_network.vnetidentityweu.id
    registration_enabled = false
}
resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink3" {
    name = "link-to-subscription"
    resource_group_name = azurerm_resource_group.rg.name
    private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
    virtual_network_id = azurerm_virtual_network.vnet.id
    registration_enabled = true
}
/*=== Azure VNet Peering to Azure Connectivity Hub ===*/
data "azurerm_virtual_network" "vneteusconnectivity" {
    provider = azurerm.connectivity
    name = "prd-az-connectivity-eus-vnet"
    resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
}
resource "azurerm_virtual_network_peering" "vnet1-2" {
    provider = azurerm.connectivity
    name = var.vnet1-2PeeringName"peer1to2"
    resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
    virtual_network_name = data.azurerm_virtual_network.vneteusconnectivity.name
    remote_virtual_network_id = azurerm_virtual_network.vnet.id
    allow_forwarded_traffic = true
    allow_gateway_transit = true
    use_remote_gateways = false
}
resource "azurerm_virtual_network_peering" "vnet2-1" {
    name = var.vnet2-1PeeringName
    resource_group_name = azurerm_resource_group.rg.name
    virtual_network_name = azurerm_virtual_network.vnet.name
    remote_virtual_network_id = data.azurerm_virtual_network.vneteusconnectivity.name
    allow_forwarded_traffic = true
    allow_gateway_transit = false
    use_remote_gateways = false
}
/*=== Create Azure IP Group ===*/
resource "azurerm_ip_group" "ipgroup" {
    provider = azurerm.connectivity
    name = var.ipgroupName
    location = data.azurerm_resource_group.rgeusconnectivity.location
    resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
    cidrs = var.ipgroupcidrs
}

Add the code to File: variable.tf

variable "resource_group_name" {
    type = string
    default = "prd-az-terrform-eus-rg"
}
variable "location" {
    type = string
    default = "eastus"
}
variable "vnetName" {
    type = string
    default = "prd-az-terraform-eus-vnet"
}
variable "addressSpaceVnet" {
    type = string
    default = ["10.2.0.0/16"]
}
variable "SubnetName" {
    default = "prd-az-terraform-eus-subnet"
}
variable "addressPrefixesSnet" {
    type = string
    default = ["10.2.4.0/29"]
}
variable "nsgName" {
    type = string
    default = "prd-az-terraform-eus-nsg"
}
variable "routetableInfo" {
    type = string
    default = "prd-az-terraform-eus-routetable"
}
variable "nextHopinIPaddress" {
    type = string
    default = "10.4.0.0"
}
variable "storageAccountName" {
    type = string
    default = "prdazterraformeusst01"
}
variable "account_tier" {
    type = string
    default = "Standard"
}
variable "account_replication_type" {
    type = string
    default = "LRS"
}
variable "accessTier" {
    type = string
    default = "Hot"
}
variable "minTlsVersion" {
    type = string
    default = "TLS1_2"
}
variable "accountKind" {
    type = string
    default = "StorageV2"
}
variable "privateDNSzoneName" {
    type = string
    default = "prd.eus.azure.cloud.ads"
}
variable "vnet1-2PeeringName" {
    type = string
    default = "peer1to2"
}
variable "vnet2-1PeeringName" {
    type = string
    default = "peer2to1"
}
variable "ipgroupName" {
    type = string
    default = "prd-az-Terraform-eus-ipgroup"
}
variable "ipgroupcidrs" {
    type = list
    default = ["10.3.0.0/29", "10.4.1.8"]
}

Add the code to File: locals.tf

locals {
    nsgrules = {
        AllowRDPInBound = {
            name = "AllowRDPInBound"
            priority = 300
            direction = "Inbound"
            access = "Allow"
            protocol = "Tcp"
            source_port_range = "*"
            destination_port_range = "3389"
            source_address_prefix = "VirtualNetwork"
            destination_address_prefix = "VirtualNetwork"
        }
        AllowSSHInBound = {
            name = "AllowSSHInBound"
            priority = 400
            direction = "Inbound"
            access = "Allow"
            protocol = "Tcp"
            source_port_range = "*"
            destination_port_range = "22"
            source_address_prefix = "VirtualNetwork"
            destination_address_prefix = "VirtualNetwork"
        }
        AllowPort80InBound = {
            name = "AllowPort80InBound"
            priority = 500
            direction = "Inbound"
            access = "Allow"
            protocol = "Tcp"
            source_port_range = "*"
            destination_port_range = "80"
            source_address_prefix = "VirtualNetwork"
            destination_address_prefix = "VirtualNetwork"
        }
        AllowPort443InBound = {
            name = "AllowPort443InBound"
            priority = 600
            direction = "Inbound"
            access = "Allow"
            protocol = "Tcp"
            source_port_range = "*"
            destination_port_range = "443"
            source_address_prefix = "VirtualNetwork"
            destination_address_prefix = "VirtualNetwork"
        }
        DenyAllInBound = {
            name = "DenyAllInBound"
            priority = 4096
            direction = "Inbound"
            access = "Deny"
            protocol = "*"
            source_port_range = "*"
            destination_port_range = "*"
            source_address_prefix = "*"
            destination_address_prefix = "VirtualNetwork"
        }
    }
}

Step 3: Run the PowerShell to the new folder location

Step 4: Now authenticate to Azure using azure CLI, for that run the command az login. You will be redirected to the new browser. Log in to Microsoft by providing your credentials. Once the login is completed go back to the PowerShell session

Step 5: The list of the subscriptions associated with your Microsoft account will get listed, select the subscription in which you want to perform Landing Zone Setup by running the following command:

az account set --subscription <name or id>

Step 6: Once you set the subscription, run the command terraform init, the terraform init command is used to initialize a working directory containing Terraform configuration files.

Step 7: Next run the command terraform validate, the terraform validate command validates the configuration files in a directory and it verifies whether a configuration is systematically valid.

Step 8: Run the command terraform plan, the terraform plan command creates an execution plan, which lets you preview the changes that Terraform plans to make to your infrastructure.

Step 9: At last, run the command terraform apply, the terraform apply command executes the actions proposed in a Terraform plan. Provide the value as Yes, if you want to apply the configuration.


Terraform CLI Cheat Sheet List:

This table provides an extensive list of Terraform CLI commands along with their brief descriptions. You can refer to this cheat sheet as a quick reference for various Terraform operations.

Command Description
terraform init Initialize a Terraform configuration in the current directory.
terraform apply Create or update infrastructure according to Terraform configuration.
terraform destroy Destroy the Terraform-managed infrastructure.
terraform plan Show the execution plan for the Terraform configuration.
terraform validate Validate the Terraform configuration files.
terraform refresh Refresh the Terraform state.
terraform output Output the Terraform state or a specific output value.
terraform output <name> Output the value of a specific output from the Terraform state.
terraform import Import existing infrastructure into Terraform state.
terraform import <address> Import a specific resource into the Terraform state.
terraform taint Mark a resource as tainted, triggering recreation on the next apply.
terraform untaint Remove the tainted state from a resource.
terraform fmt Format and update the Terraform code to follow best practices.
terraform show Show the execution plan in a human-readable format with colors.
terraform show -no-color Show the execution plan in a human-readable format without colors.
terraform state show Show detailed information about a resource in the Terraform state.
terraform state list List resources in the Terraform state.
terraform state mv Move a resource from one address to another in the Terraform state.
terraform state rm Remove a resource from the Terraform state.
terraform workspace Workspace management commands.
terraform variable Manage Terraform variables and their values.

# Check Version of Terraform
terraform -version

# For Terraform Help
terraform -help

# Show proposed changes that an apply would perform
terraform plan

# Apply all terraform changes
terraform apply

# Apply a specific terraform resource
terraform apply -target=<resource>

# Use a variables file with your terraform apply/plan
terraform apply -var-file=<path_to_vars_file>

# Destroy all terraform resources
terraform destroy

# Destroy select terraform resources
terraform destroy -target=<resource>

# Dry run terraform destroy
terraform plan -destroy

# Mark a resource as tainted and force a destroy/recreate
terraform taint <resource>

# Mark a tainted resource as clean
terraform untaint <resource> Modules/Providers

# Initialize Terraform backend & download specified plugins/modules
terraform init Debug

# Clear out old modules/plugins & re-initialize
terraform rm -rf ./.terraform/ && terraform init

# Show current terraform resources
terraform show

# Pull modules into your .terraform directory
terraform get-update=true

# Validate your terraform code
terraform validate

# Show terraform providers
terraform providers

# Show all terraform outputs
terraform output

# Test resource interpolation (uses your state file)
echo '<resource_expression>' | terraform console

# Pull the remote state
terraform state pull > terraform.tfstate

# Visualize Terraform Dependency Graph (requires graphviz)
terraform graph | dot -Tsvg> graph.svg

# Push local state to remote state (uses file: terraform.tfstate)
terraform state push Workspaces

# Update local state file against real resources
terraform refresh

# Create a terraform workspace
terraform workspace new <workspace>

# Tell Terraform a resource has been moved into a module
terraform state mv <resource> <module>

# Use a terraform workspace
terraform workspace select <workspace>

# Import an existing resource into
terraform state

# Note that this is different syntax for every resource
terraform import <resource>

# List terraform workspaces
terraform workspace list

# Show current terraform workspace
terraform workspace show"