Skip to main content
Jorge Bernhardt Jorge Bernhardt
  1. Posts/

Terraform - Deploy Azure Bastion into Virtual Network

·1532 words·8 mins· 100 views · 5 likes ·
Terraform Microsoft Azure IaC Azure Bastion

Hi there! In a previous article, we discussed the deployment of an Azure Bastion host in an existing Virtual Network (VNet) using Azure CLI and PowerShell. While those methods are effective, there’s an opportunity to simplify the entire process.

Using Infrastructure as Code (IaC) can simplify the process of deploying an Azure Bastion host and enable scalability for future deployments. In this post, I’ll provide you with a step-by-step guide on how to deploy the Azure Bastion host resource to an existing network with Terraform.

Prerequisites>

Prerequisites #

  • You need Terraform CLI on your local machine, if you’re new to using Terraform to deploy Microsoft Azure resources, then I recommend you check out this link.
  • A text editor or IDE of your choice (Visual Studio Code with Terraform extension is my recommendation)
Declare Azure Provider in Terraform>

Declare Azure Provider in Terraform #

The provider.tf file in Terraform is used to specify and configure the providers used in your Terraform configuration. A provider is a service or platform where the resources will be managed. This could be a cloud provider like Microsoft Azure, AWS, Google Cloud, etc.

This file is important because it tells Terraform which provider’s API to use when creating, updating, and deleting resources. Without it, Terraform wouldn’t know where to manage your resources.

provider "azurerm" {
  features {}
}
Create an Azure Bastion host using Terraform>

Create an Azure Bastion host using Terraform #

In the case of Azure Bastion host deployment, the main.tf file contains the following key components:

  • Existing Resource Group: This block of code retrieves the details of an existing Azure resource group. The name of the resource group is passed in as a variable var.resource_group.name.
  • Existing Virtual Network: This block retrieves the information of an existing virtual network in Azure. The name of the virtual network is provided via var.vnet.name and the name of the resource group where the virtual network resides is taken from the defined resource group data source.
  • Subnet: This block creates a subnet in the fetched existing virtual network. The variables provide the subnet name, resource group, virtual network name, and address prefixes.
  • Public IP: This block creates a public IP address resource in Azure, with the name, location, resource group, allocation method, and SKU defined by provided variables.
  • Azure Bastion host: This block creates an Azure Bastion Host resource. Variables provide the bastion hostname, location, resource group, and SKU. The block also contains settings and configurations for Bastion Host based on its SKU.
// Get the details of an existing resource group
data "azurerm_resource_group" "rg" {
  name = var.resource_group.name
}

// Get the details of an existing virtual network
data "azurerm_virtual_network" "vnet" {
  name                = var.vnet.name
  resource_group_name = data.azurerm_resource_group.rg.name
}

// Creating a Subnet
resource "azurerm_subnet" "subnet" {
  name                 = var.subnet.name
  resource_group_name  = data.azurerm_resource_group.rg.name
  virtual_network_name = data.azurerm_virtual_network.vnet.name
  address_prefixes     = var.subnet.address_prefixes
}

// Creating a Public IP Address
resource "azurerm_public_ip" "public_ip" {
  name                = var.public_ip.name
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
  allocation_method   = var.public_ip.allocation_method
  sku                 = var.public_ip.sku
  tags                = var.tags
}

// Creating an Azure Bastion Host
resource "azurerm_bastion_host" "bastion_host" {
  name                = var.bastion_host.name
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name
  sku                 = var.bastion_host.sku

  // Setting the optional Bastion host settings based on SKU type
  copy_paste_enabled     = var.bastion_host.sku == "Standard" ? var.bastion_host.copy_paste_enabled : null
  file_copy_enabled      = var.bastion_host.sku == "Standard" ? var.bastion_host.file_copy_enabled : null
  ip_connect_enabled     = var.bastion_host.sku == "Standard" ? var.bastion_host.ip_connect_enabled : null
  scale_units            = var.bastion_host.sku == "Standard" ? var.bastion_host.scale_units : null
  shareable_link_enabled = var.bastion_host.sku == "Standard" ? var.bastion_host.shareable_link_enabled : null
  tunneling_enabled      = var.bastion_host.sku == "Standard" ? var.bastion_host.tunneling_enabled : null

  // Configuring IP settings for the Bastion host
  ip_configuration {
    name                 = var.bastion_host.ip_configuration.name
    subnet_id            = azurerm_subnet.subnet.id
    public_ip_address_id = azurerm_public_ip.public_ip.id
  }

  // Adding tags to the Bastion host
  tags = var.tags
}
Declaration of input variables>

Declaration of input variables #

