Azure ARM Template Scopes

Not long ago, you could only deploy resources to a resource group using ARM templates. To get create a resource group that you can deploy to, you had to use PowerShell to create the group first. That has all changed now. Now ARM templates can be used to deploy resources at Tenant, Management Group, Subscription, or Resource group scope.

Using a deployment scoped to a tenant, for example, you can deploy resources to management groups, subscriptions, and resource groups. The scope of the deployment restricts the resources available to deploy and the functions that can be invoked.

The schema that you refer to is different for each scope.

To illustrate deploying to these different scopes, I’m going to create a management group, define a policy scoped to that management group, create a resource group, and assign the policy to that resource group.

You can use this quickstart template to deploy a management group. I’ve replicated the template below for your convenience. Note the schema used this ARM template. It’s different from the resource group templates.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "mgName": {
        "type": "string",
        "defaultValue": "[concat('mg-', uniqueString(newGuid()))]"
      }
    },
    "resources": [
      {
        "type": "Microsoft.Management/managementGroups",
        "apiVersion": "2020-02-01",
        "name": "[parameters('mgName')]",
        "properties": {
        }
      }
    ]
  }

The following PowerShell command deploys the above template to Azure. Replace the Location and TemplateFile parameters.

New-AzTenantDeployment -Location "preferred location" -TemplateFile "template file name"

When I tried to deploy the above template I encountered an error.

“New-AzTenantDeployment: 11:40:14 AM – Error: Code=AuthorizationFailed; Message=The client ‘blah’ with object id ‘blah-blah-blah’ does not have authorization to perform action ‘Microsoft.Resources/deployments/validate/action’ over scope ‘/providers/Microsoft.Resources/deployments/ManagementGroup’ or the scope is invalid. If access was recently granted, please refresh your credentials.”
In order to create management groups, you need to elevate your access to manage all subscriptions and management groups. It’s very easy to do. Just follow this article. And, assign yourself the Owner role with root scope as detailed here.

Next, I need to create a policy definition at the management group level.
This is a modified version of Andrew Matveychuk’s template. Andrew has written a detailed post on deploying policies. For the original template go here. The policy definition itself, I believe, is based on a built-in Azure policy called “Allowed Locations”. You can read more about creating custom policies here. You can see all built-in policy definitions on the Azure portal under Policies->Authoring->Definitions. You can export existing policies from the portal and use that as a base for your policies.

{
        "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "policyName": {
                "type": "string",
                "defaultValue": "Restrict Locations"
            },
            "policyDisplayName": {
                "type": "string",
                "defaultValue": "Restrict Locations"
            },
            "policyDescription": {
                "type": "string",
                "defaultValue": "This policy restrict the locations you can specify when deploying resources. Excludes resource groups, Microsoft.AzureActiveDirectory/b2cDirectories, and resources that use the 'global' region."
            }
        },
        "resources": [
            {
                "type": "Microsoft.Authorization/policyDefinitions",
                "name": "[parameters('policyName')]",
                "apiVersion": "2019-09-01",
                "properties": {
                    "displayName": "[parameters('policyDisplayName')]",
                    "policyType": "Custom",
                    "description": "[parameters('policyDescription')]",
                    "metadata": {
                        "category": "General"
                    },
                    "mode": "All",
                    "parameters": {
                        "allowedLocations": {
                            "type": "Array",
                            "metadata": {
                                "displayName": "Allowed locations",
                                "description": "The list of locations that can be specified when deploying resources.",
                                "strongType": "location"
                            }
                        }
                    },
                    "policyRule": {
                        "if": {
                            "allOf": [
                                {
                                    "field": "location",
                                    "notIn": "[[parameters('allowedLocations')]" 
                                },
                                {
                                    "field": "location",
                                    "notEquals": "global"
                                },
                                {
                                    "field": "type",
                                    "notEquals": "Microsoft.AzureActiveDirectory/b2cDirectories"
                                }
                            ]
                        },
                        "then": {
                            "effect": "Deny"
                        }
                    }
                }
            }
        ]
    }

We need to use the New-AzManagementGroupDeployment PowerShell command to deploy the above template.

New-AzManagementGroupDeployment -Name policyDeploy -Location "preferred location" -ManagementGroupId "your management group id" -TemplateFile "template file name"

To check the deployed policy, be sure to set the scope to the management group on the portal. All the built-in policies are at the tenant scope.

The next step is to create a resource group within the management group. Before doing that,you need a subscription under the newly created management group. You can either create a new subscription or move an existing subscription. I did the later. You can move a subscription from one management group to another using an ARM template.

The final step is to create a resource group and assign a policy to that resource group. The custom policy definition that lives under the management group is treated as an extension resource. Therefore, we need to use the function extensionResourceId to get its resource id. You can read more about it here.

{
        "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
        "rgName": {
            "type": "string"
        },
        "rgLocation": {
            "type": "string"
        },
        "targetMG": {
            "type": "string"
        },
        "policyParameters": {
            "type": "object"
        
        }
        },
        "variables": {
        "mgScope": "[tenantResourceId('Microsoft.Management/managementGroups', parameters('targetMG'))]"
        },
        "resources": [
        {
            "type": "Microsoft.Resources/resourceGroups",
            "apiVersion": "2020-10-01",
            "name": "[parameters('rgName')]",
            "location": "[parameters('rgLocation')]",
            "properties": {}
        },
        {
            "type": "Microsoft.Authorization/policyAssignments",
            "apiVersion": "2020-09-01",
            "name": "locationpolicytest",
            "properties": {
            "scope": "[subscription().id]",
            "policyDefinitionId": "[extensionResourceId(variables('mgScope'), 'Microsoft.Authorization/policyDefinitions', 'Restrict Locations')]",
            "parameters": "[parameters('policyParameters')]"
            }
        }
        ]
    }

Here’s the parameter file that I used with the above template.

{
        "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "rgName": {
                "value": "subscriptionDeployGroup"
            },
            "rgLocation": {
                "value": "Australia East"
            },
            "targetMG": {
                "value": "testgroup"
            },
            "policyParameters": {
                "value": { 
                    "allowedLocations": {
                        "value": [  
                            "Australia East",
                            "Australia Southeast"
                        ]
                    }
                }
            }
        }

The PowerShell command to deploy the above template is as follows:

New-AzSubscriptionDeployment `
>>   -Name policyassign `
>>   -Location "preferred location" `
>>   -TemplateFile "template file name" `
>>   -TemplateParameterFile "parameter file name"

One thought on “Azure ARM Template Scopes

Leave a comment