I have been using ARM (Azure Resource Management) templates to deploy Azure resources for a fairly long time. Generally speaking, these templates are straightforward to use, but some steps can be a little laborious.
Let’s look at an actual example. I need to deploy an Azure Function App which will need to access an Azure App Configuration. The Azure service must therefore be given the appropriate permissions via Azure RBAC (Role-Based Access Control).
For this example, a system identity is going to be assigned to the Function App. An identity can be assigned using the ARM template for Function App deployment.
"resources": [ { "type": "Microsoft.Web/sites", "apiVersion": "2016-08-01", "name": "[parameters('azureServiceName')]", "location": "[parameters('azureLocation')]", "tags": { }, "kind": "functionapp", "identity": { "type": "SystemAssigned" }, "properties": { "enabled": true
In the example above, the identity property stipulates the type as SystemAssigned. Azure takes care of creating an identity for the service. Moreover, Azure will automatically delete this identity when the resource is decommissioned.
Assigning the role at the time the Azure App Configuration is deployed is simple enough. However, in this case, it is not a viable option because the Azure App Configuration service has existed for a long time, and I don’t want to redeploy that. In addition, it is also possible to assign a role in the same ARM template as the one for the Azure Function App. However, I want to re-use my ARM template for other Azure services. So I am going to assign the role in a separate ARM file as here:
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "azureServiceName": { "type": "String", "defaultValue": "", "metadata": { "description": "Azure service name to grant access" } }, "appConfigurationName": { "type": "String", "defaultValue": "", "metadata": { "description": "App configuration name" } } }, "variables": { "appConfigurationDataReaderRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/516239f1-63e1-4d78-a4de-a74fb236a071')]", "azureServicePrincipalId": "[resourceId('Microsoft.Web/sites', parameters('azureServiceName'))]", "appConfigurationId": "[resourceId('Microsoft.AppConfiguration/configurationStores', parameters('appConfigurationName'))]" }, "resources": [ { "type": "Microsoft.AppConfiguration/configurationStores/providers/roleAssignments", "apiVersion": "2018-01-01-preview", "name": "[concat(parameters('appConfigurationName'), '/Microsoft.Authorization/', guid(uniqueString(parameters('azureServiceName'))))]", "properties": { "roleDefinitionId": "[variables('appConfigurationDataReaderRoleId')]", "principalId": "[reference(variables('azureServicePrincipalId'), '2018-11-01', 'Full').identity.principalId]", "scope": "[variables('appConfigurationId')]" } } ], "outputs": {} }
Without going to extreme lengths, I have made some aspects of this ARM configurable. There are therefore two parameters, i.e. the Azure service name (the name of the Function App) and the Azure App Configuration service name.
To assign a role to an identity, the role’s identifier as defined by Azure has to be retrieved. Here, I want to give the App Configuration Data Reader role. This is a native Azure role, and its identifiers are supplied by Microsoft. This is why the appConfigurationDataReaderRoleId variable is a character string ending in a GUID.
Next, the identity of the Azure service to which the role is to be assigned (in this case, the Function App) needs to be found. The reference to the resource is retrieved, and stored in the azureServicePrincipalId variable.
Then I need the identifier for the resource to which the authority is to be assigned (in this case, Azure App Configuration). This is done in my appConfigurationId variable.
Lastly, there are two specific points to be noted: