A step by step guide to setting up your Azure environment to support Automation for Resource Manager Powershell scripts, using Service Principal authentication.
Getting set up so that you can run Azure Resource Manager Powershell scripts in your Automation account isn’t quite as easy as perhaps it should be. I thought it was worthwhile documenting the steps you need to go through to get setup, and explaining what those steps do. If you want to just get setup without understanding it, download the script code and go for it. I’ve based this post around the script here https://github.com/Azure/azure-powershell/files/104033/01CreateCertAndServicePrincipal.ps1.txt, although I’ve modified it very slightly to introduce a delay for the AD application creation to finalise, and to ensure we create within the context of the subscription you want to control.
Firstly, I should explain there are other ways to do this. You can get logged in to run your powershell using a username and password – as long as the user you use has the right authority within the AD tenant and subscription. Here’s how:
param( $inputuser = 'john.doe@mydomain.co.uk', $inputpass = "mypassw0rd" ) $Password = ConvertTo-SecureString -AsPlainText $inputpass -Force $Credential = New-Object System.Management.Automation.PSCredential $inputuser, $Password Login-AzureRmAccount -Credential $Credential
However, I’d recommend you don’t use this method (and particularly don’t store your passwords in the ps1 file – you
could use Azure Vault for this instead or create the credential as an Asset in the Automation account, and load it from there instead). If you’re going to use this method, make sure the user account you’re using is actually in the AD domain, and not a guest account (like a Microsoft account), and that multi factor authentication (MFA) is disabled for the account. Also, remember to update the password in the Vault or Credentials asset whenever the password is changed. All good reasons to use a security principal instead…
A security principal is like a service account – it’s one that’s setup for use by an application or service, and not one intended for user by an interactive user account. There is a way to create a service principal with a password or secret to login, but that method’s not currently supported by the Azure automation account. You can get a script to work locally in your ISE creating the credential in this way and running against your subscription quite happily, but when you deploy the script to the automation account, it will not work, and the error messages you get will not be obvious – messages referring to not being able to find tenants, and some commands working once then not working the second time you run them.
The mechanism that IS supported by Azure Automation however, is to create a Service Principal with a Certificate as the credential, and this is what the guide below will walk you through. A Service Principal is based around an Azure AD Application that you create, and when logging in, it’s this AD Application which allows the Azure Resource Manager to authenticate your credentials. Here, we’re going to create an AD Application via Powershell and pass in the certificate key value as the credential. Just as a side note – if you were not using certificates (and wanted to create an SP but didn’t need it to run under an Automation account), you can create the Application in the Portal UI and have it generate you a key (secret) instead – you would then use the Application Id as the username and the secret as the password to login.
Step 1 – Login to Azure Resource Manager
Using an account that has access to the directory of the tenant and subscription you want to work with.
$subscriptionId = '<YOUR_SUBSCRIPTION_ID>' $account = Login-AzureRmAccount -SubscriptionId $subscriptionId
Step 2 – Create the certificate
Since this getting started guide is all based around a Powershell script which does all this for you, we’re going to create the certificate from Powershell commands.
$name = '<YOUR AD APP NAME>' $thumbprint = (New-SelfSignedCertificate -DnsName "$name" -CertStoreLocation Cert:\CurrentUser\My -KeySpec KeyExchange).Thumbprint $cert = (Get-ChildItem -Path cert:\CurrentUser\My\$thumbprint) mkdir "C:\${name}" Export-Certificate -Cert $cert -FilePath "C:\${name}\${name}.cer" -Type CERT $password = Read-Host -Prompt "Enter a password for the new .pfx certificate:" -AsSecureString if ($password -eq $null) { throw "You must enter a password so the .pfx can be created" } Export-PfxCertificate -Cert $cert -FilePath "C:\${name}\${name}.pfx" -Password $password
The parameter can be anything you like – its used here for to give the certificate a readable name and a filename when exporting it to disk. In the finished script I’ve also appended ‘-app’ to the name you specify and use that as the Azure AD Application name. The script will prompt you for a password, which it will use to secure the certificate. At this point, we now have the certificate saved to your C:\ drive in a folder named , as a .cer and a .pfx. Keep these somewhere safe, you will need to upload the .pfx to the Azure automation account later.
Step 3 – Create the Azure AD Application
Now we’re going to use the key from the certificate as the credential when creating the application instead of a password.
$applicationName = $name+'-app''<NAME FOR YOUR NEW SERVICE PRINCIPAL AND APPLICATION>' $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("C:\${name}\${name}.pfx", $password) $keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData()) $azureAdApplication = New-AzureRmADApplication -DisplayName "${applicationName}" -HomePage "https://${applicationName}" -IdentifierUris "https://${applicationName}" -KeyValue $keyValue -KeyType AsymmetricX509Cert
At this stage, you can verify that the application has been created successfully in your AD tenant. This is in the “classic” portal, under “Active Directory” -> Choose your tenant, then click the “Applications” tab. You may need to search by “Applications my company owns” instead of “Applications my company uses” to find it.
Notice there are no keys shown in the application in the portal.
Step 4 – Create the Service Principal
Now we need to create the SP account itself against the new application. We can then assign a role (and optionally scope) to the account to control the access rights of the account. Without specifying a scope, the account will be able to see all resource groups within the subscription by default.
$servicePrincipal= New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId New-AzureRmRoleAssignment -RoleDefinitionName Owner -ServicePrincipalName $azureAdApplication.ApplicationId
That completes the creation side of things, we now need to setup the Automation account within Azure.
Step 5 – Configure the Automation Account
Go to the production Portal, and if you haven’t already created an Automation Account, do so now.
Now click on “assets”, then “certificates”, and click on the “Add a certificate” toolbar button.
Name the certificate – this name will be used in your Powershell scripts to load it from this store, so give it a name that identifies the purpose and perhaps indicate it’s a service principal account – eg. “eu-dev-automation-sp” in my example, indicating the environment, subscription, purpose and type. Use the file browser to find the .pfx file in your C:\ drive, choose “Yes” to mark the certificate as exportable, and save. To make life a little easier, we’re also going to add a couple of Variable assets in the automation account, to store the Service Principal application Id, and the tenant Id.
Back in the Assets section of the automation account portal page, click on Variables. Use the Add a variable toolbar button to create two new string variables. One called SPAppID and the other called SPTenant – paste in the values output from the script. If you’re ever stuck for these – the Application ID can be found in the classic portal, by going to the Active Directory tab and selecting your tenant. The tenant ID will be in the Url after /Directory; Find your application (as described above), and the application Id appears in the configure page as an entry called CLIENT ID.
Now we’re good to start consuming the certificate in our automation scripts. The snippet below will have you login using the SP account, and all ready to start calling AzureRM cmdlets.
$SPAppID = Get-AutomationVariable -Name 'SPAppID' $SPTenant = Get-AutomationVariable -Name 'SPTenant' $Certificate = Get-AutomationCertificate -Name "eu-dev-automation-sp" $CertThumbprint = ($Certificate.Thumbprint).ToString() <# # NOTE: this if statement will ALWAYS return true, given our current sandbox implementation: # Certificate Assets uploaded by the user are placed in Cert:\LocalMachine\Root when the sandbox is created. # However, for the Login-AzureRmAccount cmdlet to work, it must be given a certificate that is stored in # Cert:\CurrentUser\My or Cert:\LocalMachine\My. So, the following code puts the cert there. # This is very ugly right now, but will be fixed in an upcoming Automation release. #> if ((Test-Path Cert:\CurrentUser\My\$CertThumbprint) -eq $false) { "Installing the Service Principal's certificate..." $store = new-object System.Security.Cryptography.X509Certificates.X509Store("My", "CurrentUser") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.Add($Certificate) $store.Close() } <# # # Now the actual work begins # #> Login-AzureRmAccount -ServicePrincipal -TenantId $SPTenant -CertificateThumbprint $CertThumbprint -ApplicationId $SPAppID <# now we can run cmdlets like Get-AzureRMResource #>
And here is the full setup script for creating the AD Application and Service Principal:
PARAM ( [Parameter(Mandatory=$true, HelpMessage="Enter the name of the AD application to be created, eg. AutomationSP")] [string] $name = 'eu-dev-automation-sp', [Parameter(Mandatory=$true, HelpMessage="Enter the subscription ID")] [string] $subscriptionId ) $applicationName = $name+"-app" # 1.Sign in to your account. Write-Output "BEGIN STEP 1" $account = Login-AzureRmAccount -SubscriptionId $subscriptionId if ($account -eq $null) { throw "You must sign in to continue running this script." } # 2.Create the Certificate Write-Output "BEGIN STEP 2" <# NOTE: for the certificate to be found by the Login-AzureRmAccount cmdlet, it must be in CurrentUser\My #> $thumbprint = (New-SelfSignedCertificate -DnsName "$name" -CertStoreLocation Cert:\CurrentUser\My -KeySpec KeyExchange).Thumbprint $cert = (Get-ChildItem -Path cert:\CurrentUser\My\$thumbprint) mkdir "C:\${name}" Export-Certificate -Cert $cert -FilePath "C:\${name}\${name}.cer" -Type CERT [System.Security.SecureString]$password = Read-Host -Prompt "Enter a password for the new .pfx certificate:" -AsSecureString if ($password -eq $null) { throw "You must enter a password so the .pfx can be created" } Export-PfxCertificate -Cert $cert -FilePath "C:\${name}\${name}.pfx" -Password $password # 3. create an X509Certificate object from your certificate and retrieve the key value. Write-Output "BEGIN STEP 3" $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("C:\${name}\${name}.pfx", $password) $keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData()) # 4. Create an application in the directory with key values. Write-Output "BEGIN STEP 4" $azureAdApplication = New-AzureRmADApplication -DisplayName "${applicationName}" -HomePage "https://${applicationName}" -IdentifierUris "https://${applicationName}" -KeyValue $keyValue -KeyType AsymmetricX509Cert # 5.Create a service principal Write-Output "BEGIN STEP 5" $servicePrincipal= New-AzureRmADServicePrincipal -ApplicationId $azureAdApplication.ApplicationId sleep -Seconds 20 # 6. Grant the Service Principal role Write-Output "BEGIN STEP 6" New-AzureRmRoleAssignment -RoleDefinitionName Owner -ServicePrincipalName $azureAdApplication.ApplicationId <# # OPTIONAL # 7.Log in to Azure using ServicePrincipal Write-Output "BEGIN STEP 7" $tenantId = $account.Context.Subscription.TenantId Login-AzureRmAccount -ServicePrincipal -TenantId $tenantId -CertificateThumbprint $thumbprint -ApplicationId $azureAdApplication.ApplicationId #> Write-Output "Done."
Pingback: Continuous Deployment of Azure ARM Based Environments using VSTS | russellyoung.net()