Figuring out the Minimum Permissions Required to Deploy an Azure ARM Template

Maninderjit (Mani) Bindra
Microsoft Azure
Published in
4 min readAug 15, 2023

Please Note: There has been a major enhancement to the az-mpf utility.In addition to ARM templates it now also supports Bicep and Terraform. There is a substantial change in the sub commands and flags of this utility. This article will be updated to reflect these changes however the repository has already been updated with most usage scenarios, and can be referred to in the interim.

There are a few things you need to consider when associating a service principal / managed identity with an Azure Policy assignment. If the Azure Policy is using an ARM template (deploy if exists) for enforcement / remediation, then it becomes critical to figure out the right roles to associate with the service principal / managed identity. Ideally you need to associate enough permissions for the ARM template deployment to succeed , and not to much more. Figuring out the right combination of built-in roles (or custom roles in some cases) is non trivial for complex deployments. To figure this out, one of the first steps is to understand the minimum permissions required for the ARM template deployment, and then we can figure out whether to create a custom role or use one or more built-in roles.

One way to figure out the minimum permissions required is by looking at the ARM template and trying to figure it out manually. This is challenging, particularly as the templates increase in complexity.

In this post we will look at one way of figuring out the minimum permissions required using the open source az-mpf utility.

How does the az-mpf utility work?

Note! The image in this section is from the az-mpf github repository https://github.com/maniSbindra/az-mpf, which is owned by me.

Update 4th-Feb-2024: Thanks to the work done by Engin Polat, starting version 0.3 the default mechanism for finding the minimum permissions required is using what if analysis, in this mode only a resource group is temporarily created, the other resources in the ARM template are not required to be created. Setting the flag mpfMode to fullDeployment can still be used to revert to the old operation mode where the utility temporarily create resources to ascertain the minimum permissions required.

Starting with no permissions associated with a service principal (SP), the utility tries to either perform what if analysis (default whatif mpfMode) on the provided ARM template or deploy (fullDeployment mpfMode) the provided ARM template, when authorization errors are received, they are parsed to find the resource and permissions from the error message, these permissions are then made available to the SP and the deployment is re-tried, till finally the deployment succeeds. At the point when the deployment succeeds, the minimum permissions required are printed and all Azure resources created are cleaned up.

Example with an ARM template

Let us take the example of the vm-simple-linux ARM template from the Azure Quickstart templates, the template and parameters files are azuredeploy.json and azuredeploy.parameters.json respectively. This template aims to deploy a Linux VM with associated components. Without looking at the template it detail let us run the az-mpf tool against it.

As mentioned earlier the utility will uses az CLI credentials for some tasks, which need to have permissions to create/delete temporary resource group,role assignments, and role definition. A separate Service Principal (SP) credentials are used to try and deploy the ARM template. Initially the all Role Assignments for the SP are deleted, then as the utility starts getting Authorization errors, when attempting deployments using the SP, the permissions mentioned in the authorization errors are provided to the SP, and then the deployment re-tried. After Successful deployment the utility will print the minimum permissions required. Let us execute the utility by following the steps below:

# Login using az CLI if not already logged in
# az login

export SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID"
export TENANT_ID="YOUR_TENANT_ID"
export SP_CLIENT_ID="YOUR_SP_CLIENT_ID"
export SP_CLIENT_SECRET="YOUR_SP_CLIENT_SECRET"
export SP_OBJECT_ID="YOUR_SP_OBJECT_ID" #Your Service Principal Object ID

# Download az-mpf using instructions at https://github.com/maniSbindra/az-mpf#installation
# Alternately you can build it locally: https://github.com/maniSbindra/az-mpf#installation

# Since I am on a MAC Arm64 download corresponding executable
# Note! modify the curl command below to use the correct platform and the latest version of az-mpf
curl -L https://github.com/maniSbindra/az-mpf/releases/download/v0.3.2/az-mpf-darwin-arm64 -o az-mpf
chmod +x ./az-mpf

# Download the template and parameters file to same directory
curl -L https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.compute/vm-simple-linux/azuredeploy.json -o ./azuredeploy.json
curl -L https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.compute/vm-simple-linux/azuredeploy.parameters.json -o ./azuredeploy.parameters.json

# Run az-mpf for the parameter file

./az-mpf -subscriptionID=${SUBSCRIPTION_ID} -spClientID="${SP_CLIENT_ID}" -spObjectID=${SP_OBJECT_ID} -spClientSecret="${SP_CLIENT_SECRET}" -tenantID="${TENANT_ID}" -templateFile="./azuredeploy.json" -parametersFile="./azuredeploy.parameters.json" -showDetailedOutput

The detailed output is shown in the gist below

Examining the output of the az-mpf

Let us examine the summary output of the command in more detail:

------------------------------------------------------------------------------------------------------------------------------------------
Permissions Assigned to Service Principal for Resource Group: /subscriptions/SSSSSSSS-SSSS-SSSS-SSSS-SSSSSSSSSSSS/resourceGroups/testdeployrg-5fOqWzS
------------------------------------------------------------------------------------------------------------------------------------------
Microsoft.Compute/virtualMachines/extensions/read
Microsoft.Compute/virtualMachines/extensions/write
Microsoft.Compute/virtualMachines/read
Microsoft.Compute/virtualMachines/write
Microsoft.Network/networkInterfaces/read
Microsoft.Network/networkInterfaces/write
Microsoft.Network/networkSecurityGroups/read
Microsoft.Network/networkSecurityGroups/write
Microsoft.Network/publicIPAddresses/read
Microsoft.Network/publicIPAddresses/write
Microsoft.Network/virtualNetworks/read
Microsoft.Network/virtualNetworks/subnets/read
Microsoft.Network/virtualNetworks/subnets/write
Microsoft.Network/virtualNetworks/write
Microsoft.Resources/deployments/read
Microsoft.Resources/deployments/write

This shows us the minimal permissions which will need to be provided to the Service Principal to create and update this ARM template deployment. The permissions in the output represent 3 main groups, permissions to create the ARM deployment (Microsoft.Resources/deployments/+), permissions to create the virtual machine and extensions (Microsoft.Compute/+) and permissions to create Network components(Microsoft.Network/+).

We could create a custom role with precisely these permissions, but doing so each time could cause the number of custom roles to become unmanageable.

The other option which should be considered is to check the existing built-in roles, for a role or roles which contains all the required permissions, and a few additional permissions, which you/your organization find acceptable to assign.

Thanks for reading this post. I hope you liked it. Please feel free to write your comments and views about the same over here or at @manisbindra

--

--

Maninderjit (Mani) Bindra
Microsoft Azure

Gopher, Cloud, Containers, K8s, DevOps | LFCS | CKA | CKS | Principal Software Engineer @ Microsoft