diff --git a/examples/landing-zones/tagged-subscription/README.md b/examples/landing-zones/tagged-subscription/README.md new file mode 100644 index 00000000..65aa1d95 --- /dev/null +++ b/examples/landing-zones/tagged-subscription/README.md @@ -0,0 +1,82 @@ +# Create new subscription with a tag into a management group + +The ARM template provided in this folder can be used to create new, empty subscriptions with a tag into the targeted management group. + +## Parameters + +- "subscriptionAliasName": It is recommended that the subscription alias name is the same as the displayName to ensure easier manageability +- "billingAccountId": Provide the full resourceId for the enrollmentAccount. E.g., "/providers/Microsoft.Billing/billingAccounts//enrollmentAccounts/" +- "targetManagementGroup": Provide the full resourceId for the target management group in order to place the subscription directly under a management group. E.g., "/providers/Microsoft.Management/managementGroups/" +- "tagValue": Provide a value for the tag to identify the 'application key' for the subscription. + +````json + + "parameters": { + "subscriptionAliasName": { + "type": "string", + "metadata": { + "description": "Provide alias (and displayName) for the subscription" + } + }, + "targetManagementGroup": { + "type": "string", + "metadata": { + "details": "Select targeted management group that the subscription will land into" + } + }, + "billingAccountId": { + "type": "string", + "metadata": { + "description": "Provide the resourceId for the enrollment account or MCA" + } + }, + "tagValue": { + "type": "string", + "metadata": { + "description": "Provide a value of the required application key tag." + } + } + }, +```` + +## Scope escape + +This ARM template is using the "scope escape" property on the resource in order to create a tenant level resource (subscription aliases) while being invoked as a management group deployment + +````json + + { + "scope": "/", // routing the request to tenant root + "name": "[parameters('subscriptionAliasName')]", + "type": "Microsoft.Subscription/aliases", + "apiVersion": "2020-09-01", + "properties": { + "workLoad": "Production", + "displayName": "[parameters('subscriptionAliasName')]", + "billingScope": "[parameters('billingAccountId')]", + "managementGroupId": "[tenantResourceId('Microsoft.Management/managementGroups/', parameters('targetManagementGroup'))]" + } + } +```` +## Deploy using AzOps + +See these [instructions](../../../docs/Deploy/enable-subscription-creation.md) for how to use this template with the AzOps GitHub Actions/DevOps pipeline. + +## Deploy using Azure PowerShell + +````pwsh +New-AzManagementGroupDeployment ` + -Name ` + -Location - ` + -ManagementGroupId ` + -TemplateUri "https://raw.githubusercontent.com/Azure/Enterprise-Scale/main/examples/landing-zones/tagged-subscription/taggedSubscription.json" +```` + +## Deploy using Azure CLI + +````cli +az deployment mg create \ + --name \ + --location \ + --management-group-id \ + --template-uri "https://raw.githubusercontent.com/Azure/Enterprise-Scale/main/examples/landing-zones/tagged-subscription/taggedSubscription.json" \ No newline at end of file diff --git a/examples/landing-zones/tagged-subscription/taggedSubscription.json b/examples/landing-zones/tagged-subscription/taggedSubscription.json new file mode 100644 index 00000000..6395e4bf --- /dev/null +++ b/examples/landing-zones/tagged-subscription/taggedSubscription.json @@ -0,0 +1,184 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "subscriptionAliasName": { + "type": "string", + "metadata": { + "description": "Provide alias (and displayName) for the subscription" + } + }, + "targetManagementGroup": { + "type": "string", + "metadata": { + "details": "Select targeted management group that the subscription will land into" + } + }, + "billingAccountId": { + "type": "string", + "metadata": { + "description": "Provide the resourceId for the enrollment account or MCA" + } + }, + "tagValue": { + "type": "string", + "metadata": { + "description": "Provide a value of the required application key tag." + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2019-10-01", + "name": "[concat('create-', parameters('subscriptionAliasName'))]", + "scope": "[concat('Microsoft.Management/managementGroups/', parameters('targetManagementGroup'))]", + "location": "[deployment().location]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + // Sharing parameter values from outer to inner execution scope + "subAliasName": { + "value": "[parameters('subscriptionAliasName')]" + }, + "mgmtGroupId": { + "value": "[parameters('targetManagementGroup')]" + }, + "billingId": { + "value": "[parameters('billingAccountId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // parameters for inner scope + "subAliasName": { + "type": "string" + }, + "mgmtGroupId": { + "type": "string" + }, + "billingId": { + "type": "string" + } + }, + "resources": [ + { + "scope": "/", // routing the request to tenant root + "name": "[parameters('subAliasName')]", + "type": "Microsoft.Subscription/aliases", + "apiVersion": "2020-09-01", + "properties": { + "workLoad": "Production", + "displayName": "[parameters('subAliasName')]", + "billingScope": "[parameters('billingId')]", + "managementGroupId": "[tenantResourceId('Microsoft.Management/managementGroups/', parameters('mgmtGroupId'))]" + } + } + ], + "outputs": { + // Referencing the guid generated for the subscription to be used in subsequent (optional) deployments to this subscription + "subscriptionId": { + "type": "string", + "value": "[reference(parameters('subAliasName')).subscriptionId]" + } + } + } + } + }, + { + // Creating deployment to invoke policyAssignment of newly created subscription + "scope": "[concat('Microsoft.Management/managementGroups/', parameters('targetManagementGroup'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2019-08-01", + "name": "[concat('tag-', parameters('subscriptionAliasName'))]", + "location": "[deployment().location]", + "dependsOn": [ + "[concat('Microsoft.Resources/deployments/', 'create-', parameters('subscriptionAliasName'))]" + ], + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + // Value coming from the previous deployment's output inner scope to be used to target subscription deployments + "targetSubscriptionId": { + "value": "[reference(concat('create-', parameters('subscriptionAliasName'))).outputs.subscriptionId.value]" + }, + "tagInputValue": { + "value": "[parameters('tagValue')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // parameters for inner scope + "targetSubscriptionId": { + "type": "string" + }, + "tagInputValue": { + "type": "string" + } + }, + "resources": [ + { + // subscription scoped deployment to make policy assignment + "type": "Microsoft.Resources/deployments", + "apiVersion": "2019-08-01", + "name": "subscription-dep", + "location": "[deployment().location]", + "subscriptionId": "[parameters('targetSubscriptionId')]", + "properties": { + "mode": "Incremental", + "expressionEvaluationOptions": { + "scope": "inner" + }, + "parameters": { + "tagInputValueInner": { + "value": "[parameters('tagInputValue')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // parameters for inner scope + "tagInputValueInner": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Resources/tags", + "apiVersion": "2020-06-01", + "name": "default", + "properties": { + "tags": { + "appKey": "[parameters('tagInputValueInner')]" + } + } + } + ] + } + } + } + ] + } + } + } + ], + "outputs": { + "subscriptionIdOuter": { + "type": "string", + "value": "[reference(concat('create-', parameters('subscriptionAliasName'))).outputs.subscriptionId.value]" + } + } +} \ No newline at end of file diff --git a/examples/management-groups/README.md b/examples/management-groups/README.md new file mode 100644 index 00000000..ee54a5fd --- /dev/null +++ b/examples/management-groups/README.md @@ -0,0 +1,62 @@ +# ARM templates for Management Groups + +This folder contains example ARM templates for organizations to create new subscriptions into management groups. + +## Recommendations + +In order to create subscriptions at scale using ARM templates, we strongly recommends the following settings, convention and authoring styles. + +* Always use the “scope” resource property to route the request to the tenant root + + As management groups should be placed into a management group, we recommend to invoke the template deployment at the targeted management group, and use the "scope" property to interact with the tenant RP directly vs invoking the deployment at the tenant root (/). From a security perspective, this reduces need for having blast-radius permissions in the Azure tenant. + + The example resource below shows how to use the "scope" property and route the request to the tenant root. + + ````json + { + "scope": "/", // routing the request to the tenant root + "type": "Microsoft.Management/managementGroups", + "apiVersion": "2020-05-01", + "name": "[parameters('mgmtGroupName')]", + ... + } + ```` + +* Always specify the parent management group to avoid creating management group directory under the root group + + Management groups can be created into the existing hierarchy, and default behavior when not specifying the parentId will create the management group under the root directly. To avoid this, always provide the parentId as an input parameter that should have the same value as the management group id used when invoking the deployment. + + The example below shows the "parent object" property to determine the parent and child relationship, using a conditon to avoid failure if not provided. + + ````json + "properties": { + "displayName": "[parameters('mgmtGroupName')]", + "details": { + "parent": { + "id": "[if(not(empty(parameters('parentMgmtGroupId'))), concat('/providers/Microsoft.Management/managementGroups/', parameters('parentMgmtGroupId')), json('null'))]" + } + } + ```` + +* Always use the same name for the management group id and the display name + + We recommend to use the same name for the management group id and the management group display name and also to ensure uniqueness in your tenant + + The example below shows that the 'mgmtGroupName' parameter is used for both the management group id name, and the displayName of the management group. + + ````json + + { + "scope": "/", // routing the request to the tenant root + "type": "Microsoft.Management/managementGroups", + "apiVersion": "2020-05-01", + "name": "[parameters('mgmtGroupName')]", + "properties": { + "displayName": "[parameters('mgmtGroupName')]", + ```` + +* Enable the Management Group hierarchy settings + + It is recommend to enable the management group hierarchy settings in your Azure tenant to ensure that role-based-access-control is required to create, update, and delete management groups. + + ![management group hierarchy settings](../../docs/media/mg-hierarchy-settings.png) diff --git a/examples/management-groups/child-group/child-group.json b/examples/management-groups/child-group/child-group.json new file mode 100644 index 00000000..b249cf55 --- /dev/null +++ b/examples/management-groups/child-group/child-group.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "mgmtGroupName": { + "type": "string", + "metadata": { + "description": "Provide a unique name for the management group." + } + }, + "parentMgmtGroupId": { + "type": "string", + "metadata": { + "description": "Provide the name of the management group where you are invoking the deployment." + } + } + }, + "resources": [ + { + "scope": "/", // routing the request to the tenant root + "type": "Microsoft.Management/managementGroups", + "apiVersion": "2020-05-01", + "name": "[parameters('mgmtGroupName')]", + "properties": { + "displayName": "[parameters('mgmtGroupName')]", + "details": { + "parent": { + "id": "[if(not(empty(parameters('parentMgmtGroupId'))), concat('/providers/Microsoft.Management/managementGroups/', parameters('parentMgmtGroupId')), json('null'))]" + } + } + } + } + ] +} \ No newline at end of file diff --git a/examples/policies/naming-convention/README.md b/examples/policies/policy-definition/README.md similarity index 100% rename from examples/policies/naming-convention/README.md rename to examples/policies/policy-definition/README.md diff --git a/examples/policies/naming-convention/naming-convention.json b/examples/policies/policy-definition/policy-definition.json similarity index 100% rename from examples/policies/naming-convention/naming-convention.json rename to examples/policies/policy-definition/policy-definition.json