
Azure Infrastructure as Code - Part Three
To kick Azure Infrastructure as Code - Part Three off, we first need to install Terraform and then we will continue with completing our very first Terraform lifecycle. Follow along in these two videos as we install Terraform to both a Mac and Windows then proceed with the instructions.
Installing Terraform to Windows
curl.exe -O https://releases.hashicorp.com/terraform/0.12.26/terraform_0.12.26_windows_amd64.zip
Expand-Archive terraform_0.12.26_windows_amd64.zip
Rename-Item -path .\terraform_0.12.26_windows_amd64\ .\terraform
Installing Terraform to Mac
brew install terraform
terraform -install-autocomplete
Running your first Terraform
With Terraform there is a lifecycle for a resource and it can be broken down into four phases: Init, Plan, Apply, and Destroy.
init — Init. Initialize the (local) Terraform environment. Usually executed only once per session.
plan — Plan. Compare the Terraform state with the as-is state in the cloud, build and display an execution plan. This does not change the deployment (read-only).
apply — Apply the plan from the plan phase. This potentially changes the deployment (read and write).
destroy — Destroy all resources that are governed by this specific terraform environment.
This article assumes that you have created an Azure account and subscription. The first thing we will do is install the Azure CLI tools and configure it to be used with terraform.
The Azure CLI Tool installed
Install the Azure CLI tool with brew in MacOSX:
brew update && brew install azure-cli
To install the Azure CLI using PowerShell in Windows, start PowerShell as administrator and run the following command:
$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; Remove-Item .\AzureCLI.msi
You can now run the Azure CLI with the az command from either Windows Command Prompt, PowerShell, or Mac Terminal.
You will use the Azure CLI tool to authenticate with Azure. Terraform must authenticate to Azure to create infrastructure. In your terminal, use the Azure CLI tool to set up your account permissions locally.
az login
You now have logged in using your account you created in previous lectures. In the output in the terminal, find the ID of the subscription that you want to use:
{
"cloudName": "AzureCloud",
"homeTenantId": "0envbwi39-home-Tenant-Id",
"id": "35akss-subscription-id",
"isDefault": true,
"managedByTenants": [],
"name": "Subscription-Name",
"state": "Enabled",
"tenantId": "0envbwi39-TenantId",
"user":
{
"name": "your-username@domain.com",
"type": "user"
}
}
Once you have chosen the account subscription ID, set the account with the Azure CLI.
az account set --subscription "35akss-subscription-id"
Next, we create a Service Principal. A Service Principal is an application within Azure Active Directory with the authentication tokens Terraform needs to perform actions on your behalf. Update the <SUBSCRIPTION_ID> with the subscription ID you specified in the previous step.
az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<SUBSCRIPTION_ID>
The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see the assignment details
{
"appId": "xxxxxx-xxx-xxxx-xxxx-xxxxxxxxxx",
"displayName": "azure-cli-2022-xxxx",
"password": "xxxxxx~xxxxxx~xxxxx",
"tenant": "xxxxx-xxxx-xxxxx-xxxx-xxxxx"
}
Next you need to set your environment variables. HashiCorp recommends setting these values as environment variables rather than saving them in your Terraform configuration. Open a Mac terminal or PowerShell and input the values that were outputted from the previous command. Subscription ID we got from the previous step.
For Mac Terminal
export ARM_CLIENT_ID="<APPID_VALUE>"
export ARM_CLIENT_SECRET="<PASSWORD_VALUE>"
export ARM_SUBSCRIPTION_ID="<SUBSCRIPTION_ID>"
export ARM_TENANT_ID="<TENANT_VALUE>"
For PowerShell
$env:ARM_CLIENT_ID = "APPID_VALUE"
$env:ARM_CLIENT_SECRET = "PASSWORD_VALUE"
$env:ARM_TENANT_ID = "TENANT_VALUE"
$env:ARM_SUBSCRIPTION_ID = "SUBSCRIPTION_ID"
Install Visual Studio Code and Setup Environment
Great! We are all configured to use Azure now. Now the next thing we are going to do is open up a terminal install Visual Studio Code by issuing this command on a Mac:
brew install visual-studio-code
Or on a Windows machine navigating to this URL to download.
Next, in the terminal on Mac we will issue the following commands to create a directory that will contain our Terraform configuration:
mkdir ~/tf-exercise-1
cd ~/tf-exercise-1
And open up a file for main.tf
code main.tf
On Windows create a folder anywhere called "tf-exercise-1" and create a new file called "main" with the file extension ".tf" and open that file with Visual Studio Code
Now we need to write configuration to create a new resource group. Copy and paste the code snippet into the "main.tf" file
# Configure the Azure provider
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0.2"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "myTFResourceGroup"
location = "westus2"
}
Note: The location of your resource group is hardcoded in this example. If you do not have access to the resource group location westus2, update the main.tf file with your Azure region. This is a complete configuration that Terraform can apply. In the following sections we will review each block of the configuration in more detail.
Terraform Block
The terraform {} block contains Terraform settings, including the required providers Terraform will use to provision your infrastructure. For each provider, the source attribute defines an optional hostname, a namespace, and the provider type. Terraform installs providers from the Terraform Registry by default. In this example configuration, the azurerm provider’s source is defined as hashicorp/azurerm, which is shorthand for registry.terraform.io/hashicorp/azurerm.
You can also define a version constraint for each provider in the required_providers block.
The version attribute is optional, but we recommend using it to enforce the provider version. Without it, Terraform will always use the latest version of the provider, which may introduce breaking changes.
Providers
The provider block configures the specified provider, in this case azurerm. A provider is a plugin that Terraform uses to create and manage your resources. You can define multiple provider blocks in a Terraform configuration to manage resources from different providers.
Resource
Use resource blocks to define components of your infrastructure. A resource might be a physical component such as a server, or it can be a logical resource such as a Heroku application.
Resource blocks have two strings before the block: the resource type and the resource name. In this example, the resource type is azurerm_resource_group and the name is rg.
The prefix of the type maps to the name of the provider. In the example configuration, Terraform manages the azurerm_resource_group resource with the azurerm provider.
Together, the resource type and resource name form a unique ID for the resource. For example, the ID for your network is azurerm_resource_group.rg.
Resource blocks contain arguments which you use to configure the resource. The Azure provider documentation documents supported resources and their configuration options, including azurerm_resource_group and its supported arguments.
Initialize your Terraform configuration
Initialize your learn-terraform-azure directory in your terminal. The terraform commands will work with any operating system. Your output should look similar to this one:
terraform init
Initializing the backend...Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 3.0.2"...
- Installing hashicorp/azurerm v3.0.2...
- Installed hashicorp/azurerm v3.0.2 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running “terraform plan” to see any changes that are required for your infrastructure.
All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
Format and validate the configuration
We recommend using consistent formatting in all of your configuration files. The terraform fmt command automatically updates configurations in the current directory for readability and consistency.
Format your configuration. Terraform will print out the names of the files it modified, if any. In this case, your configuration file was already formatted correctly, so Terraform won’t return any file names.
terraform fmt
You can also make sure your configuration is syntactically valid and internally consistent by using the terraform validate command. The example configuration provided above is valid, so Terraform will return a success message.
terraform validate
Success! The configuration is valid.
Apply your Terraform Configuration
Run the terraform apply command to apply your configuration. This output shows the execution plan and will prompt you for approval before proceeding. If anything in the plan seems incorrect or dangerous, it is safe to abort here with no changes made to your infrastructure. Type yes at the confirmation prompt to proceed.
terraform apply
An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the action of creating a resource group:
azurerm_resource_group.rg will be created
+ resource "azurerm_resource_group" "rg" {
+ id = (known after apply)
+ location = "westus2"
+ name = "myTFResourceGroup"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
azurerm_resource_group.rg: Creating...
azurerm_resource_group.rg: Creation complete after 1s [id=/subscriptions/c9ed8610-47a3-4107-a2b2-a322114dfb29/resourceGroups/myTFResourceGroup]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Navigate to the Azure portal in your web browser to check to make sure the resource group was created.
Inspect your state
When you apply your configuration, Terraform writes data into a file called terraform.tfstate. This file contains the IDs and properties of the resources Terraform created so that it can manage or destroy those resources going forward. Your state file contains all of the data in your configuration and could also contain sensitive values in plaintext, so do not share it or check it in to source control.
Inspect the current state using terraform show.
terraform show
azurerm_resource_group.rg:
resource "azurerm_resource_group" "rg" {
id = "/subscriptions/c9ed8610-47a3-4107-a2b2-a322114dfb29/resourceGroups/myTFResourceGroup"
location = "westus2"
name = "myTFResourceGroup"
}
When Terraform created this resource group, it also gathered the resource’s properties and meta-data. These values can be referenced to configure other resources or outputs.
To review the information in your state file, use the state command. If you have a long state file, you can see a list of the resources you created with Terraform by using the list subcommand.
terraform state list
azurerm_resource_group.rg
If you run terraform state, you will see a full list of available commands to view and manipulate the configuration’s state.
terraform state
Usage: terraform state <subcommand> [options] [args]
This command has subcommands for advanced state management. These subcommands can be used to slice and dice the Terraform state. This is sometimes necessary in advanced cases. For your safety, all state management commands that modify the state create a timestamped backup of the state prior to making modifications.
The structure and output of the commands is specifically tailored to work well with the common Unix utilities such as grep, awk, etc. We recommend using those tools to perform more advanced state tasks.
Terraform Destroy
Lastly, issue the terraform destroy command to complete the lifecycle and undo the changes that you made. Terraform keeps a state of the changes you made in the terraform state file so it knows exactly which ones to undo.
terraform destroy
# azurerm_resource_group.rg will be destroyed
resource "azurerm_resource_group" "rg" {
id = "/subscriptions/b7b18fdb-6e24-4934-a25e-2957c9e62d05/resourceGroups/myTFResourceGroup" -> null
location = "westus2" -> null
name = "myTFResourceGroup" -> null
tags = {} -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Summary
You have now completed your very first terraform lifecycle. Congratulations! It's fairly simple, the configuration files get more complex from here but the steps and lifecycle remain the same. We just created a resource group in Azure, but we will continue the terraform exercises by doing something a little more complex and deploying a honeypot using terraform.

Tyler Wall is the founder of Cyber NOW Education. He holds bills for a Master of Science from Purdue University and also CISSP, CCSK, CFSR, CEH, Sec+, Net+, and A+ certifications. He mastered the SOC after having held every position from analyst to architect and is the author of three books, 100+ professional articles, four online courses, and regularly holds webinars for new cybersecurity talent.
You can connect with him on LinkedIn.
To view my dozens of courses, visit my homepage and watch the trailers!
Become a Black Badge member of Cyber NOW® and enjoy all-access for life.
Check out my latest book, Jump-start Your SOC Analyst Career: A Roadmap to Cybersecurity Success, winner of the 2024 Cybersecurity Excellence Awards.