Deploy to Azure from TFS using an Azure Resource Manager Service Endpoint

by

[TL;DR — You need an Azure Resource Manager Service Endpoint and some values from Azure.  There’s a link to a PowerShell script at the bottom of this post that’ll help.  You’ll probably still need to read this blog post though.  Sorry.  I know.  Reading is hard.]

Deploying from Visual Studio Team Services (VSTS) to Azure is easy.  You pretty much just have to log in and click a few buttons and you’ve got a continuous deployment release pipeline from VSTS to Azure.  If you’re using on-premise TFS, deploying to Azure from a TFS Build or a TFS Release is nowhere near as easy.  VSTS is easy because if you’re using VSTS, it’s already in Azure and it already knows about your Azure accounts and subscriptions.

On-premise TFS doesn’t know *ANYTHING* about your Azure resources unless you tell it.

A TFS-to-Azure Resource Manager Service Endpoint

In order to connect on-premise TFS to Azure, you need to create a Resource Manager Service Endpoint.

Let’s say that you want to deploy to an Azure App Service.  Typically, you’d start your deployment journey by creating a build and a release.  Into that release definition, you’ll add a Deploy Azure App Service task.

A Deploy Azure App Service task in a release definition

If you were doing this in VSTS, you’d simply choose your Azure subscription and app service from those dropdown boxes and you’d be done.  If you’re in TFS, these dropdown boxes are going to be empty.  There’s a link to the right of the Azure subscription box that says Manage.  Click it.

Click Manage on the Azure Subscription box

If you click on the Manage link, it’ll bring you into the list of service endpoints in TFS.  There’s probably nothing there.

The list of Service Endpoints is empty

In the left panel, there’s a button that says New Service Endpoint.  Click New Service Endpoint and choose Azure Resource Manager.

You should now see a dialog for creating an Azure Resource Manager Service Endpoint.  If you’re like me, this dialog looks intimidating and unhelpful.  (You’re right.)  When I first saw this dialog, I had no clue where I’d find the values for “service principal client id”, “service principal key”, and “tenant id”.  What’s super fun about this dialog is that the names of the values here don’t match the names of the values in Windows Azure.  (Neat, huh?)

The Add Azure Resource Manager Service Endpoint dialog in TFS

Getting the Values for the Service Endpoint Dialog

So where do you get the values?  Well, I created a PowerShell script that will create the things you need in Windows Azure and then gather all the values.

Import-Module AzureRM
Import-Module Azure
Import-Module AzureRM.Resources

# change this value to be your azure subscription id
$azureSubscriptionId = "<YOUR-SUBSCRIPTION-ID-GOES-HERE>"

# this is the friendly-ish name and info for your application
# NOTE: this is the name of the Active Directory Application not the AppService
$activeDirectoryApplicationDisplayName = "hello-world-20180109"
$activeDirectoryApplicationHomePage = "http://helloworld.com"
$activeDirectoryApplicationIdentifierUris = "http://helloworld.com/hello-world-20180109"

####
# This function via 
# https://www.sabin.io/blog/adding-an-azure-active-directory-application-and-key-using-powershell/
####
function Create-AesManagedObject($key, $IV) {

$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
 $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
 $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
 $aesManaged.BlockSize = 128
 $aesManaged.KeySize = 256

if ($IV) {
 if ($IV.getType().Name -eq "String") {
 $aesManaged.IV = [System.Convert]::FromBase64String($IV)
 }
 else {
 $aesManaged.IV = $IV
 }
 }

if ($key) {
 if ($key.getType().Name -eq "String") {
 $aesManaged.Key = [System.Convert]::FromBase64String($key)
 }
 else {
 $aesManaged.Key = $key
 }
 }

$aesManaged
}

####
# This function via 
# https://www.sabin.io/blog/adding-an-azure-active-directory-application-and-key-using-powershell/
####
function Create-AesKey() {
 $aesManaged = Create-AesManagedObject 
 $aesManaged.GenerateKey()
 [System.Convert]::ToBase64String($aesManaged.Key)
}

$servicePrincipalKeyValue = Create-AesKey

$keyId = [guid]::NewGuid()

# $psadCredential = New-Object Microsoft.Azure.Commands.Resources.Models.ActiveDirectory.PSADPasswordCredential
$psadCredential = New-Object Microsoft.Azure.Graph.RBAC.Version1_6.ActiveDirectory.PSADPasswordCredential

$startDate = Get-Date
$psadCredential.StartDate = $startDate
$psadCredential.EndDate = $startDate.AddYears(1)
$psadCredential.KeyId = $keyId
$psadCredential.Password = $servicePrincipalKeyValue

$keyValueFilename = "key-value-$keyId.txt"

$servicePrincipalKeyValue | Out-File .\$keyValueFilename

#Login to Azure
Add-AzureRmAccount
 
Write-Output "Calling Get-AzureRmSubscription to get list of all subscriptions for debugging purposes..."
Get-AzureRmSubscription
Write-Output "***"

Write-Output "Getting subscription using Get-AzureRmSubscription..."

$subscription = 
 (Get-AzureRmSubscription -SubscriptionId $azureSubscriptionId)

Write-Output "Requested subscription: $azureSubscriptionId"

$subscriptionId = $subscription.Id
$subscriptionName = $subscription.Name
$tenantId = $subscription.tenantId

