Starting with Terraform, Windows and Azure Part 3

In the last two posts we got our local toolset going. Now it is time to make sure you can actually connect to your Azure tenant with Terraform. This assumes you have access to an Azure Tennant already. If you don’t then it is really easy to get started with you own Azure Tennant and Microsoft will even throw some credits your way. You can find more information here.

My preferred method of setting up Terraform involves PowerShell, the AzureRM module and a script. The objective is to create a Service Principal and obtain the ClientID and Client Secret. These can be incorporated into you Terraform Scripts or put into environmental variables to keep your terraform templates and modules a bit safer.

macky

 

Now that we got that out of the way, I’ve devised a script to setup the service principal and obtain the credentials.

I Tend to write scripts with mostly functions in them, some I stole, some are my own feel free to use the material provided in any way you see fit. 😉

First some variables:

# Variables that can be defined, or left blank
[String]$azure_client_name=""     	# Application name
[Securestring]$azure_client_secret  # Application password
[String]$azure_group_name=""
[String]$azure_storage_name=""
[String]$azure_subscription_id="" 	# Derived from the account after login
[String]$azure_tenant_id=""       	# Derived from the account after login
[String]$location="West Europe"
[String]$azure_object_id=""

I thought is was a good idea to get the requirements, just to be certain. The following function checks if you have Azure PowerShell installed. If not, you can get is here.

function Requirements() {
	$found=0
	$azureversion = (Get-Module -ListAvailable -Name Azure -Refresh)
	If ($azureversion.Version.Major -gt 0) {
		$found=$found + 1
		Write-Output "Found Azure PowerShell version: $($azureversion.Version.Major).$($azureversion.Version.Minor)"
	}
	Else {
		Write-Output "Azure PowerShell is missing. Please download and install Azure PowerShell from"
		Write-Output "http://aka.ms/webpi-azps"		
	}
	return $found
}

Then we need to get the subscription:

function AskSubscription() {
	$azuresubscription = Add-AzureRmAccount
	$script:azure_subscription_id = $azuresubscription.Context.Subscription.SubscriptionId
	$script:azure_tenant_id = $azuresubscription.Context.Subscription.TenantId		
}

Some code to generate a Random complex password; Note, you’ll get the option to put in your own password if you prefer.

Function RandomComplexPassword () {
	param ( [int]$Length = 8 )
 	#Usage: RandomComplexPassword 12
 	$Assembly = Add-Type -AssemblyName System.Web
 	$RandomComplexPassword = [System.Web.Security.Membership]::GeneratePassword($Length,2)
 	return $RandomComplexPassword
}

We also need to provide a name for our service principal.

function AskName() {
	Write-Output ""
	Write-Output "Choose a name for your client."
	Write-Output "This is mandatory - do not leave blank."
	Write-Output "ALPHANUMERIC ONLY. Ex: mytfdeployment."
	Write-Output -n "> "
	$script:meta_name = Read-Host
}

Now the time comes to use the random complex password and convert it to a secure string for creating the service principle name.

function AskSecret() {
	Write-Output ""
	Write-Output "Enter a secret for your application. We recommend generating one with"
	Write-Output "openssl rand -base64 24. If you leave this blank we will attempt to"
	Write-Output "generate one for you using .Net Security Framework. THIS WILL BE SHOWN IN PLAINTEXT AND MIGHT NOT WORK, so please make your own."
	Write-Output "Ex: myterraformsecret8734"
	Write-Output -n "> "
	$script:azure_client_secret = Read-Host
	if ($script:azure_client_secret -eq "") {
		$script:azure_client_secret = RandomComplexPassword(43)
	}	
	Write-Output "Client_secret: $script:azure_client_secret"
	$script:password = ConvertTo-SecureString $script:azure_client_secret -AsPlainText -Force
}

Time to create the Service Principle.

function CreateServicePrinciple() {
	Write-Output "==> Creating service principal"
	$app = New-AzureRmADApplication -DisplayName $meta_name -HomePage "https://$script:meta_name" -IdentifierUris "https://$script:meta_name" -Password $script:password
 	New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId	
	#sleep 30 seconds to allow resource creation to converge
	Write-Output "Allow for 30 seconds to create the service principal"
	Start-Sleep -s 30
 	New-AzureRmRoleAssignment -RoleDefinitionName Owner -ServicePrincipalName $app.ApplicationId.Guid
	$script:azure_client_id = $app.ApplicationId
	$script:azure_object_id = $app.ObjectId
	if ($error.Count > 0)
	{
		Write-Output "Error creating service principal: $azure_client_id"
		exit
	}
}

Let’s generate some output, so you can copy that and store it somewhere safe! And I mean SAFE! It’s access to your Azure subscription!

function ShowConfigs() {
	Write-Output ""
	Write-Output "Use the following configuration for your Terraform scripts:"
	Write-Output ""
	Write-Output "{"
	Write-Output "      'client_id': $azure_client_id,"
	Write-Output "      'client_secret': $azure_client_secret,"
	Write-Output "      'subscription_id': $azure_subscription_id,"
	Write-Output "      'tenant_id': $azure_tenant_id"
	Write-Output "}"
	Write-Output ""
	Write-Output "Use the following Environmetal variable direct in PowerShell Terminal"
	Write-Output ""
	Write-Output "`$env:ARM_CLIENT_ID=`"$azure_client_id`""
	Write-Output "`$env:ARM_CLIENT_SECRET=`"$azure_client_secret`""
	Write-Output "`$env:ARM_TENANT_ID=`"$azure_tenant_id`""
	Write-Output "`$env:ARM_SUBSCRIPTION_ID=`"$azure_subscription_id`""
	Write-Output ""
}

And now to assemble it all in a script and add the code to actually run all the functions 😀

# Variables that can be defined, or left blank
[String]$azure_client_name=""     	# Application name
[Securestring]$azure_client_secret  # Application password
[String]$azure_group_name=""
[String]$azure_storage_name=""
[String]$azure_subscription_id="" 	# Derived from the account after login
[String]$azure_tenant_id=""       	# Derived from the account after login
[String]$location="West Europe"
[String]$azure_object_id=""


function Requirements() {
	$found=0
	$azureversion = (Get-Module -ListAvailable -Name Azure -Refresh)
	If ($azureversion.Version.Major -gt 0) {
		$found=$found + 1
		Write-Output "Found Azure PowerShell version: $($azureversion.Version.Major).$($azureversion.Version.Minor)"
	}
	Else {
		Write-Output "Azure PowerShell is missing. Please download and install Azure PowerShell from"
		Write-Output "http://aka.ms/webpi-azps"		
	}
	return $found
}

function AskSubscription() {
	$azuresubscription = Add-AzureRmAccount
	$script:azure_subscription_id = $azuresubscription.Context.Subscription.SubscriptionId
	$script:azure_tenant_id = $azuresubscription.Context.Subscription.TenantId		
}

Function RandomComplexPassword () {
	param ( [int]$Length = 8 )
 	#Usage: RandomComplexPassword 12
 	$Assembly = Add-Type -AssemblyName System.Web
 	$RandomComplexPassword = [System.Web.Security.Membership]::GeneratePassword($Length,2)
 	return $RandomComplexPassword
}

function AskName() {
	Write-Output ""
	Write-Output "Choose a name for your client."
	Write-Output "This is mandatory - do not leave blank."
	Write-Output "ALPHANUMERIC ONLY. Ex: mytfdeployment."
	Write-Output -n "> "
	$script:meta_name = Read-Host
}

function AskSecret() {
	Write-Output ""
	Write-Output "Enter a secret for your application. We recommend generating one with"
	Write-Output "openssl rand -base64 24. If you leave this blank we will attempt to"
	Write-Output "generate one for you using .Net Security Framework. THIS WILL BE SHOWN IN PLAINTEXT."
	Write-Output "Ex: myterraformsecret8734"
	Write-Output -n "> "
	$script:azure_client_secret = Read-Host
	if ($script:azure_client_secret -eq "") {
		$script:azure_client_secret = RandomComplexPassword(43)
	}	
	Write-Output "Client_secret: $script:azure_client_secret"
	$script:password = ConvertTo-SecureString $script:azure_client_secret -AsPlainText -Force
}

function CreateServicePrinciple() {
	Write-Output "==> Creating service principal"
	$app = New-AzureRmADApplication -DisplayName $meta_name -HomePage "https://$script:meta_name" -IdentifierUris "https://$script:meta_name" -Password $script:password
 	New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId	
	#sleep 30 seconds to allow resource creation to converge
	Write-Output "Allow for 30 seconds to create the service principal"
	Start-Sleep -s 30
 	New-AzureRmRoleAssignment -RoleDefinitionName Owner -ServicePrincipalName $app.ApplicationId.Guid
	$script:azure_client_id = $app.ApplicationId
	$script:azure_object_id = $app.ObjectId
	if ($error.Count > 0)
	{
		Write-Output "Error creating service principal: $azure_client_id"
		exit
	}
}

function ShowConfigs() {
	Write-Output ""
	Write-Output "Use the following configuration for your Terraform scripts:"
	Write-Output ""
	Write-Output "{"
	Write-Output "      'client_id': $azure_client_id,"
	Write-Output "      'client_secret': $azure_client_secret,"
	Write-Output "      'subscription_id': $azure_subscription_id,"
	Write-Output "      'tenant_id': $azure_tenant_id"
	Write-Output "}"
	Write-Output ""
	Write-Output "Use the following Environmetal variable direct in PowerShell Terminal"
	Write-Output ""
	Write-Output "`$env:ARM_CLIENT_ID=`"$azure_client_id`""
	Write-Output "`$env:ARM_CLIENT_SECRET=`"$azure_client_secret`""
	Write-Output "`$env:ARM_TENANT_ID=`"$azure_tenant_id`""
	Write-Output "`$env:ARM_SUBSCRIPTION_ID=`"$azure_subscription_id`""
	Write-Output ""
}

$reqs = Requirements	
	if($reqs -gt 0)
	{
		AskSubscription
		AskName
		AskSecret
		CreateServicePrinciple
		ShowConfigs
	}

 

Just copy paste the script above and check the content…(it’s a good habit to check all the code you rip of the web!) and save it as azure-setup.ps1.

You will get an output giving all the info needed to be able to deploy your first resources to Azure.

TLDR;

There are more than one way to setup Terraform and obtain your credentials for Azure. Methods Involving the CLI or the Azure Portal are valid and more information can be found here. The above script fixes everything in about a minute.