ACKNOWLEDGEMENT: This has been originally posted on Mexia blog
I was asked by a previous client whether there would be a way to access to individual function keys, host keys and master key of an Azure Functions app instance without visiting Azure Portal. Because they wanted to use Azure Functions for their CI/CD pipeline with Deployment Gates, they would need direct access to Azure Functions HTTP triggers within their build/release pipeline. Of course, they can store those function keys as environment variables, but it will be more handy, if they can retrieve those keys programmatically. It's an interesting question to me because I know how to do it but I haven't really done this before. I did quick research on it and found a few blog posts and StackOverflow question and answer. You might have been aware that all these approaches use KUDU API at the first place. This is OK as long as it works. However, Microsoft Azure App Service Team tried to keep away from using KUDU API for it and managed to implement another approach using a JWT bearer token. In this post, I'm going to show how to use the JWT bearer token to retrieve all function master key, host keys and individual function keys through Azure PowerShell, without having dependency on KUDU APIs.
Prerequisites
In order to use this approach, we need an Azure Function app instance up and running. This can be easily done through Azure Portal or ARM template. Then, deploy a few functions.
We can add more function keys on each function, if we like.
In addition to this, we need the following information:
- Tenant Id and Subscription Id,
- Resource Group Name, and
- Function App Name.
And finally, we might have to need a service principal that is:
- Registered in our Azure Active Directory, and
- Application Id (client Id) and key (client secret).
We're not going to dive further to register the service principal to Azure Active Directory here. Instead, refer to this official guide.
Claiming JWT Token for Azure Resource Manager API
OK, first thing goes first. All Azure resources provide their REST API endpoints. In order to call those APIs, we need to get an access token beforehand. As acquiring the access token is well documented here, we just use this.
# NOTE: This is NOT a real tenant Id, subscription Id, client Id nor client secret. Use yours. | |
$tenantId = "32a489b1-612c-4d44-b4dd-714376ac7479" | |
$subscriptionId = "4ba9940b-0057-4184-9965-eb686c3027bc" | |
$clientId = "310c7137-af66-4bb1-ac76-c4cb4fcbe4e1" | |
$clientSecret = "QMvihWUWlRuPs3nNjxxzR/rULyKOAibRBkWDsbokzqw=" | |
$authUri = "https://login.microsoftonline.com/$tenantId/oauth2/token?api-version=1.0" | |
$resourceUri = "https://management.core.windows.net/" | |
$authRequestBody = @{} | |
$authRequestBody.grant_type = "client_credentials" | |
$authRequestBody.resource = $resourceUri | |
$authRequestBody.client_id = $clientId | |
$authRequestBody.client_secret = $clientSecret | |
$auth = Invoke-RestMethod -Uri $authUri -Method Post -Body $authRequestBody |
When we run this, we'll have the access token for Azure Resource Management REST API.
Claiming JWT Token for Azure Functions
Now, Azure Resource Manager REST API offers an endpoint to get functions admin token. This is the very KUDU API replacement.
# NOTE: This is NOT a real resource group name nor function app name. Use yours. | |
$resourceGroupName = "my-resource-group" | |
$functionAppName = "my-azure-function" | |
$accessTokenHeader = @{ "Authorization" = "Bearer " + $auth.access_token } | |
$azureRmBaseUri = "https://management.azure.com" | |
$azureRmApiVersion = "2016-08-01" | |
$azureRmResourceType = "Microsoft.Web/sites" | |
$azureRmResourceId = "/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/$azureRmResourceType/$functionAppName" | |
$azureRmAdminBearerTokenEndpoint = "/functions/admin/token" | |
$adminBearerTokenUri = $azureRmBaseUri + $azureRmResourceId + $azureRmAdminBearerTokenEndpoint + "?api-version=" + $azureRmApiVersion | |
$adminBearerToken = Invoke-RestMethod -Method Get -Uri $adminBearerTokenUri -Headers $accessTokenHeader |
When we run this, we'll have the admin bearer token to access to Azure Function app's admin APIs.
NOTE: This token has a very short life-time span. Therefore, it's always a good idea to get this admin token every time we access to the Azure Functions admin API endpoints.
Retrieving List of Functions
In an Azure Functions app, if more than one function exist, we can list up all functions.
$azureRmListFunctionsEndpoint = "/functions" | |
$listFunctionsUri = $azureRmBaseUri + $azureRmResourceId + $azureRmListFunctionsEndpoint + "?api-version=" + $azureRmApiVersion | |
$listFunctions = Invoke-RestMethod -Method Get -Uri $listFunctionsUri -Headers $accessTokenHeader |
When we run this, we'll see the list of functions.
NOTE: The list of functions doesn't only include HTTP trigger functions, but also include other types of functions. In this example,
test3
is a disabled Timer trigger function andtest4
is Queue trigger function.
Accessing to Individual Function Keys
According to the wiki document, we can access to the individual function keys by sending an API request to Azure Functions' admin API. We now have the bearer token to access to the admin APIs, and list of functions. Therefore, simply run the following PowerShell script to get those function keys.
$functionAppBaseUri = "https://$functionAppName.azurewebsites.net/admin" | |
$functionName = "test1" | |
$functionKeysEndpoint = "/functions/$functionName/keys" | |
$functionKeysUri = $functionAppBaseUri + $functionKeysEndpoint | |
$adminTokenHeader = @{ "Authorization" = "Bearer " + $adminBearerToken } | |
$functionKeys = Invoke-RestMethod -Method Get -Uri $functionKeysUri -Headers $adminTokenHeader |
NOTE: You can iterate
$functionName
using eitherForEach-Object
orforeach
loop to get the keys of all functions.
When we run this, we'll see the list of keys belong to the given function.
Accessing to Host Keys
Let's think about there are multiple functions in one Azure Functions instance. Iterating each function to get individual function key might not be ideal. Instead, we can use a host key to access to all functions. Let's get into it.
$hostKeysEndpoint = "/host/keys" | |
$hostKeysUri = $functionAppBaseUri + $hostKeysEndpoint | |
$hostKeys = Invoke-RestMethod -Method Get -Uri $hostKeysUri -Headers $adminTokenHeader |
When we run this, we'll see the list of host keys belong to the function app instance.
Accessing to Master Key
There's one more. We have not been able to get the master function key, _master
. In order to get this, we need to try a different endpoint, which has not been documented. Fortunately, the fact that Azure Functions is an open source project allows us to see its source code. When we have a look at the code, it has a different endpoint to get the _master
key, which is /admin/host/systemkeys/_master
.
$masterKeyEndpoint = "/host/systemkeys/_master" | |
$masterKeyUri = $functionAppBaseUri + $masterKeyEndpoint | |
$masterKey = Invoke-RestMethod -Method Get -Uri $masterKeyUri -Headers $adminTokenHeader |
When we run this, we'll see the master key belongs to the function app instance.
NOTE: Even though we can retrieve the master key, I wouldn't recommend doing it as we should keep this key as secure as possible.
So far, we have retrieved individual function keys, host keys and master key that belong to each function and function app itself. As discussed at the beginning of this post, there must be a requirement that we need to get those keys programatically. Although we just use PowerShell scripts here, we can actually write this entirely in C# or JavaScript, if necessary, as it's purely REST API-based. I hope this will give your app development with more flexibilities.