loading...

Deploying across subscriptions; Using Scope with ARM Templates for your deployments in Azure

omiossec profile image Olivier Miossec Updated on ・7 min read

Most of the time, when you think about ARM templates, it's about deploying objects in a Resource Group. It’s the most used deployment level. But ARM templates allow you to deploy more than resource group objects.
There are four levels or scopes in ARM template, Tenant, Management Group, Subscription, and resource group. These levels are called scopes. The logic behind resource deployment in the first three scopes is the same as in the Resource group scope.
At all scope, ARM templates use the JSON notation, with the five sections (Parameters, Variables, Resources, Functions, and Outputs). But schemas are different and of course, you are only allowed to deploy resources related to the scope. Impossible to deploy a VNET in the management scope (at least directly).

What can you deploy at these scopes?

Scope Deployment objects
Tenant Scope Management group objects
Policy (Definition and assignment)
Role (Definition and assignment)
Management Group Scope Management group objects
Policy (Definition and assignment)
Role (Definition and assignment)
Subscription Scope Management group objects
Policy (Definition, remediation, and assignment)
Role (Definition and assignment)
Blueprint
EventGrid Event Subscriptions
Tags (yes subscription support tags)
Budget
WorkspaceSetting (Azure Security Center)
Resource Group

For the Tenant Scope, you need some extra permission even for a Global Azure AD Administrator.
You need an elevate access to management subscription and management group.

More you may need to have extra privilege on the root of the tenant.

New-AzRoleAssignment -SignInName "[username]" -Scope "/" -RoleDefinitionName "Owner"

Or with Object ID instead of username

New-AzRoleAssignment -ObjectID "[userObjectId]" -Scope "/" -RoleDefinitionName "Owner"

If you want to create two management groups, one parent and one child using ARM templates, you will need to use the tenant scope. How can you represent in ARM the hierarchy of the two groups?
The management group resource in ARM template look like this

    {
      "type": "Microsoft.Management/managementGroups",
      "apiVersion": "2019-11-01",
      "name": "[parameters('mgName')]",
      "properties": {
      }
    }

Under the properties, you can add the parent detail like this