The variables.tf file in Terraform defines the variables I will use in the main.tf file. These variables allow for more flexibility and reusability in the code. In this example, the variables are defined in the variables.tf include:

  • resource_group: This variable will hold the details of the resource group where the resources will be created.

  • vnet: This variable will store the details of the existing virtual network.

  • subnet: This variable will hold the details of the subnet that will be created. The subnet must be named AzureBastionSubnet and must have a subnet size of /26 or greater.

  • public_ip: This variable will store the details of the public IP address that will be created. The public IP must be a standard SKU, a static allocation method, and a unique resource name.

  • bastion_host: This variable will hold all the details of the Azure Bastion Host that will be created.

  • tags: This block declares a variable named tags, which is a map of strings. It is used to assign tags to the Azure resources being created. For example, you can use a key-value pair such as Terraform = true to indicate that the resource was deployed with Terraform.

// Variable for the resource group details
variable "resource_group" {
  description = "The details of the resource group in which resources will be created"
  type = object({
    name = string 
  })
}

// Variable for the virtual network details
variable "vnet" {
  description = "The details of the existing virtual network"
  type = object({
    name                = string 
    resource_group_name = string 
  })
}

// Variable for the subnet details
variable "subnet" {
  description = "The details of the subnet to be created"
  type = object({
    name             = string // The subnet name must be "AzureBastionSubnet".
    address_prefixes = list(string) // The subnet size must be /26 or larger.
  })
}

// Variable for the public IP details
variable "public_ip" {
  description = "The details of the public IP to be created"
  type = object({
    name              = string 
    allocation_method = string // The Public IP address allocation method must be Static.
    sku               = string // The Public IP address SKU must be Standard.
  })
}

// Variable for the Azure Bastion Host details
variable "bastion_host" {
  description = "The details of the Azure Bastion Host to be created"
  type = object({
    name                   = string 
    sku                    = string // SKU of the Azure Bastion Host, either "Basic" or "Standard"
    copy_paste_enabled     = bool 
    file_copy_enabled      = bool 
    ip_connect_enabled     = bool 
    scale_units            = number /// Number of scale units for the Azure Bastion Host, between 2-50
    shareable_link_enabled = bool 
    tunneling_enabled      = bool 
    ip_configuration = object({
      name = string // Name of the IP configuration for the Azure Bastion Host.
    })
  })
}

// Variable for tags
variable "tags" {
  description = "Common tags for all resources"
  type = object({
    Environment = string 
    Terraform   = string 
  })
  default = {
    Environment = "www.jorgebernhardt.com"
    Terraform   = "true"
  }
}
Declaration of output values>

Declaration of output values #

The output.tf file in Terraform extracts and displays information about the resources created or managed by your Terraform configuration. These outputs are defined using the output keyword and can be used to return information that can be particularly helpful for troubleshooting or for referencing these resources in future configurations or scripts.

In this example, the output.tf file returns information about the Public IP Address, the Azure Bastion Host’s location, ID, and name that were created.

Once Terraform has finished applying your configuration, it will display the defined outputs.

// Output for the Public IP Address
output "public_ip_address" {
  description = "The Public IP Address"
  value       = azurerm_public_ip.public_ip.ip_address
}

// Output for the Azure Bastion Host
output "bastion_host_location" {
  description = "The Location of the Azure Bastion Host"
  value       = azurerm_bastion_host.bastion_host.location
}

// Output for the Azure Bastion Host
output "bastion_host_id" {
  description = "The ID of the Azure Bastion Host"
  value       = azurerm_bastion_host.bastion_host.id
}

// Output for the Azure Bastion Host
output "bastion_host_name" {
  description = "The Name of the Azure Bastion Host"
  value       = azurerm_bastion_host.bastion_host.name
}
Executing the Terraform Deployment>

Executing the Terraform Deployment #

Now that you’ve declared the resources correctly, it’s time to take the following steps to deploy them in your Azure environment.

  • Initialization: To begin, execute the terraform init command. This will initialize your working directory that holds the .tf files and download the provider specified in the provider.tf file, and configure the Terraform backend. I suggest looking at this link if you’re curious about the process.

  • Planning: Next, execute the terraform plan. This command creates an execution plan and shows Terraform’s actions to achieve the desired state defined in your .tf files. This gives you a chance to review the changes before applying them.

  • Apply: When you’re satisfied with the plan, execute the terraform apply command. This will implement the required modifications to attain the intended infrastructure state. Before making any changes, you will be asked to confirm your decision.

  • Inspection: After applying the changes, you can use terraform show command to see the current state of your infrastructure.

  • Destroy (optional): when a project is no longer needed or when resources have become outdated. You can use the terraform destroy command. This will remove all the resources that Terraform has created.

References and useful links #

Thank you for taking the time to read my post. I sincerely hope that you find it helpful.