From 58ba4f78ae74a16d9be2e115cab1df8e49a39ae9 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Mon, 9 Oct 2023 21:48:01 +0100 Subject: [PATCH] Add new modules for MGs & Subscription Alias with `targetScope = MG` (Non-Breaking) (#651) * add new module * change telem * #650 fixes * add accelerator flex * update api * Sub Updates * typo * doc updates * Generate Parameter Markdowns [jtracey93/48d9ad1a] * remove file not needed --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../Deploy-ALZManagementGroups.ps1 | 38 ++- .../bicep/CRML/subscriptionAlias/README.md | 43 +++- .../subscriptionAliasScopeEscape.bicep.md | 107 ++++++++ .../subscriptionAlias/subscriptionAlias.bicep | 2 +- .../subscriptionAliasScopeEscape.bicep | 48 ++++ .../bicep/modules/managementGroups/README.md | 67 ++++- .../managementGroupsScopeEscape.bicep.md | 155 ++++++++++++ .../managementGroups/managementGroups.bicep | 18 +- .../managementGroupsScopeEscape.bicep | 230 ++++++++++++++++++ 9 files changed, 682 insertions(+), 26 deletions(-) create mode 100644 infra-as-code/bicep/CRML/subscriptionAlias/generateddocs/subscriptionAliasScopeEscape.bicep.md create mode 100644 infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep create mode 100644 infra-as-code/bicep/modules/managementGroups/generateddocs/managementGroupsScopeEscape.bicep.md create mode 100644 infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep diff --git a/accelerator/pipeline-scripts/Deploy-ALZManagementGroups.ps1 b/accelerator/pipeline-scripts/Deploy-ALZManagementGroups.ps1 index dd4ac6f1c..7792349d6 100644 --- a/accelerator/pipeline-scripts/Deploy-ALZManagementGroups.ps1 +++ b/accelerator/pipeline-scripts/Deploy-ALZManagementGroups.ps1 @@ -1,9 +1,12 @@ param ( + [Parameter()] + [String]$NonRootParentManagementGroupId = "$($env:NONROOTPARENTMANAGEMENTGROUPID)", + [Parameter()] [String]$Location = "$($env:LOCATION)", [Parameter()] - [String]$TemplateFile = "upstream-releases\$($env:UPSTREAM_RELEASE_VERSION)\infra-as-code\bicep\modules\managementGroups\managementGroups.bicep", + [String]$TemplateFile = "upstream-releases\$($env:UPSTREAM_RELEASE_VERSION)\infra-as-code\bicep\modules\managementGroups\", [Parameter()] [String]$TemplateParameterFile = "config\custom-parameters\managementGroups.parameters.all.json", @@ -13,13 +16,30 @@ param ( ) # Parameters necessary for deployment -$inputObject = @{ - DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) - Location = $Location - TemplateFile = $TemplateFile - TemplateParameterFile = $TemplateParameterFile - WhatIf = $WhatIfEnabled - Verbose = $true + +if ($NonRootParentManagementGroupId -eq '') { + $inputObject = @{ + DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) + Location = $Location + TemplateFile = $TemplateFile + "managementGroups.bicep" + TemplateParameterFile = $TemplateParameterFile + WhatIf = $WhatIfEnabled + Verbose = $true + } + + New-AzTenantDeployment @inputObject } -New-AzTenantDeployment @inputObject +if ($NonRootParentManagementGroupId -ne '') { + $inputObject = @{ + ManagementGroupId = $NonRootParentManagementGroupId + DeploymentName = 'alz-MGDeployment-{0}' -f ( -join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) + Location = $Location + TemplateFile = $TemplateFile + "managementGroupsScopeEscape.bicep" + TemplateParameterFile = $TemplateParameterFile + WhatIf = $WhatIfEnabled + Verbose = $true + } + + New-AzManagementGroupDeployment @inputObject +} diff --git a/infra-as-code/bicep/CRML/subscriptionAlias/README.md b/infra-as-code/bicep/CRML/subscriptionAlias/README.md index 5586a80aa..3797712ee 100644 --- a/infra-as-code/bicep/CRML/subscriptionAlias/README.md +++ b/infra-as-code/bicep/CRML/subscriptionAlias/README.md @@ -1,6 +1,6 @@ # Module: Subscription Alias -> **IMPORTANT:** We recommend moving to using the [Bicep Subscription Vending Module](https://aka.ms/sub-vending/bicep) instead of this module! +> ⚠️⚠️ **IMPORTANT:** We recommend moving to using the [Bicep Subscription Vending Module](https://aka.ms/sub-vending/bicep) instead of this module! ⚠️⚠️ The Subscription Alias module deploys an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA as documented in [Create Azure subscriptions programmatically](https://learn.microsoft.com/azure/cost-management-billing/manage/programmatically-create-subscription). @@ -10,7 +10,8 @@ The Subscription will be created and placed under the Tenant Root Group, unless ## Parameters -- [Parameters for Azure Commercial Cloud](generateddocs/subscriptionAlias.bicep.md) +- [Parameters for `subscriptionAlias.bicep` Azure Commercial Cloud](generateddocs/subscriptionAlias.bicep.md) +- [Parameters for `subscriptionAliasScopeEscape.bicep` Azure Commercial Cloud](generateddocs/subscriptionAliasScopeEscape.bicep.md) ## Outputs @@ -29,7 +30,8 @@ In this example, the Subscription is created upon an EA Account through a tenant > For the below examples we assume you have downloaded or cloned the Git repo as-is and are in the root of the repository as your selected directory in your terminal of choice. -### Azure CLI +### Azure CLI - `subscriptionAlias.bicep` + ```bash dateYMD=$(date +%Y%m%dT%H%M%S%NZ) @@ -41,7 +43,23 @@ TEMPLATEFILE="infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS ``` -### PowerShell +### Azure CLI - `subscriptionAliasScopeEscape.bicep` + +Use this module if you do not want to grant Tenant Root Management Group Deployment permissions. + +```bash + +dateYMD=$(date +%Y%m%dT%H%M%S%NZ) +NAME="alz-SubscriptionAlias-${dateYMD}" +LOCATION="eastus" +PARAMETERS="@infra-as-code/bicep/CRML/subscriptionAlias/parameters/subscriptionAlias.parameters.all.json" +TEMPLATEFILE="infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep" +MGID="alz" + +az deployment mg create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID +``` + +### PowerShell - `subscriptionAlias.bicep` ```powershell @@ -55,6 +73,23 @@ $inputObject = @{ New-AzTenantDeployment @inputObject ``` +### PowerShell - `subscriptionAliasScopeEscape.bicep` + +Use this module if you do not want to grant Tenant Root Management Group Deployment permissions. + +```powershell + +$inputObject = @{ + DeploymentName = 'alz-SubscriptionAlias-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) + TemplateParameterFile = 'infra-as-code/bicep/CRML/subscriptionAlias/parameters/subscriptionAlias.parameters.all.json' + Location = 'EastUS' + TemplateFile = "infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep" + ManagementGroupId = 'alz' +} + +New-AzManagementGroupDeployment @inputObject +``` + ### Output Screenshot ![Example Deployment Output](media/exampleDeploymentOutput.png "Example Deployment Output") diff --git a/infra-as-code/bicep/CRML/subscriptionAlias/generateddocs/subscriptionAliasScopeEscape.bicep.md b/infra-as-code/bicep/CRML/subscriptionAlias/generateddocs/subscriptionAliasScopeEscape.bicep.md new file mode 100644 index 000000000..fed135d3f --- /dev/null +++ b/infra-as-code/bicep/CRML/subscriptionAlias/generateddocs/subscriptionAliasScopeEscape.bicep.md @@ -0,0 +1,107 @@ +# ALZ Bicep CRML - Subscription Alias Module with Scope Escape + +Module to deploy an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access. + +## Parameters + +Parameter name | Required | Description +-------------- | -------- | ----------- +parSubscriptionName | Yes | Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only. +parSubscriptionBillingScope | Yes | The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in. +parTags | No | Tags you would like to be applied. +parManagementGroupId | No | The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional) +parSubscriptionOwnerId | No | The object ID of a responsible user, Microsoft Entra group or service principal. (Optional) +parSubscriptionOfferType | No | The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production +parTenantId | No | The ID of the tenant. Defaults to = tenant().tenantId + +### parSubscriptionName + +![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square) + +Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only. + +### parSubscriptionBillingScope + +![Parameter Setting](https://img.shields.io/badge/parameter-required-orange?style=flat-square) + +The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in. + +### parTags + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Tags you would like to be applied. + +### parManagementGroupId + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional) + +### parSubscriptionOwnerId + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +The object ID of a responsible user, Microsoft Entra group or service principal. (Optional) + +### parSubscriptionOfferType + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production + +- Default value: `Production` + +- Allowed values: `DevTest`, `Production` + +### parTenantId + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +The ID of the tenant. Defaults to = tenant().tenantId + +- Default value: `[tenant().tenantId]` + +## Outputs + +Name | Type | Description +---- | ---- | ----------- +outSubscriptionName | string | +outSubscriptionId | string | + +## Snippets + +### Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "template": "infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.json" + }, + "parameters": { + "parSubscriptionName": { + "value": "" + }, + "parSubscriptionBillingScope": { + "value": "" + }, + "parTags": { + "value": {} + }, + "parManagementGroupId": { + "value": "" + }, + "parSubscriptionOwnerId": { + "value": "" + }, + "parSubscriptionOfferType": { + "value": "Production" + }, + "parTenantId": { + "value": "[tenant().tenantId]" + } + } +} +``` diff --git a/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep b/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep index 815165259..763415b60 100644 --- a/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep +++ b/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAlias.bicep @@ -42,7 +42,7 @@ resource resSubscription 'Microsoft.Subscription/aliases@2021-10-01' = { properties: { additionalProperties: { tags: parTags - managementGroupId: empty(parManagementGroupId) ? null : managementGroup(parManagementGroupId) + managementGroupId: empty(parManagementGroupId) ? null : '/providers/Microsoft.Management/managementGroups/${parManagementGroupId}' subscriptionOwnerId: empty(parSubscriptionOwnerId) ? null : parSubscriptionOwnerId subscriptionTenantId: parTenantId } diff --git a/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep b/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep new file mode 100644 index 000000000..facb8bbfe --- /dev/null +++ b/infra-as-code/bicep/CRML/subscriptionAlias/subscriptionAliasScopeEscape.bicep @@ -0,0 +1,48 @@ +targetScope = 'managementGroup' + +metadata name = 'ALZ Bicep CRML - Subscription Alias Module with Scope Escape' +metadata description = 'Module to deploy an Azure Subscription into an existing billing scope that can be from an EA, MCA or MPA, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access.' + +@sys.description('Name of the subscription to be created. Will also be used as the alias name. Whilst you can use any name you like we recommend it to be: all lowercase, no spaces, alphanumeric and hyphens only.') +param parSubscriptionName string + +@sys.description('The full resource ID of billing scope associated to the EA, MCA or MPA account you wish to create the subscription in.') +param parSubscriptionBillingScope string + +@sys.description('Tags you would like to be applied.') +param parTags object = {} + +@sys.description('The ID of the existing management group where the subscription will be placed. Also known as its parent management group. (Optional)') +param parManagementGroupId string = '' + +@sys.description('The object ID of a responsible user, Microsoft Entra group or service principal. (Optional)') +param parSubscriptionOwnerId string = '' + +@allowed([ + 'DevTest' + 'Production' +]) +@sys.description('The offer type of the EA, MCA or MPA subscription to be created. Defaults to = Production') +param parSubscriptionOfferType string = 'Production' + +@sys.description('The ID of the tenant. Defaults to = tenant().tenantId') +param parTenantId string = tenant().tenantId + +resource resSubscription 'Microsoft.Subscription/aliases@2021-10-01' = { + scope: tenant() + name: parSubscriptionName + properties: { + additionalProperties: { + tags: parTags + managementGroupId: empty(parManagementGroupId) ? null : '/providers/Microsoft.Management/managementGroups/${parManagementGroupId}' + subscriptionOwnerId: empty(parSubscriptionOwnerId) ? null : parSubscriptionOwnerId + subscriptionTenantId: parTenantId + } + displayName: parSubscriptionName + billingScope: parSubscriptionBillingScope + workload: parSubscriptionOfferType + } +} + +output outSubscriptionName string = resSubscription.name +output outSubscriptionId string = resSubscription.properties.subscriptionId diff --git a/infra-as-code/bicep/modules/managementGroups/README.md b/infra-as-code/bicep/modules/managementGroups/README.md index 0c7210376..d94f1000a 100644 --- a/infra-as-code/bicep/modules/managementGroups/README.md +++ b/infra-as-code/bicep/modules/managementGroups/README.md @@ -16,7 +16,8 @@ The Management Groups module deploys a management group hierarchy in a customer' ## Parameters -- [Link to Parameters](generateddocs/managementGroups.bicep.md) +- [Link to `managementGroup.bicep` Parameters](generateddocs/managementGroups.bicep.md) +- [Link to `managementGroupsScopeEscape.bicep` Parameters](generateddocs/managementGroupsScopeEscape.bicep.md) ### Child Platform & Landing Zone Management Groups Flexibility @@ -141,7 +142,7 @@ In this example, the management groups are created at the `Tenant Root Group` th > For the examples below we assume you have downloaded or cloned the Git repo as-is and are in the root of the repository as your selected directory in your terminal of choice. -### Azure CLI +### Azure CLI - `managementGroups.bicep` ```bash # For Azure global regions @@ -167,7 +168,37 @@ PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementG az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS ``` -### PowerShell +### Azure CLI - `managementGroupsScopeEscape.bicep` + +Use this module if you do not want to grant Tenant Root Management Group Deployment permissions. + +```bash +# For Azure global regions + +dateYMD=$(date +%Y%m%dT%H%M%S%NZ) +NAME="alz-MGDeployment-${dateYMD}" +LOCATION="eastus" +TEMPLATEFILE="infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep" +PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json" +MGID="alz" + +az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID +``` +OR +```bash +# For Azure China regions + +dateYMD=$(date +%Y%m%dT%H%M%S%NZ) +NAME="alz-MGDeployment-${dateYMD}" +LOCATION="chinaeast2" +TEMPLATEFILE="infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep" +PARAMETERS="@infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json" +MGID="alz" + +az deployment tenant create --name ${NAME:0:63} --location $LOCATION --template-file $TEMPLATEFILE --parameters $PARAMETERS --management-group-id $MGID +``` + +### PowerShell - `managementGroups.bicep` ```powershell # For Azure global regions @@ -193,6 +224,36 @@ $inputObject = @{ New-AzTenantDeployment @inputObject ``` +### PowerShell - `managementGroupsScopeEscape.bicep` + +Use this module if you do not want to grant Tenant Root Management Group Deployment permissions. + +```powershell +# For Azure global regions + +$inputObject = @{ + DeploymentName = 'alz-MGDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) + Location = 'EastUS' + TemplateFile = "infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep" + TemplateParameterFile = 'infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json' + ManagementGroupId = 'alz' +} +New-AzManagementGroupDeployment @inputObject +``` +OR +```powershell +# For Azure China regions + +$inputObject = @{ + DeploymentName = 'alz-MGDeployment-{0}' -f (-join (Get-Date -Format 'yyyyMMddTHHMMssffffZ')[0..63]) + Location = 'chinaeast2' + TemplateFile = "infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep" + TemplateParameterFile = 'infra-as-code/bicep/modules/managementGroups/parameters/managementGroups.parameters.all.json' + ManagementGroupId = 'alz' +} +New-AzManagementGroupDeployment @inputObject +``` + ![Example Deployment Output](media/exampleDeploymentOutput.png "Example Deployment Output") ## Bicep Visualizer diff --git a/infra-as-code/bicep/modules/managementGroups/generateddocs/managementGroupsScopeEscape.bicep.md b/infra-as-code/bicep/modules/managementGroups/generateddocs/managementGroupsScopeEscape.bicep.md new file mode 100644 index 000000000..6516e5eb6 --- /dev/null +++ b/infra-as-code/bicep/modules/managementGroups/generateddocs/managementGroupsScopeEscape.bicep.md @@ -0,0 +1,155 @@ +# ALZ Bicep - Management Groups Module with Scope Escape + +ALZ Bicep Module to set up Management Group structure, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access. + +## Parameters + +Parameter name | Required | Description +-------------- | -------- | ----------- +parTopLevelManagementGroupPrefix | No | Prefix used for the management group hierarchy. This management group will be created as part of the deployment. +parTopLevelManagementGroupSuffix | No | Optional suffix for the management group hierarchy. This suffix will be appended to management group names/IDs. Include a preceding dash if required. Example: -suffix +parTopLevelManagementGroupDisplayName | No | Display name for top level management group. This name will be applied to the management group prefix defined in parTopLevelManagementGroupPrefix parameter. +parTopLevelManagementGroupParentId | No | Optional parent for Management Group hierarchy, used as intermediate root Management Group parent, if specified. If empty, default, will deploy beneath Tenant Root Management Group. +parLandingZoneMgAlzDefaultsEnable | No | Deploys Corp & Online Management Groups beneath Landing Zones Management Group if set to true. +parPlatformMgAlzDefaultsEnable | No | Deploys Management, Identity and Connectivity Management Groups beneath Platform Management Group if set to true. +parLandingZoneMgConfidentialEnable | No | Deploys Confidential Corp & Confidential Online Management Groups beneath Landing Zones Management Group if set to true. +parLandingZoneMgChildren | No | Dictionary Object to allow additional or different child Management Groups of Landing Zones Management Group to be deployed. +parPlatformMgChildren | No | Dictionary Object to allow additional or different child Management Groups of Platform Management Group to be deployed. +parTelemetryOptOut | No | Set Parameter to true to Opt-out of deployment telemetry. + +### parTopLevelManagementGroupPrefix + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Prefix used for the management group hierarchy. This management group will be created as part of the deployment. + +- Default value: `alz` + +### parTopLevelManagementGroupSuffix + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Optional suffix for the management group hierarchy. This suffix will be appended to management group names/IDs. Include a preceding dash if required. Example: -suffix + +### parTopLevelManagementGroupDisplayName + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Display name for top level management group. This name will be applied to the management group prefix defined in parTopLevelManagementGroupPrefix parameter. + +- Default value: `Azure Landing Zones` + +### parTopLevelManagementGroupParentId + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Optional parent for Management Group hierarchy, used as intermediate root Management Group parent, if specified. If empty, default, will deploy beneath Tenant Root Management Group. + +### parLandingZoneMgAlzDefaultsEnable + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Deploys Corp & Online Management Groups beneath Landing Zones Management Group if set to true. + +- Default value: `True` + +### parPlatformMgAlzDefaultsEnable + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Deploys Management, Identity and Connectivity Management Groups beneath Platform Management Group if set to true. + +- Default value: `True` + +### parLandingZoneMgConfidentialEnable + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Deploys Confidential Corp & Confidential Online Management Groups beneath Landing Zones Management Group if set to true. + +- Default value: `False` + +### parLandingZoneMgChildren + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Dictionary Object to allow additional or different child Management Groups of Landing Zones Management Group to be deployed. + +### parPlatformMgChildren + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Dictionary Object to allow additional or different child Management Groups of Platform Management Group to be deployed. + +### parTelemetryOptOut + +![Parameter Setting](https://img.shields.io/badge/parameter-optional-green?style=flat-square) + +Set Parameter to true to Opt-out of deployment telemetry. + +- Default value: `False` + +## Outputs + +Name | Type | Description +---- | ---- | ----------- +outTopLevelManagementGroupId | string | +outPlatformManagementGroupId | string | +outPlatformChildrenManagementGroupIds | array | +outLandingZonesManagementGroupId | string | +outLandingZoneChildrenManagementGroupIds | array | +outSandboxManagementGroupId | string | +outDecommissionedManagementGroupId | string | +outTopLevelManagementGroupName | string | +outPlatformManagementGroupName | string | +outPlatformChildrenManagementGroupNames | array | +outLandingZonesManagementGroupName | string | +outLandingZoneChildrenManagementGroupNames | array | +outSandboxManagementGroupName | string | +outDecommissionedManagementGroupName | string | + +## Snippets + +### Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "template": "infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.json" + }, + "parameters": { + "parTopLevelManagementGroupPrefix": { + "value": "alz" + }, + "parTopLevelManagementGroupSuffix": { + "value": "" + }, + "parTopLevelManagementGroupDisplayName": { + "value": "Azure Landing Zones" + }, + "parTopLevelManagementGroupParentId": { + "value": "" + }, + "parLandingZoneMgAlzDefaultsEnable": { + "value": true + }, + "parPlatformMgAlzDefaultsEnable": { + "value": true + }, + "parLandingZoneMgConfidentialEnable": { + "value": false + }, + "parLandingZoneMgChildren": { + "value": {} + }, + "parPlatformMgChildren": { + "value": {} + }, + "parTelemetryOptOut": { + "value": false + } + } +} +``` diff --git a/infra-as-code/bicep/modules/managementGroups/managementGroups.bicep b/infra-as-code/bicep/modules/managementGroups/managementGroups.bicep index f9f966650..07988a5c6 100644 --- a/infra-as-code/bicep/modules/managementGroups/managementGroups.bicep +++ b/infra-as-code/bicep/modules/managementGroups/managementGroups.bicep @@ -102,20 +102,20 @@ var varDecommissionedMg = { var varCuaid = '9b7965a0-d77c-41d6-85ef-ec3dfea4845b' // Level 1 -resource resTopLevelMg 'Microsoft.Management/managementGroups@2021-04-01' = { +resource resTopLevelMg 'Microsoft.Management/managementGroups@2023-04-01' = { name: '${parTopLevelManagementGroupPrefix}${parTopLevelManagementGroupSuffix}' properties: { displayName: parTopLevelManagementGroupDisplayName details: { parent: { - id: empty(parTopLevelManagementGroupParentId) ? '/providers/Microsoft.Management/managementGroups/${tenant().tenantId}' : parTopLevelManagementGroupParentId + id: empty(parTopLevelManagementGroupParentId) ? '/providers/Microsoft.Management/managementGroups/${tenant().tenantId}' : '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupParentId}' } } } } // Level 2 -resource resPlatformMg 'Microsoft.Management/managementGroups@2021-04-01' = { +resource resPlatformMg 'Microsoft.Management/managementGroups@2023-04-01' = { name: varPlatformMg.name properties: { displayName: varPlatformMg.displayName @@ -127,7 +127,7 @@ resource resPlatformMg 'Microsoft.Management/managementGroups@2021-04-01' = { } } -resource resLandingZonesMg 'Microsoft.Management/managementGroups@2021-04-01' = { +resource resLandingZonesMg 'Microsoft.Management/managementGroups@2023-04-01' = { name: varLandingZoneMg.name properties: { displayName: varLandingZoneMg.displayName @@ -139,7 +139,7 @@ resource resLandingZonesMg 'Microsoft.Management/managementGroups@2021-04-01' = } } -resource resSandboxMg 'Microsoft.Management/managementGroups@2021-04-01' = { +resource resSandboxMg 'Microsoft.Management/managementGroups@2023-04-01' = { name: varSandboxMg.name properties: { displayName: varSandboxMg.displayName @@ -151,7 +151,7 @@ resource resSandboxMg 'Microsoft.Management/managementGroups@2021-04-01' = { } } -resource resDecommissionedMg 'Microsoft.Management/managementGroups@2021-04-01' = { +resource resDecommissionedMg 'Microsoft.Management/managementGroups@2023-04-01' = { name: varDecommissionedMg.name properties: { displayName: varDecommissionedMg.displayName @@ -164,7 +164,7 @@ resource resDecommissionedMg 'Microsoft.Management/managementGroups@2021-04-01' } // Level 3 - Child Management Groups under Landing Zones MG -resource resLandingZonesChildMgs 'Microsoft.Management/managementGroups@2021-04-01' = [for mg in items(varLandingZoneMgChildrenUnioned): if (!empty(varLandingZoneMgChildrenUnioned)) { +resource resLandingZonesChildMgs 'Microsoft.Management/managementGroups@2023-04-01' = [for mg in items(varLandingZoneMgChildrenUnioned): if (!empty(varLandingZoneMgChildrenUnioned)) { name: '${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}${parTopLevelManagementGroupSuffix}' properties: { displayName: mg.value.displayName @@ -177,7 +177,7 @@ resource resLandingZonesChildMgs 'Microsoft.Management/managementGroups@2021-04- }] //Level 3 - Child Management Groups under Platform MG -resource resPlatformChildMgs 'Microsoft.Management/managementGroups@2021-04-01' = [for mg in items(varPlatformMgChildrenUnioned): if (!empty(varPlatformMgChildrenUnioned)) { +resource resPlatformChildMgs 'Microsoft.Management/managementGroups@2023-04-01' = [for mg in items(varPlatformMgChildrenUnioned): if (!empty(varPlatformMgChildrenUnioned)) { name: '${parTopLevelManagementGroupPrefix}-platform-${mg.key}${parTopLevelManagementGroupSuffix}' properties: { displayName: mg.value.displayName @@ -203,7 +203,7 @@ output outPlatformManagementGroupId string = resPlatformMg.id output outPlatformChildrenManagementGroupIds array = [for mg in items(varPlatformMgChildrenUnioned): '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupPrefix}-platform-${mg.key}${parTopLevelManagementGroupSuffix}'] output outLandingZonesManagementGroupId string = resLandingZonesMg.id -output outLandingZoneChildrenManagementGroupIds array = [for mg in items(varLandingZoneMgChildrenUnioned): '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}${parTopLevelManagementGroupSuffix}' ] +output outLandingZoneChildrenManagementGroupIds array = [for mg in items(varLandingZoneMgChildrenUnioned): '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}${parTopLevelManagementGroupSuffix}'] output outSandboxManagementGroupId string = resSandboxMg.id diff --git a/infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep b/infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep new file mode 100644 index 000000000..6701b3d01 --- /dev/null +++ b/infra-as-code/bicep/modules/managementGroups/managementGroupsScopeEscape.bicep @@ -0,0 +1,230 @@ +targetScope = 'managementGroup' + +metadata name = 'ALZ Bicep - Management Groups Module with Scope Escape' +metadata description = 'ALZ Bicep Module to set up Management Group structure, using Scope Escaping feature of ARM to allow deployment not requiring tenant root scope access.' + +@sys.description('Prefix used for the management group hierarchy. This management group will be created as part of the deployment.') +@minLength(2) +@maxLength(10) +param parTopLevelManagementGroupPrefix string = 'alz' + +@sys.description('Optional suffix for the management group hierarchy. This suffix will be appended to management group names/IDs. Include a preceding dash if required. Example: -suffix') +@maxLength(10) +param parTopLevelManagementGroupSuffix string = '' + +@sys.description('Display name for top level management group. This name will be applied to the management group prefix defined in parTopLevelManagementGroupPrefix parameter.') +@minLength(2) +param parTopLevelManagementGroupDisplayName string = 'Azure Landing Zones' + +@sys.description('Optional parent for Management Group hierarchy, used as intermediate root Management Group parent, if specified. If empty, default, will deploy beneath Tenant Root Management Group.') +param parTopLevelManagementGroupParentId string = '' + +@sys.description('Deploys Corp & Online Management Groups beneath Landing Zones Management Group if set to true.') +param parLandingZoneMgAlzDefaultsEnable bool = true + +@sys.description('Deploys Management, Identity and Connectivity Management Groups beneath Platform Management Group if set to true.') +param parPlatformMgAlzDefaultsEnable bool = true + +@sys.description('Deploys Confidential Corp & Confidential Online Management Groups beneath Landing Zones Management Group if set to true.') +param parLandingZoneMgConfidentialEnable bool = false + +@sys.description('Dictionary Object to allow additional or different child Management Groups of Landing Zones Management Group to be deployed.') +param parLandingZoneMgChildren object = {} + +@sys.description('Dictionary Object to allow additional or different child Management Groups of Platform Management Group to be deployed.') +param parPlatformMgChildren object = {} + +@sys.description('Set Parameter to true to Opt-out of deployment telemetry.') +param parTelemetryOptOut bool = false + +// Platform and Child Management Groups +var varPlatformMg = { + name: '${parTopLevelManagementGroupPrefix}-platform${parTopLevelManagementGroupSuffix}' + displayName: 'Platform' +} + +// Used if parPlatformMgAlzDefaultsEnable == true +var varPlatformMgChildrenAlzDefault = { + connectivity: { + displayName: 'Connectivity' + } + identity: { + displayName: 'Identity' + } + management: { + displayName: 'Management' + } +} + +// Landing Zones & Child Management Groups +var varLandingZoneMg = { + name: '${parTopLevelManagementGroupPrefix}-landingzones${parTopLevelManagementGroupSuffix}' + displayName: 'Landing Zones' +} + +// Used if parLandingZoneMgAlzDefaultsEnable == true +var varLandingZoneMgChildrenAlzDefault = { + corp: { + displayName: 'Corp' + } + online: { + displayName: 'Online' + } +} + +// Used if parLandingZoneMgConfidentialEnable == true +var varLandingZoneMgChildrenConfidential = { + 'confidential-corp': { + displayName: 'Confidential Corp' + } + 'confidential-online': { + displayName: 'Confidential Online' + } +} + +// Build final onject based on input parameters for child MGs of LZs +var varLandingZoneMgChildrenUnioned = (parLandingZoneMgAlzDefaultsEnable && parLandingZoneMgConfidentialEnable && (!empty(parLandingZoneMgChildren))) ? union(varLandingZoneMgChildrenAlzDefault, varLandingZoneMgChildrenConfidential, parLandingZoneMgChildren) : (parLandingZoneMgAlzDefaultsEnable && parLandingZoneMgConfidentialEnable && (empty(parLandingZoneMgChildren))) ? union(varLandingZoneMgChildrenAlzDefault, varLandingZoneMgChildrenConfidential) : (parLandingZoneMgAlzDefaultsEnable && !parLandingZoneMgConfidentialEnable && (!empty(parLandingZoneMgChildren))) ? union(varLandingZoneMgChildrenAlzDefault, parLandingZoneMgChildren) : (parLandingZoneMgAlzDefaultsEnable && !parLandingZoneMgConfidentialEnable && (empty(parLandingZoneMgChildren))) ? varLandingZoneMgChildrenAlzDefault : (!parLandingZoneMgAlzDefaultsEnable && parLandingZoneMgConfidentialEnable && (!empty(parLandingZoneMgChildren))) ? union(varLandingZoneMgChildrenConfidential, parLandingZoneMgChildren) : (!parLandingZoneMgAlzDefaultsEnable && parLandingZoneMgConfidentialEnable && (empty(parLandingZoneMgChildren))) ? varLandingZoneMgChildrenConfidential : (!parLandingZoneMgAlzDefaultsEnable && !parLandingZoneMgConfidentialEnable && (!empty(parLandingZoneMgChildren))) ? parLandingZoneMgChildren : (!parLandingZoneMgAlzDefaultsEnable && !parLandingZoneMgConfidentialEnable && (empty(parLandingZoneMgChildren))) ? {} : {} +var varPlatformMgChildrenUnioned = (parPlatformMgAlzDefaultsEnable && (!empty(parPlatformMgChildren))) ? union(varPlatformMgChildrenAlzDefault, parPlatformMgChildren) : (parPlatformMgAlzDefaultsEnable && (empty(parPlatformMgChildren))) ? varPlatformMgChildrenAlzDefault : (!parPlatformMgAlzDefaultsEnable && (!empty(parPlatformMgChildren))) ? parPlatformMgChildren : (!parPlatformMgAlzDefaultsEnable && (empty(parPlatformMgChildren))) ? {} : {} + +// Sandbox Management Group +var varSandboxMg = { + name: '${parTopLevelManagementGroupPrefix}-sandbox${parTopLevelManagementGroupSuffix}' + displayName: 'Sandbox' +} + +// Decomissioned Management Group +var varDecommissionedMg = { + name: '${parTopLevelManagementGroupPrefix}-decommissioned${parTopLevelManagementGroupSuffix}' + displayName: 'Decommissioned' +} + +// Customer Usage Attribution Id +var varCuaid = '9b7965a0-d77c-41d6-85ef-ec3dfea4845b' + +// Level 1 +resource resTopLevelMg 'Microsoft.Management/managementGroups@2023-04-01' = { + scope: tenant() + name: '${parTopLevelManagementGroupPrefix}${parTopLevelManagementGroupSuffix}' + properties: { + displayName: parTopLevelManagementGroupDisplayName + details: { + parent: { + id: empty(parTopLevelManagementGroupParentId) ? '/providers/Microsoft.Management/managementGroups/${tenant().tenantId}' : '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupParentId}' + } + } + } +} + +// Level 2 +resource resPlatformMg 'Microsoft.Management/managementGroups@2023-04-01' = { + scope: tenant() + name: varPlatformMg.name + properties: { + displayName: varPlatformMg.displayName + details: { + parent: { + id: resTopLevelMg.id + } + } + } +} + +resource resLandingZonesMg 'Microsoft.Management/managementGroups@2023-04-01' = { + scope: tenant() + name: varLandingZoneMg.name + properties: { + displayName: varLandingZoneMg.displayName + details: { + parent: { + id: resTopLevelMg.id + } + } + } +} + +resource resSandboxMg 'Microsoft.Management/managementGroups@2023-04-01' = { + scope: tenant() + name: varSandboxMg.name + properties: { + displayName: varSandboxMg.displayName + details: { + parent: { + id: resTopLevelMg.id + } + } + } +} + +resource resDecommissionedMg 'Microsoft.Management/managementGroups@2023-04-01' = { + scope: tenant() + name: varDecommissionedMg.name + properties: { + displayName: varDecommissionedMg.displayName + details: { + parent: { + id: resTopLevelMg.id + } + } + } +} + +// Level 3 - Child Management Groups under Landing Zones MG +resource resLandingZonesChildMgs 'Microsoft.Management/managementGroups@2023-04-01' = [for mg in items(varLandingZoneMgChildrenUnioned): if (!empty(varLandingZoneMgChildrenUnioned)) { + scope: tenant() + name: '${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}${parTopLevelManagementGroupSuffix}' + properties: { + displayName: mg.value.displayName + details: { + parent: { + id: resLandingZonesMg.id + } + } + } +}] + +//Level 3 - Child Management Groups under Platform MG +resource resPlatformChildMgs 'Microsoft.Management/managementGroups@2023-04-01' = [for mg in items(varPlatformMgChildrenUnioned): if (!empty(varPlatformMgChildrenUnioned)) { + scope: tenant() + name: '${parTopLevelManagementGroupPrefix}-platform-${mg.key}${parTopLevelManagementGroupSuffix}' + properties: { + displayName: mg.value.displayName + details: { + parent: { + id: resPlatformMg.id + } + } + } +}] + +// Optional Deployment for Customer Usage Attribution +module modCustomerUsageAttribution '../../CRML/customerUsageAttribution/cuaIdManagementGroup.bicep' = if (!parTelemetryOptOut) { + #disable-next-line no-loc-expr-outside-params //Only to ensure telemetry data is stored in same location as deployment. See https://github.com/Azure/ALZ-Bicep/wiki/FAQ#why-are-some-linter-rules-disabled-via-the-disable-next-line-bicep-function for more information //Only to ensure telemetry data is stored in same location as deployment. See https://github.com/Azure/ALZ-Bicep/wiki/FAQ#why-are-some-linter-rules-disabled-via-the-disable-next-line-bicep-function for more information + name: 'pid-${varCuaid}-${uniqueString(deployment().location)}' + params: {} +} + +// Output Management Group IDs +output outTopLevelManagementGroupId string = resTopLevelMg.id + +output outPlatformManagementGroupId string = resPlatformMg.id +output outPlatformChildrenManagementGroupIds array = [for mg in items(varPlatformMgChildrenUnioned): '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupPrefix}-platform-${mg.key}${parTopLevelManagementGroupSuffix}'] + +output outLandingZonesManagementGroupId string = resLandingZonesMg.id +output outLandingZoneChildrenManagementGroupIds array = [for mg in items(varLandingZoneMgChildrenUnioned): '/providers/Microsoft.Management/managementGroups/${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}${parTopLevelManagementGroupSuffix}' ] + +output outSandboxManagementGroupId string = resSandboxMg.id + +output outDecommissionedManagementGroupId string = resDecommissionedMg.id + +// Output Management Group Names +output outTopLevelManagementGroupName string = resTopLevelMg.name + +output outPlatformManagementGroupName string = resPlatformMg.name +output outPlatformChildrenManagementGroupNames array = [for mg in items(varPlatformMgChildrenUnioned): mg.value.displayName] + +output outLandingZonesManagementGroupName string = resLandingZonesMg.name +output outLandingZoneChildrenManagementGroupNames array = [for mg in items(varLandingZoneMgChildrenUnioned): mg.value.displayName] + +output outSandboxManagementGroupName string = resSandboxMg.name + +output outDecommissionedManagementGroupName string = resDecommissionedMg.name