Assigning RBAC permissions with Azure Resource Manager templates

Recently, I updated my AKS ARM template supporting the latest AKS feature set and important RBAC role assignments for the AKS cluster.

After having a hard time, I managed to get the RBAC role assignment working.

When you now think what should be so complicated? Check out my tweet on Twitter getting the background information.

-> https://twitter.com/neumanndaniel/status/1294272253211947008

It is not as easy as it sounds. Just using the Microsoft.Authorization resource provider as in the ARM template reference only works on resource group level and above like subscriptions.

-> https://docs.microsoft.com/en-us/azure/templates/microsoft.authorization/roleassignments

Here starts our journey of todays blog post. I walk you through my findings and provide you with the necessary guidance doing the RBAC role assignment with ARM templates in the correct way. So, at least I can spare you some time when this topic arises.

Let us start from the beginning why you can use the Microsoft.Authorization resource provider only on resource group level and above like subscriptions.

When using the RP Microsoft.Authorization we specifically using the resource type roleAssignments.

As you can deploy ARM templates only on MG (management group), subscription and RG (resource group) level, tenant level is also possible, the RP targets the resource at the deployment level. This excludes resources and their sub resources for instance a Virtual Network and its subnets.

Let us take a look at two ARM templates with an RBAC role assignment on subscription and RG level.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
  },
  "resources": [
    {
      "name": "7bc954d3-82e4-4473-9c02-3a4bebe54156",
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2020-04-01-preview",
      "properties": {
        "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
        "principalId": "00000000-0000-0000-0000-000000000000",
        "scope": "/subscriptions/00000000-0000-0000-0000-000000000000"
      }
    }
  ]
}

As seen above the template assigns a user object Network Contributor permission on the subscription level.

az deployment sub create --location northeurope --template-file subscription.json --verbose --no-wait

The Azure CLI command explicitly targets the subscription for the deployment.

Similar looks the template targeting the resource group.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
  },
  "resources": [
    {
      "name": "5399cb2e-af9f-48e5-8336-30166a882a1d",
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2020-04-01-preview",
      "properties": {
        "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
        "principalId": "00000000-0000-0000-0000-000000000000",
        "scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/azst-aks1"
      }
    }
  ]
}
az deployment group create -g azst-aks1 --template-file resource_group.json --verbose --no-wait

Using the following ARM template assigning the Network Contributor permissions on a VNET subnet will not work.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
  },
  "resources": [
    {
      "name": "8d8defe1-27bc-46e2-bf1b-76c8834cdfac",
      "type": "Microsoft.Authorization/roleAssignments",
      "apiVersion": "2020-04-01-preview",
      "properties": {
        "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
        "principalId": "00000000-0000-0000-0000-000000000000",
        "scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/azst-aks1/providers/Microsoft.Network/virtualNetworks/azst-aks1/subnets/azst-aks1"
      }
    }
  ]
}
az deployment group create -g azst-aks1 --template-file resource.json --verbose --no-wait

We get an error message that the assignment scope does not match the scope of the deployment.

The request to create role assignment '8d8defe1-27bc-46e2-bf1b-76c8834cdfac' is not valid. Role assignment scope '/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/azst-aks1/providers/Microsoft.Network/virtualNetworks/azst-aks1/subnets/azst-aks1' must match the scope specified on the URI '/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/azst-aks1

As you can deploy ARM templates only on MG (management group), subscription and RG (resource group) level, tenant level is also possible, the RP targets the resource at the deployment level. This excludes resources and their sub resources for instance a Virtual Network and its subnets.

So, how you going to do an RBAC role assignment on a resource or sub resource?

Via a sub resource deployment. Each RP has a providers resource type roleAssignments which we can leverage in a sub resource deployment assigning the RBAC role to resources like a VNET or sub resources like a VNET subnet.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
  },
  "resources": [
    {
      "name": "azst-aks1/azst-aks1/Microsoft.Authorization/bf99d7eb-7c96-472f-b655-ce306fc65b37",
      "type": "Microsoft.Network/virtualNetworks/subnets/providers/roleAssignments",
      "apiVersion": "2020-04-01-preview",
      "properties": {
        "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
        "principalId": "00000000-0000-0000-0000-000000000000",
        "scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/azst-aks1/providers/Microsoft.Network/virtualNetworks/azst-aks1/subnets/azst-aks1"
      }
    }
  ]
}

The template assumes that the target resource lives in the same RG that the deployment targets. If this is not the case, you must use a nested / linked template where you can specify the resource group.

...
    {
      "apiVersion": "[variables('apiVersion').deployments]",
      "type": "Microsoft.Resources/deployments",
      "name": "[concat(parameters('name'), '-vnet-subnet-rbac')]",
      "resourceGroup": "[parameters('vnetResourceGroupName')]",
      "dependsOn": [
        "[concat('Microsoft.ContainerService/managedClusters/', parameters('name'))]"
      ],
      "properties": {
        "mode": "Incremental",
        "template": {
          "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
          "contentVersion": "1.0.0.0",
          "parameters": {},
          "variables": {},
          "resources": [
            {
              "apiVersion": "[variables('apiVersion').rbac]",
              "type": "Microsoft.Network/virtualNetworks/subnets/providers/roleAssignments",
              "name": "[concat(parameters('vnetName'), '/', parameters('vnetSubnetName'), '/Microsoft.Authorization/', guid(resourceGroup().id, parameters('name'), 'Network Contributor'))]",
              "properties": {
                // Network Contributor = 4d97b98b-1d4f-4787-a291-c67834d212e7
                "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')]",
                "principalId": "[reference(concat('Microsoft.ContainerService/managedClusters/', parameters('name')), variables('apiVersion').aks, 'Full').identity.principalId]",
                "scope": "[variables('agentPoolProfiles').vnetSubnetId]"
              }
            }
          ],
          "outputs": {}
        }
      }
    },
...

One important part in the RBAC role assignment via sub resource deployments plays the dependency between the name and the type.

The segment length must be the same.

Not the name defines the segment length it is the type and the /providers/ does not count towards the length.

...
"type": "Microsoft.Network/virtualNetworks/subnets/providers/roleAssignments",
...

From the given example someone might think the segment length is five, but as I mentioned that /providers/ does not count the actual segment length is four.

Looking at the name this is true for our name.

...
"name": "azst-aks1/azst-aks1/Microsoft.Authorization/bf99d7eb-7c96-472f-b655-ce306fc65b37",
...

I hope you got some useful insights into RBAC role assignments via Azure Resource Manager templates that can spare you some time in the foreseeable future.

Facebooktwitterlinkedinmail