Azure: Bootstrapping A Subscription for Terraform Using Powershell

Mike Ramplin
3 min readMar 9, 2021

When we’re creating and managing multiple Azure subscriptions using Terraform it always seems a little old fashioned having to create the pre-requisites components terraform needs by hand. So to get rid of this manual step, let’s automate it with Powershell.

So, we’re starting with an empty Azure Subscription that we want to bootstrap ready to use with Terraform.

Logging Into Azure

First of all we need to log into Azure. It gets really annoying being prompted for your Azure credentials every time you run a script; so we’ll use Get-AzContext to check we’re not currently logged on before trying to log in.

If( -not (Get-AzContext))
{
Connect-AzAccount
}

Setting Up Azure KeyVault and creating our Service Principal

Rather than copying and pasting our Service Principal and it’s secret every time we want to run terraform, we’ll store our Service Principal details in an Azure KeyVault. Before we can do that we need to create our KeyVault using New-AzKeyVault; and once we’ve done that, not forgetting to give ourselves access using Set-AzKeyVaultAccessPolicy!

New-AzKeyVault -Name <key vault name> 
-ResourceGroupName <resource group>
-Location <azure region>
-Sku Standard
Set-AzKeyVaultAccessPolicy -VaultName <key vault name>
-ResourceGroupName <resource group name>
-ObjectId $(Get-AzADUser).Id
-PermissionsToSecrets set,get,list

Now we have a vault to store our credentials in we’ll create our ServicePrincipal using New-AzADServicePrincipal setting the scope as our Subscription ID

$spn = New-AzADServicePrincipal -Scope /subscriptions/<subscription id> -DisplayName <display name>

And now we have an SPN lets write it to our Azure KeyVault for us to use later. We can use Set-AzKeyVaultSecret to write our SPN to key vault; but as the SPN ID is plain text we first of all need to convert it to a SecureString

Set-AzKeyVaultSecret -VaultName <key vault name> 
-Name “TerraformServicePrincipalName”
-SecretValue $(ConvertTo-SecureString $spn.ApplicationId -AsPlainText -Force)

We don’t need to do the same conversion with our SPN secret as it’s already a SecureString

Set-AzKeyVaultSecret -VaultName <key vault name> -Name “TerraformServicePrincipalSecret” -SecretValue $spn.Secret

Creating the Storage Account to Store our Terraform State

Now we’ll create the resource group that we’ll use to store our storage account using New-AzResourceGroup. Once we have that resource group we’ll create our Storage Account using New-AzStorageAccount and finally we’ll create a Storage Container in our Storage Account using New-AzStorageContainer

New-AzResourceGroup -Name <resource group> 
-Location <region>
$storageAccount = New-AzStorageAccount -ResourceGroupName <resource group> -Name <storage account name> -Location <region> -SkuName Standard_LRS New-AzStorageContainer -Name <storage account name> -Permission Off -Context $storageAccount.Context

Terraform Bootstrapping

Now we have our Azure infrastructure up and running we can bootstrap our Terraform environment ready to run our code. First of all we need to retrieve our Service Principal. We’re going to use a known name for our Service Principal and retrieve the Service Principal using the known display name

$spn = Get-AzADServicePrincipal -DisplayName <spn display name>

And once we have that value we’ll retrieve our Service Principal secret from the Azure KeyVault we retrieved earlier.

Once we have the SPN Secret stored in the $spnSecret variable we need to convert it from a SecureString to a regular string. Where we make a call out to the .NET functions SecureStringToBSTR and PtrToStringBSTR. This will leave us with the SPN secret in our $spnSecretValue environment variable.

$spnSecret = Get-AzKeyVaultSecret -VaultName $kvName -Name ‘TerraformServicePrincipalSecret’$spnSecretPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($spnSecret.SecretValue)$spnSecretValue = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($spnSecretPtr)

Now we have the SPN value; and SPN secret, we’ll set the values of the environment variable ARM_CLIENT_ID to our service principal ID, and the ARM_CLIENT_SECRET environment variable to our SPN secret.

Additionally we’ll set ARM_SUBSCRIPTION_ID to our Azure Subscription; and ARM_TENANT_ID to our Azure Tenant ID.

$env:ARM_CLIENT_ID=$spn.ApplicationId
$env:ARM_CLIENT_SECRET=$spnSecretValue
$env:ARM_SUBSCRIPTION_ID=<subscription ID>
$env:ARM_TENANT_ID=<tenant ID>

Terraform will use these variables to retrieve the credentials and authenticate to Azure using our SPN when running Terraform code.

We’re now just left with setting up our skeleton Terraform code so we can initialise our environment. We’ll generate our terraform-backend.tf file with the details we generated earlier in this post to point to the infrastructure we created.

$string = @”
terraform {
backend “azurerm” {
resource_group_name = “<resource group name>”
storage_account_name = “<storage account name>”
container_name = “<storage container name>”
key = “terraform.tfstate”
}
}
“@ | Set-Content terraform-backend.tf

Then we’ll set-up our skeleton main.tf file to download our azurerm provider; and we’ll create a test resource group just to test

$string = @”
provider “azurerm” {
version = “~>2.0”
features {}
}
resource “azurerm_resource_group” “rg” {
name = “tfbootstrap-test”
location = “<region>”
}
“@ | Set-Content main.tf

At this point we’re ready to run. So we run terraform init which initialises our environment

The code developed as part of this article can be found here.

--

--