Working with Azure Functions and VSTS - retrieving secrets

This post is an extension to the post written by Marek Grabarz here. If you haven't got a chance to read, I strongly recommend you to do so - it presents a bit more general approach to automate Azure Functions and can act as baseline when it comes to build your custom solution.

The problem

You have an ARM template and full CI/CD pipeline prepared. All works smoothly and with easy. You're just about to grab a beer and celebrate success when suddenly you realizes, that you haven't put functions' keys to the output. After searching multiple pages you finally finds Marek's post, which explains in detail what is needed to obtain a secret from a function. 

Unfortunately using Azure Active Directory Authentication Library (aka ADAL) with VSTS results with the following error:

/
2017-04-24T08:16:40.9453294Z GAC    Version        Location                                                                                         
2017-04-24T08:16:40.9463285Z ---    -------        --------                                                                                         
2017-04-24T08:16:40.9523277Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:16:40.9683275Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:16:42.5695169Z ##[error]Exception calling "AcquireToken" with "4" argument(s): "user_interaction_required: One of two conditions was encountered: 1. The PromptBehavior.Never flag was passed, but the constraint could not be honored, because user interaction was required. 2. An error occurred during a silent web authentication that prevented the http authentication flow from completing in a short enough time frame"
2017-04-24T08:16:42.6375138Z ##[section]Finishing: Azure PowerShell script: FilePath

All right - maybe using PrompBehaviour.Auto is going to help:

/
2017-04-24T08:42:56.7459955Z GAC    Version        Location                                                                                         
2017-04-24T08:42:56.7459955Z ---    -------        --------                                                                                         
2017-04-24T08:42:56.7519937Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:42:56.7679946Z False  v4.0.30319     C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ResourceManager\AzureResourceManager\Az...
2017-04-24T08:42:57.9119830Z ##[error]Exception calling "AcquireToken" with "4" argument(s): "Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application."
2017-04-24T08:42:58.0019817Z ##[section]Finishing: Azure PowerShell script: FilePath

Apparently the way how VSTS authenticates itself is different than doing it locally(when you check logs, you'll see, that it doesn't call Login-AzureRMAccount - instead Add-AzureRMAccount -ServicePrincipal, what could be the reason, why ADAL is problematic in this particular scenario). We have to find another way to get the token for authentication. 

The solution

It seems, that the best way to obtain a token is to call a REST API under https://login.windows.net/{tenantId}/oauth2/token. To do so you need a couple of things:

  • client_id for the service principal VSTS uses
  • client_secret(a key which is connected to the service principal)

The best way to find them is to do following:

1. Go to the Services panel

2. You should see the endpoint defined for VSTS. From here you can click on Manage Service Principal

You'll be forwarded to the old portal. Now go to the Configure tab - from here you can copy client_id needed for the API. You can also find the Keys section which is the last thing we need here - just add another key and copy its value(remember that once you leave this page, you won't be able to retrieve it). Once we're armed with additional data, we can use it to get our token:

/
$tokenEndpoint = "https://login.windows.net/{tenantId}/oauth2/token"
$body = @{
        'resource'= "https://management.core.windows.net/"
        'client_id' = "client_id"
        'grant_type' = 'client_credentials'
        'client_secret' = "client_secret"
}

$params = @{
    ContentType = 'application/x-www-form-urlencoded'
    Headers = @{'accept'='application/json'}
    Body = $body
    Method = 'Post'
    URI = $TokenEndpoint
}

$token = Invoke-RestMethod @params
$token | select access_token, @{L='Expires';E={[timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($_.expires_on))}} | fl *

This token can be further used to get a function key like this(assuming you have a master key):

/
$hostKeyRequest = Invoke-RestMethod -Method GET -Uri "https://$functionAppName.azurewebsites.net/admin/HOST/KEYS?CODE=$masterKey" -Headers @{ Authorization = $token }

Summary

Integrating multiple resources in Azure and VSTS can be a little tricky sometimes, but as you can see it still doesn't require much work to get it working. With a simple Powershell script and one call to the API you can authenticate requests from your VSTS instance and make it work with most components available in Azure.