Write-Output "Subscription Id: $subscriptionId"
Write-Output "Subscription Name: $subscriptionName"
Write-Output "Tenant Id: $tenantId"

Write-Output "Calling Set-AzureRmContext..."
Set-AzureRmContext -SubscriptionId $subscriptionId -TenantId $tenantId

#create SPN
Write-Output "Calling New-AzureRmADApplication..."
New-AzureRmADApplication -DisplayName $activeDirectoryApplicationDisplayName -HomePage $activeDirectoryApplicationHomePage -IdentifierUris $activeDirectoryApplicationIdentifierUris -PasswordCredentials $psadCredential -OutVariable app

Write-Output "Got application..."
Write-Output $app

$servicePrincipalClientId = $app.ApplicationId

Write-Output "Calling New-AzureRmADServicePrincipal..."
New-AzureRmADServicePrincipal -ApplicationId $app.ApplicationId -OutVariable servicePrincipal

Write-Output "Got service principal..."
Write-Output $servicePrincipal


Write-Output "Pausing for a bit to let New-AzureRmADServicePrincipal catch up before adding role assignment..."
Start-Sleep -s 10

Write-Output "Calling New-AzureRmRoleAssignment..."
New-AzureRmRoleAssignment -RoleDefinitionName Contributor -ServicePrincipalName $app.ApplicationId.Guid -OutVariable roleAssignment

Write-Output "Got role assignment..."
Write-Output $roleAssignment

Write-Output "Reloading what we just created..."

Get-AzureRmADApplication -DisplayNameStartWith $activeDirectoryApplicationDisplayName -OutVariable reloadedApp
Get-AzureRmADServicePrincipal -ServicePrincipalName $reloadedApp.ApplicationId.Guid -OutVariable SPN

Write-Output "Here's the SPN..."
Write-Output $SPN

Write-Output ""
Write-Output "************************"
Write-Output ""
Write-Output "Here's all the info you need."
Write-Output ""
Write-Output "Subscription Id: $subscriptionId"
Write-Output "Subscription Name: $subscriptionName"
Write-Output "Service Principal Client Id: $servicePrincipalClientId"
Write-Output "Service Principal Key: $servicePrincipalKeyValue"
Write-Output "Tenant Id: $tenantId"
Write-Output ""
Write-Output "Key value is also written to $keyValueFilename"

Before you run the powershell script, you’ll need to get the Subscription ID for your Azure Subscription and plug it into the script where it says “<YOUR-SUBSCRIPTION-ID-GOES-HERE>.  You can find your Subscription ID by going to http://portal.azure.com and going to Subscriptions.

List of Azure Subscriptions and Subscription IDs

When you run the powershell script, it’ll prompt you to log in to your subscription.  NOTE: if you get an error that says something like “Powershell script Import-Module : The specified module ‘AzureRM’ was not loaded because no valid module file was found in any module directory” then you need to install the Azure PowerShell modules by running “install-module azurerm -allowclobber”.

When the PowerShell script finishes running, you’ll see a bunch of values at the bottom of the script with a message that says “Here’s all the info you need.”

The results of the PowerShell script

Populate the Add Azure Resource Manager Service Endpoint Dialog

Plug the values from the PowerShell script output into the Add Azure Resource Manager Service Endpoint dialog and click Verify connection.  The connection should say verified.

Plug the values from the PowerShell script into the Azure Resource Manager Service Endpoint dialog

When you go back to the editor for your Release, the Azure subscription and App service name dialogs should now have values.

Subscription info is populated nowFrom here, you can pretty much just continue on and deploy to that Azure Subscription from TFS as you see fit.

What did this actually do?

If you’re curious what this script actually did, you can check it out in the Azure Portal (http://portal.azure.com).  The script does two big things: 1) creates an application registration in your Azure Active Directory, and 2) grants that Contributor permissions for your Azure App Service.

To view the changes in Azure Active Directory (AAD), go to the portal, and go to Azure Active Directory.  In AAD, click into the “App registrations” section to view the application registration.

To view the changes for your Azure App Service, go to the portal, and go to App Services.  Click into your App Service and then navigate to “Access control (IAM)”.  You should see an app listed under Contributor.

Summary

If you want to deploy to Azure from an on-premise TFS, you’ll need to create an Azure Resource Manager Service Endpoint connection.  Creating all the stuff you need in Azure and gathering the right IDs, can be a bit of a pain so I’ve created a PowerShell script to streamline the process.

Here’s a link to that script.

Huge thanks to Simon Sabin for this blog post that helped me to get rolling with some of the more difficult bits of PowerShell + Azure.

I hope this helps.

-Ben

 

— TFS DevOps got you down?  Trying to connect TFS and Azure so you can do continuous delivery and automated releases?  Are you dreaming of agility and Scrum awesomeness?  Well, we can help.  Drop us a line at info@benday.com


One Response to "Deploy to Azure from TFS using an Azure Resource Manager Service Endpoint"
  1. Hi Ben,

    Nice article, but is there way to get these details through Azure/VSTS API’s instead of power-shell. Because for executing a power-shell sometimes makes things difficult. If he values can be fetched from API, we can provide user-friendly UI and ask users to enter the subscription-Id and they will get the values populated.

    Regards,
    Raj

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.