Certificate-based authentication to Azure Resource Manager using Powershell

One big change that the move to Azure Resource Manager (or ARM) brings with it, is that the classic “management certificates” are no longer supported if you’re using the Azure Powershell cmdlets. However, that doesn’t means that you can’t use PKI to authenticate to arm. If you want to talk directly to the arm REST api using Powershell or whatever language you want, certificate-based authentication is actually a very viable alternative to having to supply your every script with a username/password that could potentially be put to some nasty use.

So, without further ado, here’s my end-to-end example of how to set up the thing and perform some operations against the arm api using certificates.

Just to take a birds-eye view at this first:
Any operation you want to do against a “Microsoft cloud resource” generally happens “through” an application created in Azure AD. If you have Office365 you’ll noticed that applications such as Exchange, Sharepoint, etc are automatically created in there:
2015-07-19 15_35_40-Microsoft Edge

Each of these applications inside Azure AD has a set of permissions against the various Microsoft Online Services. Normally ,when logging into an application, this is what happens (simplified):
Option1

The Client (a powershell script, .Net app or whatever) authenticates to Microsoft’s authentication endpoint using a username, password and some way of identifying the Azure AD Application it’s going through. It also presents which resource it wants to access, such as the Office365 Graph api, Azure Resource Manager or something else. If the Azure AD app has access to that resource and the username/password thing works out, the client gets back an access token which can be used to access the actual resource.

With certificate-based access, it’s pretty much the same, excepct that we add a certificate’s public key to the Azure AD application. And instead of using a username/password to authenticate, we use the private key of that same certificate (which has to be present on the computer where the client runs). Something like this:
Option2

So, here’s how to:
1. Define an Azure AD App with a certificate
2. Give that app access to Azure Resource Manager
3. Use the app in a Powershell script to call some of the ARM rest apis.

First, use makecert.exe to create a new certificate. Note that I’m using yesterday’s date as the starting date to get around the timezone diff between me and azure.

#Set the validity date to yesterday to avoid problems with timezones between me and azure
$CertStart = "07/18/2015"
#Add the makecert path to the active path variable
$env:Path = $env:Path + ";C:\Program Files (x86)\Windows Kits\8.1\bin\x64"
makecert.exe -r -pe -n "CN=PowerShellAzureADApp2" -ss My -len 2048 PowerShellAzureADApp2.cer -b $CertStart

 

After that’s done, you should have a new cert in your personal store:
2015-07-19 15_57_13-Start

The next thing we need to do is to define an application in Azure AD. This has been described in so many blog posts already, but here are the basic steps:
1. Go to Azure AD in the “old” Azure Portal, click “Applications” and then “Add” at the bottom of the screen. Choose “An application my organization is developing” (you need to be using the Azure AD directory which is the default for your Azure subscription).
2. Choose Web application (I’ve had some problems when using “Native Application”). The naming should be unique, I’m going with “PowerShellAzureAdApp2”.
2015-07-19 17_21_33-Active Directory - Microsoft Azure

The redirect URI must be unique inside your directory. I’ll use “http://PowerShellAzureADApp2”:
2015-07-19 17_21_55-Active Directory - Microsoft Azure

Note the Client ID of the newly created app, which you can find in the “configure” tab:
2015-07-19 16_10_45-Microsoft Edge

After the app has been created, we need to send the public key of the certificate we created to it. This is done using the Powershell module for Azure AD, which you can install from here: https://msdn.microsoft.com/en-us/library/azure/jj151815.aspx#bkmk_installmodule

After that’s done, upload the certificate:

connect-msolservice
$appid = "53f000c7-6746-4b9c-8841-cc36fd28c3db" # this is the client id you got from the "configure" tab in the portal
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
$cer.Import("D:\trond.hindenes\Documents\PowerShellAzureADApp2.cer")
$binCert = $cer.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)

#The cert has a longer expiration date, but Azure seems to prefer around 3 years
$expdate = (get-date).AddYears(3)
$startdate = $cer.GetEffectiveDateString() | get-date
New-MsolServicePrincipalCredential -AppPrincipalId $appid -Type asymmetric -Value $credValue -StartDate $startdate -EndDate $expdate -Usage verify

Once that’s done you can verify the certificate with this command:

Get-MsolServicePrincipalCredential –ServicePrincipalName $appid -ReturnKeyValues 0

At this point we’ve set up the app and the certificate, but we haven’t given the app access to using the ARM rest apis. This is done using the “Regular” Azure module’s resource manager “mode”:

Add-AzureAccount
Switch-AzureMode -Name AzureResourceManager
New-AzureRoleAssignment –ServicePrincipalName $appid –RoleDefinitionName Owner

At this point, all we have to do is to construct a Powershell script that will load the certificate’s private key, authenticate, and query some arm resources for us (note that you need to have nuget installed. Nuget defaults to downloading packages in your current directory):

#Install the Azure AD dll
nuget install Microsoft.IdentityModel.Clients.ActiveDirectory

Add-Type -Path "D:\trond.hindenes\Desktop\Microsoft.IdentityModel.Clients.ActiveDirectory.2.18.206251556\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
Add-Type -Path "D:\trond.hindenes\Desktop\Microsoft.IdentityModel.Clients.ActiveDirectory.2.18.206251556\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"

$cert = Get-childitem Cert:\CurrentUser\My | where {$_.Subject -eq "CN=PowerShellAzureADApp2"}
#The tenant id (guid from "view endpoints" in the azure ad portal)"
$tenant = "b0f4de9b-5bb1-4f3d-84b4-xxxxx"
#Resource url for the arm api
$resource = "https://management.core.windows.net/"

$appid = "21deb934-f92f-446d-894f-xxxxx" # We already have this
$redirect = new-object System.Uri("http://PowerShellAzureADApp2") #the redirect URI you set for the app when creating it
$AuthContext = new-object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/$tenant") 

$assertioncert = new-object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate($clientID,$cert)
$result = $AuthContext.AcquireToken($resource,$assertioncert) 

$authHeader = @{
'Content-Type'='application\json'
'Authorization'=$result.CreateAuthorizationHeader()
}

#Your azure subscription id
$subscriptionId = "2ee69600-66dc-43b9-8ffe-xxxxx"

$allProviders = (Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/${subscriptionId}/providers?api-version=2014-04-01-preview" -Headers $authHeader -Method Get -Verbose).Value
$allProviders

So, to summarize: With this method we can invoke the Azure resource manager api (or any other MS online api) directly using PKI rather than usernames/passwords.

  • Jeff Lawrence

    This code has some issues – $redirect is defined but never used, also $clientID is used although I think it should be $appId