Secure Authentication of Azure Resource Management Deployments

Best practices for authenticating AzureRM cmdlets in a Continuous Deployment system.

Overview

In my current job much of my day to day activity is spent developing and maintaining tools for build and deployment automation.  In particular, most of my work involves the Microsoft Azure Cloud, and the current and preferred approach for provisioning and deploying is to use ARM (Azure Resource Manager).

Many of the tools and scripts I write for deployments are written in PowerShell using the Azure PowerShell Cmdlets and these are generally run from a build system (e.g. Bamboo, TeamCity,  …).  As such, we need to be able to authenticate the PowerShell session with Azure at run time in what is effectively a headless environment.

AzureRM vs Azure Service Management

It is important to clarify now some key differences between the (classic or perhaps legacy) Azure Service Management Cmdlets and the newer Azure Resource Manager Cmdlets.  The Service Management Cmdlets can be typically authenticated by importing a PublishSettings file (see: Get-AzurePublishSettingsFile and Import-AzurePublishSettingsFile).  This “permanently” sets up your environment  to use management certificates to authenticate with Azure.  This is not the most secure solution since there is no clear association between the certificate thumbprints and the user accounts.  Also note that Service Management API (and the associated authentication) cannot be used for ARM deployments and related tasks.

Things are quite different with the AzureRM cmdlets.  In a typical PowerShell session you will use the Add-AzureRmAccount (or Login-AzureRmAccount) which will prompt you for your Azure credentials.  But here is were you might run into issues in your automated environment:  How do you secure your credentials used for AzureRM authentication?

Approaches to AzureRM Authentication

Simple AccountName-Password Authentication

$azureAccountName = 'your account name'
$azurePassword = 'your password' | ConvertTo-SecureString -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($azureAccountName, $azurePassword)
Login-AzureRmAccount -Credential $psCred

This approach is the easiest. You can store your account name and password somewhere securely and load them up at run time. But in a build system where everything is logged, your credentials will show up in the logs so this is not something you want to do.

One might think saving the credentials object (or password SecureString) to a file will be fine:

$psCred = New-Object System.Management.Automation.PSCredential -ArgumentList ('username', ('password' | ConvertTo-SecureString -AsPlainText -Force))
$psCred | Out-File -FilePath .\myCreds

But there are problems here with the way Secure Strings are generated and tied to the local machine. You can work around this using a custom encryption key, but there are better, more secure ways that I’ll cover later in this article.

Saving your Azure Account Profile

A more elegant solution in my opinion is to use a save AzureRmProfile; see Save-AzureRmProfile and Select-AzureRmProfile.  This approach is great and even has the advantage of allowing you to manage separate profiles for each Azure Subscription you are dealing with — but we still have some issue and here’s were we get to the real purpose of this article.  One of the key problems with this approach is that the authenticated session stored in your profile will expire.  I’ve searched for documentation on this and haven’t been able to find the lifespan of the authenticated session stored in your profiles but it appears it may last for several days.  For some uses this might be considered adequate, but realistically when we are talking about CD (Continuous Deployment) and automation we want something longer lived than that.

Other Issues with User Credentials and Profiles

On a deeper level, the techniques outlined so far do not feel right to me for a number of reasons:

  1. Tying an automated process to user credentials is a bad idea.  Particular if the person setting up your deployment automation leaves your company for some reason then things will break when their account is disabled.  Similarly, just changing their password will break the automated process.
  2. DevOps personnel will typically have Admin/Owner roles in the subscriptions they are deploying to.  So using these credentials may be giving too many privileges to your automated processes.

Using an Azure AD ServicePrincipal for Authentication

So, here I’m getting to what I now consider a best-practice for authenticating your ARM PowerShell scripts tools with Azure.  Azure Active Directory (AAD) allows you to register applications (e.g. your deployment tools) and create a ServicePrincipal for authentication and access control.  This leads to a number of crucial advantages over previously mentioned methods for authentication:

  1. Your deployment PowerShell scripts are not tied to a particular user.  Each tool or application can be registered separately and have a unique ApplicationId within AAD.
  2. RBAC (Role Based Access Control).  This seems to be a big topic in Azure these days and for good reason.  RBAC allows you to control the granularity of what your users, or applications/service principals can see and do.
  3. Fine-grained control over when you want access to expire.

There are a number of approaches that can be used with AAD but in this article I will only cover the case of using a self-signed certificate with KeyCredential authentication.  Note that I discovered much of this while figuring out access control on Azure KeyVault, something I will cover in detail in a future article.

Generate Your Self-Signed Certificate:

$now = Get-Date
$startDate = (Get-Date $now -UFormat '%m/%d/%Y')
$endDate = (Get-Date $now.AddMonths(1) -UFormat '%m/%d/%Y')
& "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\x64\makecert.exe" -sv aad-auth.pvk -n "cn=AAD Authentication" aad-auth.cer -b $startDate -e $endDate -len 2048 -r
# Convert your key and cert into a PFX file.
& "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\pvk2pfx.exe" -pvk aad-auth.pvk -spc aad-auth.cer -pfx aad-auth.pfx -po 'your password'

This code (run in a PowerShell console) will generate a self-signed certificate that will be good for 1 month only. You can adjust the certificate lifespan to what you feel is appropriate for your organization and project.

The paths to the makecert.exe and pvk2pfx.exe utilities may vary depending on your setup.  Also note that makecert.exe  and pvk2pfx.exe will prompt you a few times for passwords via popup dialog boxes.  Please remember to use a secure password to protect your keys and certificates.

Create a KeyCredential Object:

$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($PfxPath, $PfxPassword)
# print out your cert thumbprint:
$cert.Thumbprint
# Get the key value as Base64:
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
# Create the KeyCredential object:
Add-Type -Path 'C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\AzureRM.Resources\Microsoft.Azure.Commands.Resources.dll'
$credentialEndDate = $now.AddDays(14)
$keyId = [guid]::NewGuid()
$keyCredential = New-Object -TypeName Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADKeyCredential
$keyCredential.StartDate = $now
$keyCredential.EndDate = $credentialEndDate
$keyCredential.KeyId = $keyId
$keyCredential.Type = 'AsymmetricX509Cert'
$keyCredential.Usage = 'Verify'
$keyCredential.Value = $keyValue

Note that I am using the variables $PfxPath for the full path to the new PFX file and $PfxPassword for the secure password protecting the PFX file.
An important point to make here is the use of a different, shorter lifespan (14 days) for the KeyCredential object compared to the certificate (1 month). You could create a very long-lived certificate and simply control expiry through the KeyCredential object.

Create Your AAD Application using the KeyCredential for Authentication:

$pfxBaseName = (Get-Item -Path $PfxPath).BaseName
$azureAdApplication = New-AzureRmADApplication -DisplayName $pfxBaseName -HomePage ('https://{0}' -f $pfxBaseName) -IdentifierUris ('https://{0}' -f $pfxBaseName) -KeyCredentials $keyCredential

Note that the URL’s do not have to resolve to real hosts. Here, for convenience, I am using the basename of the pfx file to define my application Display Name and URL’s. You can use something else if you feel it is more helpful.

Create Your Service Principal:

$applicationId = ($azureAdApplication.ApplicationId).Guid
$servicePrincipal = New-AzureRmADServicePrincipal -ApplicationId $applicationId
# Output the ApplicationId:
$applicationId
# Output the ServicePrincipal ObjectId:
$servicePrincipal.Id.Guid

Save the ApplicationId and ServicePrincipalId for convenience, but you can always look them up later from AAD using PowerShell.
Note that here you must wait 15 to 20 seconds for the operation to complete. Even if you test with the Get-AzureRmAdServicePrincipal cmdlet you will get a valid response even before it is ready for use.

Add a Role for your New Application:

$role = New-AzureRmRoleAssignment -RoleDefinitionName 'Owner' -ServicePrincipalName $applicationId

Note that in this example I am not restricting the access of the application — it should default to the current Azure Subscription. The New-AzureRmRoleAssignment does allow you to have more fine-grained control allowing you to define a ResourceGroup or a particular Resource that you can restrict access to.
Also note that I am granting the ‘Owner’ role to this application. You can use the Get-AzureRmRoleDefinition cmdlet to see other available roles in your account or subscription.

Login and Test Your New Application Authentication:

First you must import your PFX file into your CurrentUser/My store.  This is most easily done by double-clicking on the PFX file and providing your password.  (In a future post I will provide details on how to automate this too.)

# Retrieve a subscription object:
$subscription = Get-AzureRmSubscription | where {$_.SubscriptionName -eq 'your subscription name'}
Login-AzureRmAccount -CertificateThumbprint $cert.Thumbprint -ApplicationId $azureAdApplication.ApplicationId -ServicePrincipal -TenantId $subscription.TenantId
# If you don't get an error it worked!
# Now verify this with an AzureRM cmdlet to list all Resource Groups in the subscription:
Get-AzureRmResourceGroup

Summary

And that is it. All you will need to do is install the PFX (certificate) on your build machine(s) and use the ApplicationId (GUID) and Subscription TenantId to authentication your deployment scripts.
Note that if you don’t do anything your KeyCredentials will eventually expire, and eventually the certificate will too. This is a good thing in terms of security in case any of these details leak out; however, it is still recommended that you store your PFX files and associated passwords in a secure location (I have a separate, secure Git repo for keys, certificates and other secrets that very few people have access to).
If you think these credentials have been compromised in any way you can easily revoke them using the Remove-AzureRmADApplication cmdlet. Removing the AAD application will also remove the associate Service Principal and its Role assignment. Using the above snippets you can easily automate the creation of a new AAD application and Service Principal.

Author: jkirkham1965

I specialize in designing and implementing DevOps automation solutions, particular with the Azure Cloud platform. Additionally, I am interested in and explore functional programming in languages like F#, Haskell and ELM,; plus have had a long time interest in data science.

2 thoughts on “Secure Authentication of Azure Resource Management Deployments”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s