Skip to content

Instantly share code, notes, and snippets.

@adamrushuk
Created February 17, 2019 17:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adamrushuk/878b783a6fbe56b558683857bef9ad5b to your computer and use it in GitHub Desktop.
Save adamrushuk/878b783a6fbe56b558683857bef9ad5b to your computer and use it in GitHub Desktop.
Configures Azure for secure Terraform access. Loads Azure Key Vault secrets into Terraform environment variables for the current PowerShell session.
<#
.SYNOPSIS
Configures Azure for secure Terraform access.
.DESCRIPTION
Configures Azure for secure Terraform access using Azure Key Vault.
The following steps are automated:
- Creates an Azure Service Principle for Terraform.
- Creates a new Resource Group.
- Creates a new Storage Account.
- Creates a new Storage Container.
- Creates a new Key Vault.
- Configures Key Vault Access Policies.
- Creates Key Vault Secrets for these sensitive Terraform login details:
- ARM_SUBSCRIPTION_ID
- ARM_CLIENT_ID
- ARM_CLIENT_SECRET
- ARM_TENANT_ID
- ARM_ACCESS_KEY
.NOTES
Assumptions:
- Azure PowerShell module is installed: https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
- Azure CLI is installed: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows
- You are already logged into Azure before running this script (eg. Connect-AzAccount)
Author: Adam Rush
Blog: https://adamrushuk.github.io
GitHub: https://github.com/adamrushuk
Twitter: @adamrushuk
#>
[CmdletBinding()]
param (
# This is used to assign yourself access to KeyVault
$adminUserDisplayName = '<Joe Bloggs>',
$servicePrincipleName = 'terraform',
$servicePrinciplePassword = 'MyStrongPassw0rd!',
$resourceGroupName = 'terraform-mgmt-rg',
$location = 'eastus',
$storageAccountSku = 'Standard_LRS',
$storageContainerName = 'terraform-state',
# Prepend random prefix with A character, as some resources cannot start with a number
$randomPrefix = ("a" + -join ((48..57) + (97..122) | Get-Random -Count 8 | ForEach-Object {[char]$_})),
$vaultName = "$randomPrefix-terraform-kv",
$storageAccountName = "$($randomPrefix)terraform"
)
#region Helper function for padded messages
function Write-HostPadded {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$Message,
[Parameter(Mandatory = $false)]
[String]
$ForegroundColor,
[Parameter(Mandatory = $false)]
[Int]
$PadLength = 80,
[Parameter(Mandatory = $false)]
[Switch]
$NoNewline
)
$writeHostParams = @{
Object = $Message.PadRight($PadLength, '.')
}
if ($ForegroundColor) {
$writeHostParams.Add('ForegroundColor', $ForegroundColor)
}
if ($NoNewline.IsPresent) {
$writeHostParams.Add('NoNewline', $true)
}
Write-Host @writeHostParams
}
#endregion Helper function for padded messages
#region Check Azure login
Write-HostPadded -Message "Checking for an active Azure login..." -NoNewline
# Get current context
$azContext = Get-AzContext
if (-not $azContext) {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw "There is no active login for Azure. Please login first (eg 'Connect-AzAccount' and 'az login'"
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Check Azure login
#region New Terraform SP (Service Principal)
Write-HostPadded -Message "Using Azure CLI to Create a Terraform Service Principle: [$servicePrincipleName] ..." -NoNewline
try {
az ad sp create-for-rbac --name $servicePrincipleName --password $servicePrinciplePassword | Out-String | Write-Verbose
$terraformSP = Get-AzADServicePrincipal -DisplayName $servicePrincipleName -ErrorAction 'Stop'
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion New Terraform SP (Service Principal)
#region Get Subscription
$taskMessage = "Finding Subscription and Tenant details"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$subscription = Get-AzSubscription -ErrorAction 'Stop'
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Get Subscription
#region New Resource Group
$taskMessage = "Creating Terraform Management Resource Group: [$resourceGroupName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azResourceGroupParams = @{
Name = $resourceGroupName
Location = $location
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
New-AzResourceGroup @azResourceGroupParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion New Resource Group
#region New Storage Account
$taskMessage = "Creating Terraform backend Storage Account: [$storageAccountName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azStorageAccountParams = @{
ResourceGroupName = $resourceGroupName
Location = $location
Name = $storageAccountName
SkuName = $storageAccountSku
Kind = 'StorageV2'
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
New-AzStorageAccount @azStorageAccountParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion New Storage Account
#region Select Storage Container
$taskMessage = "Selecting Default Storage Account"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azCurrentStorageAccountParams = @{
ResourceGroupName = $resourceGroupName
AccountName = $storageAccountName
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
Set-AzCurrentStorageAccount @azCurrentStorageAccountParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Select Storage Account
#region New Storage Container
$taskMessage = "Creating Terraform State Storage Container: [$storageContainerName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azStorageContainerParams = @{
Name = $storageContainerName
Permission = 'Off'
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
New-AzStorageContainer @azStorageContainerParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion New Storage Container
#region New KeyVault
$taskMessage = "Creating Terraform KeyVault: [$vaultName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azKeyVaultParams = @{
VaultName = $vaultName
ResourceGroupName = $resourceGroupName
Location = $location
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
New-AzKeyVault @azKeyVaultParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion New KeyVault
#region Set KeyVault Access Policy
$taskMessage = "Setting KeyVault Access Policy for Admin User: [$adminUserDisplayName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
$adminADUser = Get-AzADUser -DisplayName $adminUserDisplayName
try {
$azKeyVaultAccessPolicyParams = @{
VaultName = $vaultName
ResourceGroupName = $resourceGroupName
ObjectId = $adminADUser.Id
PermissionsToKeys = @('Get', 'List')
PermissionsToSecrets = @('Get', 'List', 'Set')
PermissionsToCertificates = @('Get', 'List')
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
Set-AzKeyVaultAccessPolicy @azKeyVaultAccessPolicyParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
$taskMessage = "Setting KeyVault Access Policy for Terraform SP: [$servicePrincipleName]"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
$azKeyVaultAccessPolicyParams = @{
VaultName = $vaultName
ResourceGroupName = $resourceGroupName
ObjectId = $terraformSP.Id
PermissionsToKeys = @('Get', 'List')
PermissionsToSecrets = @('Get', 'List', 'Set')
PermissionsToCertificates = @('Get', 'List')
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
Set-AzKeyVaultAccessPolicy @azKeyVaultAccessPolicyParams | Out-String | Write-Verbose
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Set KeyVault Access Policy
#region Terraform login variables
# Get Storage Access Key
$storageAccessKeys = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName
$storageAccessKey = $storageAccessKeys[0].Value # only need one of the keys
$terraformLoginVars = @{
'ARM-SUBSCRIPTION-ID' = $subscription.Id
'ARM-CLIENT-ID' = $terraformSP.ApplicationId
'ARM-CLIENT-SECRET' = $servicePrinciplePassword
'ARM-TENANT-ID' = $subscription.TenantId
'ARM-ACCESS-KEY' = $storageAccessKey
}
Write-Host "`nTerraform login details:"
$terraformLoginVars | Out-String | Write-Verbose
#endregion Terraform login variables
#region Create KeyVault Secrets
$taskMessage = "Creating KeyVault Secrets for Terraform"
Write-HostPadded -Message "`n$taskMessage..." -NoNewline
try {
foreach ($terraformLoginVar in $terraformLoginVars.GetEnumerator()) {
$AzKeyVaultSecretParams = @{
VaultName = $vaultName
Name = $terraformLoginVar.Key
SecretValue = (ConvertTo-SecureString -String $terraformLoginVar.Value -AsPlainText -Force)
ErrorAction = 'Stop'
Verbose = $VerbosePreference
}
Set-AzKeyVaultSecret @AzKeyVaultSecretParams | Out-String | Write-Verbose
}
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Create KeyVault Secrets
<#
.SYNOPSIS
Loads Azure Key Vault secrets into Terraform environment variables for the current PowerShell session.
.DESCRIPTION
Loads Azure Key Vault secrets into Terraform environment variables for the current PowerShell session.
The following steps are automated:
- Identifies the Azure Key Vault matching a search string (default: 'terraform-kv').
- Retrieves the Terraform secrets from Azure Key Vault.
- Loads the Terraform secrets into these environment variables for the current PowerShell session:
- ARM_SUBSCRIPTION_ID
- ARM_CLIENT_ID
- ARM_CLIENT_SECRET
- ARM_TENANT_ID
- ARM_ACCESS_KEY
.NOTES
Assumptions:
- Azure PowerShell module is installed: https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
- You are already logged into Azure before running this script (eg. Connect-AzAccount)
Author: Adam Rush
Blog: https://adamrushuk.github.io
GitHub: https://github.com/adamrushuk
Twitter: @adamrushuk
#>
[CmdletBinding()]
param (
# Find the Azure Key Vault that includes this string in it's name
$keyVaultSearchString = 'terraform-kv'
)
#region Helper function for padded messages
function Write-HostPadded {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[String]
$Message,
[Parameter(Mandatory = $false)]
[String]
$ForegroundColor,
[Parameter(Mandatory = $false)]
[Int]
$PadLength = 60,
[Parameter(Mandatory = $false)]
[Switch]
$NoNewline
)
$writeHostParams = @{
Object = $Message.PadRight($PadLength, '.')
}
if ($ForegroundColor) {
$writeHostParams.Add('ForegroundColor', $ForegroundColor)
}
if ($NoNewline.IsPresent) {
$writeHostParams.Add('NoNewline', $true)
}
Write-Host @writeHostParams
}
#endregion Helper function for padded messages
#region Check Azure login
Write-HostPadded -Message "Checking for an active Azure login..." -NoNewline
# Get current context
$azContext = Get-AzContext
if (-not $azContext) {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw "There is no active login for Azure. Please login first (eg 'Connect-AzAccount' and 'az login'"
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Check Azure login
#region Identify Azure Key Vault
$loadMessage = "loading Terraform environment variables just for this PowerShell session"
Write-Host "`nSTARTED: $loadMessage" -ForegroundColor 'Green'
# Get Azure objects before Key Vault lookup
Write-HostPadded -Message "Searching for Terraform KeyVault..." -NoNewline
$tfKeyVault = Get-AzKeyVault | Where-Object VaultName -match $keyVaultSearchString
if (-not $tfKeyVault) {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw "Could not find Azure Key Vault with name including search string: [$keyVaultSearchString]"
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Identify Azure Key Vault
#region Get Azure KeyVault Secrets
Write-HostPadded -Message "Retrieving Terraform secrets from Azure Key Vault..." -NoNewline
$secretNames = @(
'ARM_SUBSCRIPTION_ID'
'ARM_CLIENT_ID'
'ARM_CLIENT_SECRET'
'ARM_TENANT_ID'
'ARM_ACCESS_KEY'
)
$terraformEnvVars = @{}
# Compile Get Azure KeyVault Secrets
foreach ($secretName in $secretNames) {
try {
# Retrieve secret
$azKeyVaultSecretParams = @{
Name = $secretName -replace '_', '-'
VaultName = $tfKeyVault.VaultName
ErrorAction = 'Stop'
}
$tfSecret = Get-AzKeyVaultSecret @azKeyVaultSecretParams
# Add secret to hashtable
$terraformEnvVars.$secretName = $tfSecret.SecretValueText
} catch {
Write-Error -Message "ERROR: $taskMessage." -ErrorAction 'Continue'
throw $_
}
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
#endregion Get Azure KeyVault Secrets
#region Load Terraform environment variables
$sessionMessage = "Setting session environment variables for Azure / Terraform"
Write-Host "`nSTARTED: $sessionMessage" -ForegroundColor 'Green'
foreach ($terraformEnvVar in $terraformEnvVars.GetEnumerator()) {
Write-HostPadded -Message "Setting [$($terraformEnvVar.Key)]..." -NoNewline
try {
$setItemParams = @{
Path = "env:$($terraformEnvVar.Key)"
Value = $terraformEnvVar.Value
ErrorAction = 'Stop'
}
Set-Item @setItemParams
} catch {
Write-Host "ERROR!" -ForegroundColor 'Red'
throw $_
}
Write-Host "SUCCESS!" -ForegroundColor 'Green'
}
Write-Host "FINISHED: $sessionMessage" -ForegroundColor 'Green'
Write-Host "`nFINISHED: $loadMessage" -ForegroundColor 'Green'
#endregion Load Terraform environment variables
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment