11 min read

List of Access Keys from Output Values after ARM Template Deployment

Justin Yoo

There are many cases that we need to retrieve access keys and/or endpoints of Azure resources, as soon as they are deployed through ARM templates. Typical uses cases are:

  1. To display those values in the outputs section of ARM templates,
  2. To get a reference to the outputs section of nested ARM templates from their parent template,
  3. To store those values to Azure Key Vault, and
  4. To store those values to environment variables of Visual Studio Team Service.

Due to the nature of individual Azure resources, populating those keys through ARM template outputs section is not that easy. Rather it's a bit tricky and not well documented. In this post, I'm going to list up how to get those keys and endpoints using ARM template functions.

List of Azure Resources

NOTE: This is not a complete list, but the list contains ones quite frequently used. Therefore, if any of you know something not on the list, please let us know.

Application Insights

Azure Application Insights has an instrumentation key for other Azure resources to use. After it is deployed, the instrumentation key is found under its properties. Therefore, we need to use the reference() function.

Implementation

Here's a sample ARM template to see the outputs section:

{
"variables": {
"applicationInsights": {
"name": "my-application-insights"
},
"resourceId": "[resourceId('Microsoft.Insights/components', variables('applicationInsights').name)]",
"apiVersion": "[providers('Microsoft.Insights', 'components').apiVersions[0]]"
},
"resources": [],
"outputs": {
"instrumentationKey": {
"type": "string",
"value": "[reference(variables('resourceId'), variables('apiVersion')).instrumentationKey]"
}
}
}

Cosmos DB

Once Azure Cosmos DB instance is deployed, we might need to get at least three values – endpoint, access key and connection string. In order to get the endpoint details, the reference() function provides it, which is not that hard. But fetching the access keys are not that easy.

Available Functions

In order to identify available resource functions, simply run the following Azure PowerShell cmdlet. It will show several operations that we can utilise in the outputs section of ARM templates.

Get-AzureRmProviderOperation -OperationSearchString "Microsoft.DocumentDB/*" `
| Where-Object { $_.Operation -like "*list*" } `
| Format-Table Operation

It returns two operations – listKeys and listConnectionStrings, which are corresponding to the resource functions of listKeys() and listConnectionStrings(). The listKeys() function returns the access key. However, the other function, listConnectionStrings() returns nothing. Apparently it has not been implemented yet. Therefore, in order to get the connection string, we should use the concat() function to make-up the connection string. If we want to know the object structure that listKeys() function returns, simply run the following Azure PowerShell cmdlet:

Invoke-AzureRmResourceAction `
-ResourceGroupName "RESOURCE_GROUP_NAME" `
-ResourceType "Microsoft.DocumentDB/databaseAccounts" `
-ResourceName "COSMOS_DB_ACCOUNT_NAME" `
-Action listKeys `
-Force

The listKeys() function returns primaryMasterKey and secondaryMasterKey.

Implementation

With this information, we can implement the outputs section like this:

{
"variables": {
"cosmosDbAccount": {
"name": "my-cosmos-db"
},
"resourceId": "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosDbAccount').name)]",
"apiVersion": "[providers('Microsoft.DocumentDB', 'databaseAccounts').apiVersions[0]]"
},
"resources": [],
"outputs": {
"documentEndpoint": {
"type": "string",
"value": "[reference(variables('resourceId'), variables('apiVersion')).documentEndpoint]"
},
"accountKey": {
"type": "string",
"value": "[listKeys(variables('resourceId'), variables('apiVersion')).primaryMasterKey]"
},
"connectionString": {
"type": "string",
"value": "[concat('AccountEndpoint=https://', variables('cosmosDbAccount').name, '.documents.azure.com:443/;AccountKey=', listKeys(variables('resourceId'), variables('apiVersion')).primaryMasterKey, ';')]"
}
}
}
view raw CosmosDb.json hosted with ❤ by GitHub

Service Bus

For Azure Service Bus, we can apply a similar approach to Azure Cosmos DB. In order to pull the endpoint, we can simply use the reference() function, which is the same as Azure Cosmos DB. However, getting the access keys and connection strings is different. Let's have a look.

Available Functions

In order to identify available resource functions, simply run the following Azure PowerShell cmdlet. It will show several operations that we can utilise in the outputs section of ARM templates.

Get-AzureRmProviderOperation -OperationSearchString "Microsoft.ServiceBus/*" `
| Where-Object { $_.Operation -like "*list*" } `
| Format-Table Operation

It returns one operation, listKeys, in three different providers – Service Bus itself, Service Bus Queue and Service Bus Topic. We're interested in the root one for now. If we want to know the object structure that listKeys() function returns, simply run the following Azure PowerShell cmdlet:

Invoke-AzureRmResourceAction `
-ResourceGroupName "RESOURCE_GROUP_NAME" `
-ResourceType "Microsoft.ServiceBus/namespaces/authorizationRules" `
-ResourceName "SERVICE_BUS_NAMESPACE_NAME/RootManageSharedAccessKey" `
-Action listKeys `
-Force

Unlike Azure Cosmos DB, the listKeys() function for Azure Service Bus doesn't only return access key (primaryKey), but also returns connection strings (primaryConnectionString).

Implementation

With this information, we can implement the outputs section like this:

{
"variables": {
"serviceBus": {
"name": "my-service-bus",
"sasKeyName": "RootManageSharedAccessKey"
},
"resourceId1": "[resourceId('Microsoft.ServiceBus/namespaces', variables('serviceBus').name)]",
"resourceId2": "[resourceId('Microsoft.ServiceBus/namespaces/authorizationRules', variables('serviceBus').name, variables('serviceBus').sasKeyName)]",
"apiVersion": "[providers('Microsoft.ServiceBus', 'namespaces').apiVersions[0]]"
},
"resources": [],
"outputs": {
"serviceBusEndpoint": {
"type": "string",
"value": "[reference(variables('resourceId1'), variables('apiVersion')).serviceBusEndpoint]"
},
"serviceBusSasKey": {
"type": "string",
"value": "[listKeys(variables('resourceId2'), variables('apiVersion')).primaryKey]"
},
"serviceBusConnectionString": {
"type": "string",
"value": "[listKeys(variables('resourceId2'), variables('apiVersion')).primaryConnectionString]"
}
}
}
view raw ServiceBus.json hosted with ❤ by GitHub

Storage Accounts

Azure Storage Account is similar to Azure Cosmos DB, in terms of providing the result after ARM template deployment – it provides only access keys through the listKeys() function when it's deployed, not the connection string. Therefore, we should make this up using the concat() function.

Available Functions

In order to identify available resource functions, simply run the following Azure PowerShell cmdlet. It will show several operations that we can utilise in the outputs section of ARM templates.

Get-AzureRmProviderOperation -OperationSearchString "Microsoft.Storage/*" `
| Where-Object { $_.Operation -like "*list*" } `
| Format-Table Operation

It returns three operation, listKeys, listAccountSas and listServiceSas, but we only use the listKeys operation for now. If we want to know the object structure that listKeys() function returns, simply run the following Azure PowerShell cmdlet:

Invoke-AzureRmResourceAction `
-ResourceGroupName "RESOURCE_GROUP_NAME" `
-ResourceType "Microsoft.Storage/storageAccounts" `
-ResourceName "STORAGE_ACCOUNT_NAME" `
-Action listKeys `
-Force

The listKeys() function returns keys as an array value.

Implementation

With this information, we can implement the outputs section like this:

{
"variables": {
"storageAccount": {
"name": "my-storage-account"
},
"resourceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccount').name)]",
"apiVersion": "[providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]]"
},
"resources": [],
"outputs": {
"storageAccountKey": {
"type": "string",
"value": "[listKeys(variables('resourceId'), variables('apiVersion')).keys[0].value]"
},
"storageAccountEndpoint": {
"type": "string",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccount').name, ';AccountKey=', listKeys(variables('resourceId'), variables('apiVersion')).keys[0].value, ';EndpointSuffix=core.windows.net')]"
}
}
}

Functions

In my previous post, Dynamic Access to Azure Functions Keys without KUDU Dependencies, we had walked through Azure REST API to get function keys from an Azure Functions app. In fact, we can also get those individual function keys through the ARM template's outputs section. By the way, this approach has a restriction. This can only access to individual function keys, not host key nor master key.

Available Functions

In order to identify available resource functions, simply run the following Azure PowerShell cmdlet. It will show several operations that we can utilise in the outputs section of ARM templates.

Get-AzureRmProviderOperation -OperationSearchString "Microsoft.Web/*" `
| Where-Object { $_.Operation -like "*function*list*" } `
| Format-Table Operation

It returns one operation, listSecrets. As mentioned above, this requires individual function names to get their respective keys. If we want to know the object structure that listSecrets() function returns, simply run the following Azure PowerShell cmdlet:

$apiVersion = `
((Get-AzureRmResourceProvider `
-ProviderNamespace "Microsoft.Web").ResourceTypes `
| Where-Object { $_.ResourceTypeName -eq "sites" }).ApiVersions[0]
Invoke-AzureRmResourceAction `
-ApiVersion $apiVersion `
-ResourceGroupName "RESOURCE_GROUP_NAME" `
-ResourceType "Microsoft.Web/sites/functions" `
-ResourceName "FUNCTION_APP_NAME/FUNCTION_NAME" `
-Action listsecrets `
-Force

The listSecrets() function returns key property.

NOTE: We need to specify the API version to run this cmdlet, even though it's an optional parameter; otherwise it will throw an error.

Implementation

With this information, we can implement the outputs section like this:

{
"variables": {
"functionApp": {
"name": "my-function-app",
"functionName": "my-function"
},
"resourceId": "[resourceId('Microsoft.Web/sites/functions', variables('functionApp').name, variables('functionApp').functionName)]",
"apiVersion": "[providers('Microsoft.Web', 'sites').apiVersions[0]]"
},
"resources": [],
"outputs": {
"functionKey": {
"type": "string",
"value": "[listSecrets(variables('resourceId'), variables('apiVersion')).key]"
}
}
}

Logic Apps

When an Azure Logic Apps instance is deployed, the instance has an endpoint URL. However, we only knows when it is created. In addition to this, the endpoint URL comes with a SAS token, which we have no idea how it's generated. Therefore, we need to identify those values. Make sure that we only get the endpoint URL, if the Logic App instance is an HTTP trigger.

Available Functions

In order to identify available resource functions, simply run the following Azure PowerShell cmdlet. It will show several operations that we can utilise in the outputs section of ARM templates.

Get-AzureRmProviderOperation -OperationSearchString "Microsoft.Logic/*" `
| Where-Object { $_.Operation -like "*trigger*list*" } `
| Format-Table Operation

It returns the listCallbackUrl operation. If we want to know the object structure that listCallbackUrl() function returns, simply run the following Azure PowerShell cmdlet:

$apiVersion = `
((Get-AzureRmResourceProvider `
-ProviderNamespace "Microsoft.Logic").ResourceTypes `
| Where-Object { $_.ResourceTypeName -eq "workflows" }).ApiVersions[0]
Invoke-AzureRmResourceAction `
-ApiVersion $apiVersion `
-ResourceGroupName "RESOURCE_GROUP_NAME" `
-ResourceType "Microsoft.Logic/workflows/triggers" `
-ResourceName "RESOURCE_NAME/manual" `
-Action listCallbackUrl `
-Force

The listCallbackUrl() function returns value, basePath and queries properties.

NOTE: We need to specify the API version to run this cmdlet, even though it's an optional parameter; otherwise it will throw an error.

Implementation

With this information, we can implement the outputs section like this:

{
"variables": {
"logicApp": {
"name": "my-logic-app",
"trigger": "manual"
},
"resourceId": "[resourceId('Microsoft.Logic/workflows/triggers', variables('logicApp').name, variables('logicApp').trigger)]",
"apiVersion": "[providers('Microsoft.Logic', 'workflows').apiVersions[0]]"
},
"resources": [],
"outputs": {
"endpointUrl": {
"type": "string",
"value": "[listCallbackUrl(variables('resourceId'), variables('apiVersion')).value]"
},
"path": {
"type": "string",
"value": "[listCallbackUrl(variables('resourceId'), variables('apiVersion')).basePath]"
},
"querystring": {
"type": "string",
"value": "[concat('api-version=', variables('apiVersion'), '&sp=', uriComponent(listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sp), '&sv=', listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sv, '&sig=', listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sig)]"
},
"sasToken": {
"type": "string",
"value": "[concat('sp=', uriComponent(listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sp), '&sv=', listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sv, '&sig=', listCallbackUrl(variables('resourceId'), variables('apiVersion')).queries.sig)]"
}
}
}
view raw LogicApp.json hosted with ❤ by GitHub

So far, we have identified how we can utilise the outputs sections of ARM templates using various template functions. As we can see above, Each Azure resource has all different implementations to get keys, endpoints and connection strings. Some are directly using the reference() function, the others are using either listKeys() or listWhatever() functions to get those values. Even worse, the object returned by listKeys() or listWhatever() has all different structure. Having a different structure is fine for each Azure resource, but they could have been documented in a better way. I hope this post would help figure out how to utilise the outputs section with more ease.

ACKNOWLEDGEMENT: This post has originally been posted at Mexia blog.