In my customer engagements, I usually push early for deployment automation of some sort. My preferred way to deploy to Azure is using Azure Resource Manager JSON Templates, alongside with developer-side automated scripts. Personally I also appreciate the notion of Service Principals, i.e. using “strong” credentials such as an X.509 Certificate to authenticate to Azure Resource Manager (ARM) API.

In order to make it a bit more interesting, this article uses the “Microsoft Azure Germany” environment, instead of the ‘regular’ Azure.

Registering Azure Germany under the hood

When you install the latest Powershell for Azure (v1.5.0 at time of this writing), the command Get-AzureEnvironment | select Name should look like this:

PS C:\> Get-AzureEnvironment | select Name

Name
----
AzureCloud
AzureChinaCloud
AzureUSGovernment
AzureGermanCloud

The last line AzureGermanCloud indicates that Powershell already knows the specific management endpoints for Germany.

If you do not have that, you might consider re-installing the Powershell module

# Install the Azure Resource Manager modules from the PowerShell Gallery
Install-Module AzureRM
Install-AzureRM
Install-Module Azure

Import-AzureRM
Import-Module Azure

For the azure-cli side of things, the output of azure account env list should look like this:

PS C:\> azure account env list

info:    Executing command account env list
data:    Name
data:    -----------------
data:    AzureCloud
data:    AzureChinaCloud
data:    AzureUSGovernment
data:    AzureGermanCloud
info:    account env list command OK

If you miss that last line, you can add the environment yourself:

azure account env add ^
  --environment                               AzureGermanCloud ^
  --portal-url                                http://portal.microsoftazure.de/ ^
  --publishing-profile-url                    https://manage.microsoftazure.de/publishsettings/index ^
  --management-endpoint-url                   https://management.core.cloudapi.de/ ^
  --resource-manager-endpoint-url             https://management.microsoftazure.de/ ^
  --gallery-endpoint-url                      https://gallery.cloudapi.de/ ^
  --active-directory-endpoint-url             https://login.microsoftonline.de ^
  --active-directory-resource-id              https://management.core.cloudapi.de/ ^
  --active-directory-graph-resource-id        https://graph.cloudapi.de/ ^
  --storage-endpoint-suffix                   .core.cloudapi.de ^
  --key-vault-dns-suffix                      .vault.microsoftazure.de ^
  --sql-server-hostname-suffix                .database.cloudapi.de 

Setup of a Service Principal in Azure Active Directory (AAD)

The following Powershell script can be used to

  1. Login interactively to Azure
  2. Create a new application in Azure Active Directory. An application is a process which is cryptographically known to Azure AD (AAD).
  3. Promote that application to become a service principal, i.e. giving it the right to request authN tokens from AAD.
  4. Registering that new service principal as a Contributor to my Azure Subscription.

A few variables to start with

The initial log-in to Azure Germany happens with a regular Azure AD user, in my case that’s chgeuer@msftger.onmicrosoft.de.

$subscriptionId = "deadbeef-fb63-43e6-afa2-d832f709f700"
$tenantId = "deadbeef-e2bf-48c0-b025-23e47c410293"
$userName = "chgeuer@msftger.onmicrosoft.de"
$environmentName = "AzureGermanCloud"

Get the user’s interactive password into the Powershell environment

$cred = Get-Credential `
    -UserName $userName `
    -Message "Login $userName to $environmentName"

Login to Azure with the interactive credential

Add-AzureRmAccount `
    -EnvironmentName $environmentName `
    -Tenant $tenantId `
    -Credential $cred

Login-AzureRmAccount `
    -EnvironmentName $environmentName `
    -TenantId $tenantId `
    -SubscriptionId $subscriptionId `
    -Credential $cred

Register the application

In order to authenticate to Azure later, I want my service principal to use an X.509 Certificate. You can just bake yourself an own one using makecert.exe if you like. In my case, I saved a copy of the actual certificate on my local harddisk, which I then read into Powershell:

$certificateFile = "D:\credentials\azure-work\CN_Lenovo W530 Cert Christian.cer"

$certOctets = Get-Content -Path $certificateFile -Encoding Byte
$credValue = [System.Convert]::ToBase64String($certOctets)

$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 `
    -ArgumentList @(,[System.Byte[]]$certOctets)