"details": {
      "parent": {
        "id": "string"
      }

Creating the management group will be like this

{
    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/tenantDeploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {},
    "functions": [],
    "variables": {
        "firstMGroupName": "First-MG",
        "firstMGID": "MG-001",
        "secondMGroupName": "Second-MG",
        "secondMGID": "MG-002"
    },
    "resources": [
        {
            "type": "Microsoft.Management/managementGroups",
            "apiVersion": "2020-02-01",
            "name": "[variables('firstMGID')]",
            "properties": {
                "displayName": "[variables('firstMGroupName')]"
            },
            "resources": []
        },
        {
            "type": "Microsoft.Management/managementGroups",
            "apiVersion": "2020-02-01",
            "dependsOn": [
                "[concat('Microsoft.Management/managementGroups/', variables('firstMGID'))]"
            ],
            "name": "[variables('secondMGID')]",
            "properties": {
                "displayName": "[variables('secondMGroupName')]",
                "details": {
                    "parent": {
                        "id": "[tenantResourceId('Microsoft.Management/managementGroups/', variables('firstMGID'))]"
                    }
                }  
            },
            "resources": []
        }  
    ],
    "outputs": {}
}

To deploy it, you can use the New-AzTenantDeployment cmdlet

New-AzTenantDeployment -Name <DeploymentName> -Location <Default location for log> -TemplateFile <Path To Template File> -TemplateParameterFile <Path To Template Parameters File>

What is very interesting, all scopes also support deployment objects, Microsoft.Resources/deployments. You can use it to deploy multiple objects, but you can also use it for nested deployment across scopes.
With the deployment resource, you can deploy management group objects from the tenant scope, subscription object from the tenant scope, or management group scope and you can deploy resource group objects from the subscription scope.

Imagine the situation where you need to deploy a network infrastructure across two subscriptions. You need to deploy the hub network in one subscription and a spoke network in another subscription. Using only the resource group scope you will need to have at least two deployments. Using the proper scope you can do it with only one template.
The tenant and the Management group scope let you deploy subscription scope objects which include resource group deployment.
In our example, two VNET in different subscriptions and the peering between these two VNET can be done with one template.

You will need to use the tenant or the management group's scope. The management group require fewer permissions so let use it

"$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",

Here’s what you can see parameters

    "parameters": {
        "vnetAName": {
            "type": "string",
            "metadata": {
                "description": "Name of the first VNET"
            }
        },
        "vnetBName": {
            "type": "string",
            "metadata": {
                "description": "Name of the Second VNET"
            }
        },
        "vnetAPrefix": {
            "type": "string",
            "metadata": {
                "description": "Prefix of the first VNET"
            }
        },
        "vnetBPrefix": {
            "type": "string",
            "metadata": {
                "description": "Prefix of the Second VNET"
            }
        },
        "subscriptionAID": {
            "type": "string",
            "metadata": {
                "description": "the Subscription ID for the first VNET"
            }
        },
        "resourceGroupAName": {
            "type": "string",
            "metadata": {
                "description": "the resource group name for the first VNET"
            }
        },
        "subscriptionBID": {
            "type": "string",
            "metadata": {
                "description": "the Subscription ID for the second VNET"
            }
        },
        "resourceGroupBName": {
            "type": "string",
            "metadata": {
                "description": "the resource group name for the second VNET"
            }
        }
    },
    "variables": {
        "vnetAtoVnetBPeeringName": "[concat(parameters('vnetAName'),'-to-',parameters('vnetBName'))]",
        "vnetBtoVnetAPeeringName": "[concat(parameters('vnetBName'),'-to-',parameters('vnetAName'))]",
        "defaultLocation": "francecentral"
    },

The first task is to ensure the resource group in the subscription is created before deploying the VNET by using a first deployment resource at the subscription level and use the resource group resource.

  {
            "apiVersion": "2020-06-01",
            "name": "createNetworkRGA",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
            "subscriptionId": "[parameters('subscriptionAID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [
                         {
                          "type": "Microsoft.Resources/resourceGroups",
                          "apiVersion": "2020-06-01",
                          "location": "[variables('defaultLocation')]",
                          "name": "[parameters('resourceGroupAName')]",
                          "properties": { }
                        },

Then create a deployment resource inside the first deployment resource this time at the resource group level to deploy the network.

{
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createNetworkResource",
                            "resourceGroup": "[parameters('resourceGroupAName')]",
                            "dependsOn": [
                              "[concat('Microsoft.Resources/resourceGroups/', parameters('resourceGroupAName'))]"
                            ],
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "name": "[parameters('vnetAName')]",
                                        "type": "Microsoft.Network/virtualNetworks",
                                        "apiVersion": "2019-11-01",
                                       "location":"[variables('defaultLocation')]",
                                        "properties": {
                                            "addressSpace": {
                                                "addressPrefixes": [
                                                    "[parameters('vnetAPrefix')]"
                                                ]
                                            },

Then you can create the two peering objects, but you will need to ensure the two VNET are deployed.
Remember to create a peering resource, as it is a child resource of the virtual network object, you need to include the VNET name in the peering name.

{
            "apiVersion": "2020-06-01",
            "name": "createPeeringAtoB",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
             "dependsOn": [
                "[concat('Microsoft.Resources/deployments/', 'createNetworkRGB')]",
                "[concat('Microsoft.Resources/deployments/', 'createNetworkRGA')]"
            ],
            "subscriptionId": "[parameters('subscriptionAID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [
                        {
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createNetworkPeeringfromA",
                            "resourceGroup": "[parameters('resourceGroupAName')]",
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
                                        "apiVersion": "2020-05-01",
                                        "name": "[concat(parameters('vnetAName'), '/', variables('vnetAtoVnetBPeeringName'))]",
                                        "properties": {

The complete template is here.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetAName": {
            "type": "string",
            "defaultValue":"vnetA",
            "metadata": {
                "description": "Name of the first VNET"
            }
        },
        "vnetBName": {
            "type": "string",
            "defaultValue":"vnetB",
            "metadata": {
                "description": "Name of the Second VNET"
            }
        },
        "vnetAPrefix": {
            "type": "string",
            "defaultValue": "10.0.0.0/24",
            "metadata": {
                "description": "Prefix of the first VNET"
            }
        },
        "vnetBPrefix": {
            "type": "string",
            "defaultValue": "10.0.1.0/24",
            "metadata": {
                "description": "Prefix of the Second VNET"
            }
        },
        "subscriptionAID": {
            "type": "string",
            "metadata": {
                "description": "the Subscription ID for the first VNET"
            }
        },
        "resourceGroupAName": {
            "type": "string",
            "defaultValue": "a-rg",
            "metadata": {
                "description": "the resource group name for the first VNET"
            }
        },
        "subscriptionBID": {
            "type": "string",
            "metadata": {
                "description": "the Subscription ID for the second VNET"
            }
        },
        "resourceGroupBName": {
            "type": "string",
            "defaultValue": "b-rg",
            "metadata": {
                "description": "the resource group name for the second VNET"
            }
        }
    },
    "variables": {
        "vnetAtoVnetBPeeringName": "[concat(parameters('vnetAName'),'-to-',parameters('vnetBName'))]",
        "vnetBtoVnetAPeeringName": "[concat(parameters('vnetBName'),'-to-',parameters('vnetAName'))]",
        "defaultLocation": "francecentral"
    },
    "resources": [
            {
            "apiVersion": "2020-06-01",
            "name": "createNetworkRGA",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
            "subscriptionId": "[parameters('subscriptionAID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [
                         {
                          "type": "Microsoft.Resources/resourceGroups",
                          "apiVersion": "2020-06-01",
                          "location": "[variables('defaultLocation')]",
                          "name": "[parameters('resourceGroupAName')]",
                          "properties": { }
                        },
                        {
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createAVNET",
                            "resourceGroup": "[parameters('resourceGroupAName')]",
                            "dependsOn": [
                              "[concat('Microsoft.Resources/resourceGroups/', parameters('resourceGroupAName'))]"
                            ],
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "name": "[parameters('vnetAName')]",
                                        "type": "Microsoft.Network/virtualNetworks",
                                        "apiVersion": "2019-11-01",
                                        "location": "[variables('defaultLocation')]",
                                        "properties": {
                                            "addressSpace": {
                                                "addressPrefixes": [
                                                    "[parameters('vnetAPrefix')]"
                                                ]
                                            },
                                            "subnets": [
                                                {
                                                    "name": "Subnet-1",
                                                    "properties": {
                                                        "addressPrefix": "[parameters('vnetAPrefix')]"
                                                    }
                                                }
                                            ]
                                        }
                                    }
                                  ]
                                }
                            }
                        }
                    ]
                }
            }
        },
            {
            "apiVersion": "2020-06-01",
            "name": "createNetworkRGB",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
            "subscriptionId": "[parameters('subscriptionBID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [

                         {
                          "type": "Microsoft.Resources/resourceGroups",
                          "apiVersion": "2020-06-01",
                          "location": "[variables('defaultLocation')]",
                          "name": "[parameters('resourceGroupBName')]",
                          "properties": { }
                        } ,
                        {
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createBVnet",
                            "resourceGroup": "[parameters('resourceGroupBName')]",
                            "dependsOn": [
                              "[concat('Microsoft.Resources/resourceGroups/', parameters('resourceGroupBName'))]"
                            ],
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "name": "[parameters('vnetBName')]",
                                        "type": "Microsoft.Network/virtualNetworks",
                                        "apiVersion": "2019-11-01",
                                        "location": "[variables('defaultLocation')]",
                                        "properties": {
                                            "addressSpace": {
                                                "addressPrefixes": [
                                                    "[parameters('vnetBPrefix')]"
                                                ]
                                            },
                                            "subnets": [
                                                {
                                                    "name": "Subnet-1",
                                                    "properties": {
                                                        "addressPrefix": "[parameters('vnetBPrefix')]"
                                                    }
                                                }
                                            ]
                                        }
                                    }
                                  ]
                                }
                            }
                        }
                    ]
                }
            }
        },
        {
            "apiVersion": "2020-06-01",
            "name": "createPeeringAtoB",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
            "dependsOn": [
                    "[concat('Microsoft.Resources/deployments/', 'createNetworkRGB')]",
                    "[concat('Microsoft.Resources/deployments/', 'createNetworkRGA')]"
            ],
            "subscriptionId": "[parameters('subscriptionAID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [
                        {
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createNetworkPeeringfromA",
                            "resourceGroup": "[parameters('resourceGroupAName')]",
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
                                        "apiVersion": "2020-05-01",
                                        "name": "[concat(parameters('vnetAName'), '/', variables('vnetAtoVnetBPeeringName'))]",
                                        "properties": {
                                            "peeringState": "Connected",
                                            "remoteVirtualNetwork": {
                                                "id": "[concat('/subscriptions/',parameters('subscriptionBID'),'/resourceGroups/',parameters('resourceGroupBName'),'/providers/Microsoft.Network/virtualNetworks/', parameters('vnetBName'))]"
                                            },
                                            "allowVirtualNetworkAccess": true,
                                            "allowForwardedTraffic": true,
                                            "allowGatewayTransit": false,
                                            "useRemoteGateways": false,
                                            "remoteAddressSpace": {
                                                "addressPrefixes": [
                                                    "[parameters('vnetBPrefix')]"
                                                ]
                                            }
                                        }
                                    }
                                  ]
                                }
                            }
                        }
                    ]
                }
            }
        },
        {
            "apiVersion": "2020-06-01",
            "name": "createPeeringBtoA",
            "type": "Microsoft.Resources/deployments",
            "location": "francecentral",
             "dependsOn": [
                "[concat('Microsoft.Resources/deployments/', 'createNetworkRGB')]",
                "[concat('Microsoft.Resources/deployments/', 'createNetworkRGA')]"
            ],
            "subscriptionId": "[parameters('subscriptionBID')]",
            "properties": {
                "mode": "Incremental",
                "template": {
                    "$schema": "https://schema.management.azure.com/schemas/2019-08-01/subscriptionDeploymentTemplate.json#",
                    "contentVersion": "1.0.0.0",
                    "parameters": {},
                    "variables": {},
                    "resources": [
                        {
                            "type": "Microsoft.Resources/deployments",
                            "apiVersion": "2020-06-01",
                            "name": "createNetworkPeeringfromB",
                            "resourceGroup": "[parameters('resourceGroupBName')]",
                            "properties": {
                                  "mode": "Incremental",
                                  "template": {
                                  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
                                  "contentVersion": "1.0.0.0",
                                  "resources": [
                                    {
                                        "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
                                        "apiVersion": "2020-05-01",
                                        "name": "[concat(parameters('vnetBName'), '/', variables('vnetAtoVnetBPeeringName'))]",
                                        "properties": {
                                            "peeringState": "Connected",
                                            "remoteVirtualNetwork": {
                                                "id": "[concat('/subscriptions/',parameters('subscriptionAID'),'/resourceGroups/',parameters('resourceGroupAName'),'/providers/Microsoft.Network/virtualNetworks/', parameters('vnetAName'))]"
                                            },
                                            "allowVirtualNetworkAccess": true,
                                            "allowForwardedTraffic": true,
                                            "allowGatewayTransit": false,
                                            "useRemoteGateways": false,
                                            "remoteAddressSpace": {
                                                "addressPrefixes": [
                                                    "[parameters('vnetAPrefix')]"
                                                ]
                                            }
                                        }
                                    }
                                  ]
                                }
                            }
                        }
                    ]
                }
            }
        } 
    ],
    "outputs": {     
    }
}

There are some limitations to use multi scope deployment. You cannot use resource group scope deployment functions similar to resourceID or resourcegroup(). If you need to use these functions you will need to use a linked template instead of inline deployment presented in this example. This is the best practice.

Posted on by:

omiossec profile

Olivier Miossec

@omiossec

Microsoft Azure MVP, Passionate about Cloud and DevOps. Co-organizers of the French PowerShell UG and Paris PowerShell & WinOps UG. I live in Paris.

Discussion

markdown guide