Create the Azure AD application

Each application must have a name and a URL. In case your application is an actual web application, that URL would correspond to the real web site address. In my case, that’s just some non-existent dummy URL:

$appName = "Service Principal Lenovo my Laptop $($userName)"
$dummyUrl = "http://some-domain.com/whatever"

$application = New-AzureRmADApplication `
    -DisplayName $appName `
    -HomePage $dummyUrl `
    -IdentifierUris $dummyUrl `
    -KeyType AsymmetricX509Cert `
    -KeyValue $credValue

Promote the app to become a service principal

As part of a larger script, you should pause execution for a few seconds, as it might take 1-2 seconds for that service principal information to propagate through AAD.

New-AzureRmADServicePrincipal `
    -ApplicationId $application.ApplicationId

Start-Sleep -Seconds 2

Tell Azure that the service principal can manage my subscription

New-AzureRmRoleAssignment ` 
    -ServicePrincipalName $application.ApplicationId `
    -RoleDefinitionName Contributor


Write-Host "Login like this: "
Write-Host ""
Write-Host "Login-AzureRmAccount \`"
Write-Host "     -ServicePrincipal \`"
Write-Host "     -TenantId '$($tenantId)' \`"
Write-Host "     -ApplicationId '$($application.ApplicationId)' \`"
Write-Host "     -CertificateThumbprint '$($cer.Thumbprint)' \`"
Write-Host "     -EnvironmentName 'AzureGermanCloud'"

Use that service principal to log-in to Azure

Use that service principal to log-in to Azure using Powershell

The following code assumes that you imported the certificate into your Windows Certificate store. As you can see, the CurrentUser\My certificate store contains the X509 cert, and I also own the private key:

Get-ChildItem Cert:\CurrentUser\My | `
    where { $_.Thumbprint -eq "B8789A48A020FB1F5589C9ACAF63A4EBFFF5FA1C" } | `
    select ThumbPrint,Subject,HasPrivateKey

Output is

Thumbprint                               Subject                       HasPrivateKey
----------                               -------                       -------------
B8789A48A020FB1F5589C9ACAF63A4EBFFF5FA1C CN=Lenovo W530 Cert Christian          True

With this information I can now login with the service principal’s identity:

Login-AzureRmAccount `
	-ServicePrincipal `
	-TenantId 'deadbeef-e2bf-48c0-b025-23e47c410293' `
	-ApplicationId 'deadbeef-0980-46a6-a7fa-7ca8845aaca1' `
	-CertificateThumbprint 'B8789A48A020FB1F5589C9ACAF63A4EBFFF5FA1C' `
	-EnvironmentName 'AzureGermanCloud'

Output is

Environment           : AzureGermanCloud
Account               : deadbeef-0980-46a6-a7fa-7ca8845aaca1
TenantId              : deadbeef-e2bf-48c0-b025-23e47c410293
SubscriptionId        : deadbeef-fb63-43e6-afa2-d832f709f700
SubscriptionName      : MSFTGER Test Subscription
CurrentStorageAccount :

Use that service principal to log-in to Azure using node.js / azure-cli

The same thing can be done using the azure-cli. The main difference is that the azure-cli isn’t aware of Windows certificate stores, but still requires access to the certificate’s private key. In this case, the private key is in a PEM-file on my laptop’s harddisk:

azure config mode arm

azure login ^
  --environment AzureGermanCloud ^
  --service-principal ^
  --tenant "deadbeef-e2bf-48c0-b025-23e47c410293" ^
  --username "deadbeef-0980-46a6-a7fa-7ca8845aaca1" ^
  --thumbprint "B8789A48A020FB1F5589C9ACAF63A4EBFFF5FA1C" ^
  --certificate-file "D:\credentials\azure-work\CN_Lenovo W530 Cert Christian.pem" ^
  --json ^
  --verbose

Output is

info:    Executing command login
verbose: Authenticating...
info:    Added subscription MSFTGER Test Subscription
info:    login command OK
comments by Disqus