diff --git a/.vscode/tours/azurechinacloud-base-validation-pipeline.tour b/.vscode/tours/azurechinacloud-base-validation-pipeline.tour index ec3bdc7d8..375edfc6b 100644 --- a/.vscode/tours/azurechinacloud-base-validation-pipeline.tour +++ b/.vscode/tours/azurechinacloud-base-validation-pipeline.tour @@ -107,10 +107,15 @@ "description": "Validate ALZ hub peered spoke orchestration module deployment. Depends on management groups previously created.\r\n\r\n", "line": 182 }, + { + "file": "tests/pipelines/mc-base-unit-validate.yml", + "description": "Validate ALZ subPlacementAll orchestration module deployment. Depends on management groups previously created.\r\n\r\n", + "line": 191 + }, { "file": "tests/pipelines/mc-base-unit-validate.yml", "description": "Job to clean up tenant after deploy -> remove management group structure specific to this PR, delete resources in created subscription. ", - "line": 190 + "line": 193 } ] } \ No newline at end of file diff --git a/.vscode/tours/azurecloud-base-validation-pipeline.tour b/.vscode/tours/azurecloud-base-validation-pipeline.tour index 9814fca8d..4c9ff682e 100644 --- a/.vscode/tours/azurecloud-base-validation-pipeline.tour +++ b/.vscode/tours/azurecloud-base-validation-pipeline.tour @@ -117,10 +117,15 @@ "description": "Validate ALZ hub peered spoke orchestration module deployment. Depends on management groups previously created.\r\n\r\n", "line": 197 }, + { + "file": "tests/pipelines/mc-base-unit-validate.yml", + "description": "Validate ALZ subPlacementAll orchestration module deployment. Depends on management groups previously created.\r\n\r\n", + "line": 210 + }, { "file": "tests/pipelines/base-unit-validate.yml", "description": "Job to clean up tenant after deploy -> remove management group structure specific to this PR, delete resources in created subscription. ", - "line": 205 + "line": 212 } ] } \ No newline at end of file diff --git a/docs/wiki/CustomerUsage.md b/docs/wiki/CustomerUsage.md index dfa6bed65..87d8e8eea 100644 --- a/docs/wiki/CustomerUsage.md +++ b/docs/wiki/CustomerUsage.md @@ -24,27 +24,30 @@ module modCustomerUsageAttribution '../../CRML/customerUsageAttribution/cuaIdTen params: {} } ``` + ## Module PID Value Mapping -The following are the unique ID's (also known as PIDs) used in each of the modules. - -| Module Name | PID | -| ------------------------------ | ------------------------------------ | -| customRoleDefinitions | 032d0904-3d50-45ef-a6c1-baa9d82e23ff | -| getManagementGroupName | cff0ca56-5d8c-4594-bf79-5c046809b017 | -| hubNetworking | 2686e846-5fdc-4d4f-b533-16dcb09d6e6c | -| logging | f8087c67-cc41-46b2-994d-66e4b661860d | -| managementGroups | 9b7965a0-d77c-41d6-85ef-ec3dfea4845b | -| policy-definitions | 2b136786-9881-412e-84ba-f4c2822e1ac9 | -| policy-assignments | 78001e36-9738-429c-a343-45cc84e8a527 | -| alzDefaultPolicyAssignments | 98cef979-5a6b-403b-83c7-10c8f04ac9a2 | -| publicIp | 3f85b84c-6bad-4c42-86bf-11c233241c22 | -| resourceGroup | b6718c54-b49e-4748-a466-88e3d7c789c8 | -| roleAssignments | 59c2ac61-cd36-413b-b999-86a3e0d958fb | -| spokeNetworking | 0c428583-f2a1-4448-975c-2d6262fd193a | -| subscriptionPlacement | 3dfa9e81-f0cf-4b25-858e-167937fd380b | -| virtualNetworkPeer | ab8e3b12-b0fa-40aa-8630-e3f7699e2142 | -| vwanConnectivity | 7f94f23b-7a59-4a5c-9a8d-2a253a566f61 | -| vnetPeeringVwan | 7b5e6db2-1e8c-4b01-8eee-e1830073a63d | -| privateDnsZones | 981733dd-3195-4fda-a4ee-605ab959edb6 | -| hubSpoke - Orchestration | 50ad3b1a-f72c-4de4-8293-8a6399991beb | -| hubPeeredSpoke - Orchestration | 8ea6f19a-d698-4c00-9afb-5c92d4766fd2 | + +The following are the unique ID's (also known as PIDs) used in each of the modules: + +| Module Name | PID | +| ------------------------------- | ------------------------------------ | +| customRoleDefinitions | 032d0904-3d50-45ef-a6c1-baa9d82e23ff | +| getManagementGroupName | cff0ca56-5d8c-4594-bf79-5c046809b017 | +| hubNetworking | 2686e846-5fdc-4d4f-b533-16dcb09d6e6c | +| logging | f8087c67-cc41-46b2-994d-66e4b661860d | +| managementGroups | 9b7965a0-d77c-41d6-85ef-ec3dfea4845b | +| policy-definitions | 2b136786-9881-412e-84ba-f4c2822e1ac9 | +| policy-assignments | 78001e36-9738-429c-a343-45cc84e8a527 | +| alzDefaultPolicyAssignments | 98cef979-5a6b-403b-83c7-10c8f04ac9a2 | +| publicIp | 3f85b84c-6bad-4c42-86bf-11c233241c22 | +| resourceGroup | b6718c54-b49e-4748-a466-88e3d7c789c8 | +| roleAssignments | 59c2ac61-cd36-413b-b999-86a3e0d958fb | +| spokeNetworking | 0c428583-f2a1-4448-975c-2d6262fd193a | +| subscriptionPlacement | 3dfa9e81-f0cf-4b25-858e-167937fd380b | +| virtualNetworkPeer | ab8e3b12-b0fa-40aa-8630-e3f7699e2142 | +| vwanConnectivity | 7f94f23b-7a59-4a5c-9a8d-2a253a566f61 | +| vnetPeeringVwan | 7b5e6db2-1e8c-4b01-8eee-e1830073a63d | +| privateDnsZones | 981733dd-3195-4fda-a4ee-605ab959edb6 | +| hubSpoke - Orchestration | 50ad3b1a-f72c-4de4-8293-8a6399991beb | +| hubPeeredSpoke - Orchestration | 8ea6f19a-d698-4c00-9afb-5c92d4766fd2 | +| SubPlacementAll - Orchestration | bb800623-86ff-4ab4-8901-93c2b70967ae | diff --git a/docs/wiki/DeploymentFlow.md b/docs/wiki/DeploymentFlow.md index 20be96ce1..f3e8961b9 100644 --- a/docs/wiki/DeploymentFlow.md +++ b/docs/wiki/DeploymentFlow.md @@ -28,7 +28,7 @@ Modules in this reference implementation must be deployed in the following order | 4 | Logging & Sentinel | Configures a centrally managed Log Analytics Workspace, Automation Account and Sentinel in the `Logging` subscription. | Management Groups & Subscription for Log Analytics and Sentinel. | [infra-as-code/bicep/modules/logging](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/logging) | | 5 | Hub Networking | Azure supports two types of hub-and-spoke design, VNet hub and Virtual WAN hub. Creates resources in the `Connectivity` subscription. | Management Groups, Subscription for Hub Networking. | [See network topology deployment below](#network-topology-deployment) | | 6 | Role Assignments | Creates role assignments using built-in and custom role definitions. | Management Groups & Subscriptions. | [infra-as-code/bicep/modules/roleAssignments](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/roleAssignments) | -| 7 | Subscription Placement | Moves one or more subscriptions to the target management group. | Management Groups & Subscriptions. | [infra-as-code/bicep/modules/subscriptionPlacement](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/subscriptionPlacement) | +| 7 | Subscription Placement | Moves one or more subscriptions (based on IDs) to the target Management Groups in your ALZ hierarchy. | Management Groups & Subscriptions. | [infra-as-code/bicep/orchestration/subPlacementAll](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/orchestration/subPlacementAll) | | 8 | Built-In and Custom Policy Assignments | Creates policy assignments to provide governance at scale. | Management Groups, Log Analytics Workspace & Custom Policy Definitions | [infra-as-code/bicep/modules/policy/assignments/alzDefaults](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/policy/assignments/alzDefaults) | | 9 | Spoke Networking | Creates Spoke networking infrastructure for workloads with Virtual Network Peering (optional) to support Hub & Spoke network topology or Virtual Hub Connection (optional). | Management Groups, Hub Networking & Subscription for spoke networking | [See network topology deployment below](#network-topology-deployment) | @@ -51,9 +51,10 @@ We have some orchestration modules (Bicep files that wrap/call other Bicep modul The current available orchestration modules are listed below: -| Module | Description | Module Documentation | -| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| hubPeeredSpoke | Creates Spoke networking infrastructure for workloads with Virtual Network Peering (optional) to support Hub & Spoke network topology or Virtual Hub Connection (optional). Also can optionally place Subscription in specified Management Group, create VNet Peering in both directions, create UDR and configure a next hop IP for the default route (`0.0.0.0/0`) ***Review docs of module for more information.*** | [infra-as-code/bicep/orchestration/hubPeeredSpoke](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/orchestration/hubPeeredSpoke) | +| Module | Description | Module Documentation | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| hubPeeredSpoke | Creates Spoke networking infrastructure for workloads with Virtual Network Peering (optional) to support Hub & Spoke network topology or Virtual Hub Connection (optional). Also can optionally place Subscription in specified Management Group, create VNet Peering in both directions, create UDR and configure a next hop IP for the default route (`0.0.0.0/0`) ***Review docs of module for more information.*** | [infra-as-code/bicep/orchestration/hubPeeredSpoke](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/orchestration/hubPeeredSpoke) | +| subPlacementAll | Moves Subscription IDs that are passed in via the input parameters to the specified Management Group. Useful to have a single module's parameters that are updated over time and can be tracked in git, etc. | [infra-as-code/bicep/orchestration/subPlacementAll](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/orchestration/subPlacementAll) | > Orchestration modules to deliver the entire ALZ deployment in a single Bicep file are on our backlog and being worked on, stay tuned! diff --git a/infra-as-code/bicep/modules/managementGroups/README.md b/infra-as-code/bicep/modules/managementGroups/README.md index 3305e0076..2e5add383 100644 --- a/infra-as-code/bicep/modules/managementGroups/README.md +++ b/infra-as-code/bicep/modules/managementGroups/README.md @@ -1,6 +1,6 @@ # Module: Management Groups -The Management Groups module deploys a management group hierarchy in a customer's tenant under the `Tenant Root Group`. This is accomplished through a tenant-scoped Azure Resource Manager (ARM) deployment. The heirarchy can be modifed by editing `managementGroups.bicep`. The hierarchy created by the deployment is: +The Management Groups module deploys a management group hierarchy in a customer's tenant under the `Tenant Root Group`. This is accomplished through a tenant-scoped Azure Resource Manager (ARM) deployment. The hierarchy can be modified by editing `managementGroups.bicep`. The hierarchy created by the deployment is: - Tenant Root Group - Top Level Management Group (defined by parameter `parTopLevelManagementGroupPrefix`) diff --git a/infra-as-code/bicep/modules/subscriptionPlacement/README.md b/infra-as-code/bicep/modules/subscriptionPlacement/README.md index 12e240804..178f7c4a0 100644 --- a/infra-as-code/bicep/modules/subscriptionPlacement/README.md +++ b/infra-as-code/bicep/modules/subscriptionPlacement/README.md @@ -2,6 +2,8 @@ This module moves one or more subscriptions to be a child of the specified management group. Once the subscription(s) are moved under the management group, Azure Policies assigned to the management group or its parent management group(s) will begin to govern the subscription(s). +> Consider using the `subPlacementAll` orchestration module instead to simplify Subscription placement across your entire Management Group hierarchy in a single module. [infra-as-code/bicep/orchestration/hubPeeredSpoke](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/orchestration/subPlacementAll) + ## Parameters The module requires the following required input parameters. diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/README.md b/infra-as-code/bicep/orchestration/subPlacementAll/README.md new file mode 100644 index 000000000..cf7ebd70a --- /dev/null +++ b/infra-as-code/bicep/orchestration/subPlacementAll/README.md @@ -0,0 +1,140 @@ +# Module: Orchestration - subPlacementAll - Place All Subscriptions Into ALZ Management Group Hierarchy + +This module acts as an orchestration module that helps to define where all Subscriptions should be placed in the ALZ Management Group Hierarchy (this can be deployed via the [`managementGroups.bicep` module](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/managementGroups)), which is also described in the wiki on the [Deployment Flow article](https://github.com/Azure/ALZ-Bicep/wiki/DeploymentFlow). + +Module deploys the following resources: + +- Subscription placement for multiple Subscriptions into the ALZ Management Group hierarchy + +> This module calls the [`subscriptionPlacement.bicep` module](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/subscriptionPlacement) multiple times to move the specified Subscription IDs to the desired Management Groups. If you only want to move a single subscription at a time to a specified Management Group, then you could consider this child module that is called many times in this module. + +## Parameters + +The module requires the following inputs: + + | Parameter | Type | Default | Description | Required | Example | + | --------------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------- | + | parTopLevelManagementGroupPrefix | string | `'alz'` | Prefix for the management group hierarchy | Yes | `'alz` | + | parIntRootMgSubs | array | `[]` | An array of Subscription IDs to place in the Intermediate Root Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parPlatformMgSubs | array | `[]` | An array of Subscription IDs to place in the Platform Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parPlatformManagementMgSubs | array | `[]` | An array of Subscription IDs to place in the (Platform) Management Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parPlatformConnectivityMgSubs | array | `[]` | An array of Subscription IDs to place in the (Platform) Connectivity Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parPlatformIdentityMgSubs | array | `[]` | An array of Subscription IDs to place in the (Platform) Identity Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZonesMgSubs | array | `[]` | An array of Subscription IDs to place in the Landing Zones Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZonesCorpMgSubs | array | `[]` | An array of Subscription IDs to place in the Corp (Landing Zones) Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZonesOnlineMgSubs | array | `[]` | An array of Subscription IDs to place in the Online (Landing Zones) Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZonesConfidentialCorpMgSubs | array | `[]` | An array of Subscription IDs to place in the Confidential Corp (Landing Zones) Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZonesConfidentialOnlineMgSubs | array | `[]` | An array of Subscription IDs to place in the Confidential Online (Landing Zones) Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parLandingZoneMgChildrenSubs | object | `{}` | Dictionary Object to allow additional or different child Management Groups of the Landing Zones Management Group describing the subscriptions which each of them contain. | No | [See below](#parlandingzonemgchildrensubs-input-examples) | + | parDecommissionedMgSubs | array | `[]` | An array of Subscription IDs to place in the Decommissioned Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parSandboxMgSubs | array | `[]` | An array of Subscription IDs to place in the Sandbox Management Group. | No | `['xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy']` | + | parTelemetryOptOut | bool | `false` | Set Parameter to true to Opt-out of deployment telemetry | No | false | + +### `parLandingZoneMgChildrenSubs` Input Examples + +The `parLandingZoneMgChildrenSubs` is only used if you have deployed different Management Groups beneath the Landing Zones Management Group using the `parLandingZoneMgChildren` parameter in the [`managementGroups.bicep` module](https://github.com/Azure/ALZ-Bicep/tree/main/infra-as-code/bicep/modules/managementGroups). + +Below are some examples of how to use this input parameter in both Bicep & JSON formats. + +> **NOTE:** The keys of each object in the dictionary object only need to match the last part of the Management Group ID, as the concatenation of the rest of the Management Group ID is automatically handled in the module. +> For Example: +> Entering `pci` as a key will match the Management Group ID of `alz-landingzones-pci` (`alz` is provided via the `parTopLevelManagementGroupPrefix` parameter). The bicep snippet for this concatenation for the Management Group ID is: `${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}` (`mg` is the reference to the iterator in the loop that the module creates) + +#### Bicep Example + +```bicep +parLandingZoneMgChildrenSubs: { + pci: { + subscriptions: [ + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' + 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' + ] + } + 'another-example': { + subscriptions: [ + 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz' + ] + } +} +``` + +#### JSON Parameter File Input Example + +```json +"parLandingZoneMgChildrenSubs": { + "value": { + "pci": { + "subscriptions": [ + "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" + ] + }, + "another-example": { + "subscriptions": [ + "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz" + ] + } + } +} +``` + +## Outputs + +This module will **not** generate any outputs. + +## Deployment + +This module is intended to be used/called only once in a setup, but is likely to be ran/deployed multiple times of the lifetime of you ALZ environment; for example when a new Landing Zone Subscription is created and needs to be placed into the desired Management Group. + +This however may be done as part of another process, for example upon Subscription vending. For this reason the module will not move/touch any subscriptions that are not declared in its parameters by design. + +> 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 + +```bash +# For Azure global regions +az deployment mg create \ + --template-file infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep \ + --parameters @infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json \ + --location eastus \ + --management-group-id alz +``` + +OR + +```bash +# For Azure China regions +az deployment mg create \ + --template-file infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep \ + --parameters @infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json \ + --location chinaeast2 \ + --management-group-id alz +``` + +### PowerShell + +```powershell +# For Azure global regions +New-AzManagementGroupDeployment ` + -TemplateFile infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep ` + -TemplateParameterFile infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json ` + -Location eastus ` + -ManagementGroupId alz +``` + +OR + +```powershell +# For Azure China regions +New-AzManagementGroupDeployment ` + -TemplateFile infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep ` + -TemplateParameterFile infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json ` + -Location chinaeast2 ` + -ManagementGroupId alz + +``` + +## Bicep Visualizer + +![Bicep Visualizer](media/bicepVisualizer.png "Bicep Visualizer") diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/bicepconfig.json b/infra-as-code/bicep/orchestration/subPlacementAll/bicepconfig.json new file mode 100644 index 000000000..2c0ef2c34 --- /dev/null +++ b/infra-as-code/bicep/orchestration/subPlacementAll/bicepconfig.json @@ -0,0 +1,64 @@ +{ + "analyzers": { + "core": { + "enabled": true, + "verbose": true, + "rules": { + "adminusername-should-not-be-literal": { + "level": "error" + }, + "no-hardcoded-env-urls": { + "level": "error" + }, + "no-unnecessary-dependson": { + "level": "error" + }, + "no-unused-params": { + "level": "error" + }, + "no-unused-vars": { + "level": "error" + }, + "outputs-should-not-contain-secrets": { + "level": "error" + }, + "prefer-interpolation": { + "level": "error" + }, + "secure-parameter-default": { + "level": "error" + }, + "simplify-interpolation": { + "level": "error" + }, + "protect-commandtoexecute-secrets": { + "level": "error" + }, + "use-stable-vm-image": { + "level": "error" + }, + "explicit-values-for-loc-params": { + "level": "error" + }, + "no-hardcoded-location": { + "level": "error" + }, + "no-loc-expr-outside-params": { + "level": "error" + }, + "max-outputs": { + "level": "error" + }, + "max-params": { + "level": "error" + }, + "max-resources": { + "level": "error" + }, + "max-variables": { + "level": "error" + } + } + } + } +} \ No newline at end of file diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/media/bicepVisualizer.png b/infra-as-code/bicep/orchestration/subPlacementAll/media/bicepVisualizer.png new file mode 100644 index 000000000..ed915a129 Binary files /dev/null and b/infra-as-code/bicep/orchestration/subPlacementAll/media/bicepVisualizer.png differ diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json b/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json new file mode 100644 index 000000000..e5af4fe09 --- /dev/null +++ b/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "parTopLevelManagementGroupPrefix": { + "value": "alz" + }, + "parIntRootMgSubs": { + "value": [] + }, + "parPlatformMgSubs": { + "value": [] + }, + "parPlatformManagementMgSubs": { + "value": [] + }, + "parPlatformConnectivityMgSubs": { + "value": [] + }, + "parPlatformIdentityMgSubs": { + "value": [] + }, + "parLandingZonesMgSubs": { + "value": [] + }, + "parLandingZonesCorpMgSubs": { + "value": [] + }, + "parLandingZonesOnlineMgSubs": { + "value": [] + }, + "parLandingZonesConfidentialCorpMgSubs": { + "value": [] + }, + "parLandingZonesConfidentialOnlineMgSubs": { + "value": [] + }, + "parLandingZoneMgChildrenSubs": { + "value": {} + }, + "parDecommissionedMgSubs": { + "value": [] + }, + "parSandboxMgSubs": { + "value": [] + }, + "parTelemetryOptOut": { + "value": false + } + } +} \ No newline at end of file diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.min.json b/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.min.json new file mode 100644 index 000000000..748d16652 --- /dev/null +++ b/infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.min.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "parTopLevelManagementGroupPrefix": { + "value": "alz" + }, + "parPlatformManagementMgSubs": { + "value": [] + }, + "parPlatformConnectivityMgSubs": { + "value": [] + }, + "parPlatformIdentityMgSubs": { + "value": [] + }, + "parLandingZonesCorpMgSubs": { + "value": [] + }, + "parLandingZonesOnlineMgSubs": { + "value": [] + }, + "parDecommissionedMgSubs": { + "value": [] + }, + "parSandboxMgSubs": { + "value": [] + }, + "parTelemetryOptOut": { + "value": false + } + } +} \ No newline at end of file diff --git a/infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep b/infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep new file mode 100644 index 000000000..ba96d6ce6 --- /dev/null +++ b/infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep @@ -0,0 +1,211 @@ +targetScope = 'managementGroup' + +@description('Prefix for the management group hierarchy. This management group will be created as part of the deployment.') +@minLength(2) +@maxLength(10) +param parTopLevelManagementGroupPrefix string = 'alz' + +@description('An array of Subscription IDs to place in the Intermediate Root Management Group.') +param parIntRootMgSubs array = [] + +@description('An array of Subscription IDs to place in the Platform Management Group.') +param parPlatformMgSubs array = [] + +@description('An array of Subscription IDs to place in the (Platform) Management Management Group.') +param parPlatformManagementMgSubs array = [] + +@description('An array of Subscription IDs to place in the (Platform) Connectivity Management Group.') +param parPlatformConnectivityMgSubs array = [] + +@description('An array of Subscription IDs to place in the (Platform) Identity Management Group.') +param parPlatformIdentityMgSubs array = [] + +@description('An array of Subscription IDs to place in the Landing Zones Management Group.') +param parLandingZonesMgSubs array = [] + +@description('An array of Subscription IDs to place in the Corp (Landing Zones) Management Group.') +param parLandingZonesCorpMgSubs array = [] + +@description('An array of Subscription IDs to place in the Online (Landing Zones) Management Group.') +param parLandingZonesOnlineMgSubs array = [] + +@description('An array of Subscription IDs to place in the Confidential Corp (Landing Zones) Management Group.') +param parLandingZonesConfidentialCorpMgSubs array = [] + +@description('An array of Subscription IDs to place in the Confidential Online (Landing Zones) Management Group.') +param parLandingZonesConfidentialOnlineMgSubs array = [] + +@description('Dictionary Object to allow additional or different child Management Groups of the Landing Zones Management Group describing the Subscription IDs which each of them contain.') +param parLandingZoneMgChildrenSubs object = {} + +@description('An array of Subscription IDs to place in the Decommissioned Management Group.') +param parDecommissionedMgSubs array = [] + +@description('An array of Subscription IDs to place in the Sandbox Management Group.') +param parSandboxMgSubs array = [] + +@description('Set Parameter to true to Opt-out of deployment telemetry') +param parTelemetryOptOut bool = false + +var varMgIds = { + intRoot: parTopLevelManagementGroupPrefix + platform: '${parTopLevelManagementGroupPrefix}-platform' + platformManagement: '${parTopLevelManagementGroupPrefix}-platform-management' + platformConnectivity: '${parTopLevelManagementGroupPrefix}-platform-connectivity' + platformIdentity: '${parTopLevelManagementGroupPrefix}-platform-identity' + landingZones: '${parTopLevelManagementGroupPrefix}-landingzones' + landingZonesCorp: '${parTopLevelManagementGroupPrefix}-landingzones-corp' + landingZonesOnline: '${parTopLevelManagementGroupPrefix}-landingzones-online' + landingZonesConfidentialCorp: '${parTopLevelManagementGroupPrefix}-landingzones-confidential-corp' + landingZonesConfidentialOnline: '${parTopLevelManagementGroupPrefix}-landingzones-confidential-online' + decommissioned: '${parTopLevelManagementGroupPrefix}-decommisoned' + sandbox: '${parTopLevelManagementGroupPrefix}-sandbox' +} + +var varDeploymentNames = { + modIntRootMgSubPlacement: take('modIntRootMgSubPlacement-${uniqueString(varMgIds.intRoot, string(length(parIntRootMgSubs)), deployment().name)}', 64) + modPlatformMgSubPlacement: take('modPlatformMgSubPlacement-${uniqueString(varMgIds.platform, string(length(parPlatformMgSubs)), deployment().name)}', 64) + modPlatformManagementMgSubPlacement: take('modPlatformManagementMgSubPlacement-${uniqueString(varMgIds.platformManagement, string(length(parPlatformManagementMgSubs)), deployment().name)}', 64) + modPlatformConnectivityMgSubPlacement: take('modPlatformConnectivityMgSubPlacement-${uniqueString(varMgIds.platformConnectivity, string(length(parPlatformConnectivityMgSubs)), deployment().name)}', 64) + modPlatformIdentityMgSubPlacement: take('modPlatformIdentityMgSubPlacement-${uniqueString(varMgIds.platformIdentity, string(length(parPlatformIdentityMgSubs)), deployment().name)}', 64) + modLandingZonesMgSubPlacement: take('modLandingZonesMgSubPlacement-${uniqueString(varMgIds.landingZones, string(length(parLandingZonesMgSubs)), deployment().name)}', 64) + modLandingZonesCorpMgSubPlacement: take('modLandingZonesCorpMgSubPlacement-${uniqueString(varMgIds.landingZonesCorp, string(length(parLandingZonesCorpMgSubs)), deployment().name)}', 64) + modLandingZonesOnlineMgSubPlacement: take('modLandingZonesOnlineMgSubPlacement-${uniqueString(varMgIds.landingZonesOnline, string(length(parLandingZonesOnlineMgSubs)), deployment().name)}', 64) + modLandingZonesConfidentialCorpMgSubPlacement: take('modLandingZonesConfidentialCorpMgSubPlacement-${uniqueString(varMgIds.landingZonesConfidentialCorp, string(length(parLandingZonesConfidentialCorpMgSubs)), deployment().name)}', 64) + modLandingZonesConfidentialOnlineMgSubPlacement: take('modLandingZonesConfidentialOnlineMgSubPlacement-${uniqueString(varMgIds.landingZonesConfidentialOnline, string(length(parLandingZonesConfidentialOnlineMgSubs)), deployment().name)}', 64) + modDecommissionedMgSubPlacement: take('modDecommissionedMgSubPlacement-${uniqueString(varMgIds.decommissioned, string(length(parDecommissionedMgSubs)), deployment().name)}', 64) + modSandboxMgSubPlacement: take('modSandboxMgSubPlacement-${uniqueString(varMgIds.sandbox, string(length(parSandboxMgSubs)), deployment().name)}', 64) +} + +// Customer Usage Attribution Id +var varCuaid = 'bb800623-86ff-4ab4-8901-93c2b70967ae' + +module modIntRootMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parIntRootMgSubs)) { + name: varDeploymentNames.modIntRootMgSubPlacement + scope: managementGroup(varMgIds.intRoot) + params: { + parTargetManagementGroupId: varMgIds.intRoot + parSubscriptionIds: parIntRootMgSubs + } +} + +// Platform Management Groups +module modPlatformMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parPlatformMgSubs)) { + name: varDeploymentNames.modPlatformMgSubPlacement + scope: managementGroup(varMgIds.platform) + params: { + parTargetManagementGroupId: varMgIds.platform + parSubscriptionIds: parPlatformMgSubs + } +} + +module modPlatformManagementMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parPlatformManagementMgSubs)) { + name: varDeploymentNames.modPlatformManagementMgSubPlacement + scope: managementGroup(varMgIds.platformManagement) + params: { + parTargetManagementGroupId: varMgIds.platformManagement + parSubscriptionIds: parPlatformManagementMgSubs + } +} + +module modplatformConnectivityMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parPlatformConnectivityMgSubs)) { + name: varDeploymentNames.modPlatformConnectivityMgSubPlacement + scope: managementGroup(varMgIds.platformConnectivity) + params: { + parTargetManagementGroupId: varMgIds.platformConnectivity + parSubscriptionIds: parPlatformConnectivityMgSubs + } +} + +module modplatformIdentityMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parPlatformIdentityMgSubs)) { + name: varDeploymentNames.modPlatformIdentityMgSubPlacement + scope: managementGroup(varMgIds.platformIdentity) + params: { + parTargetManagementGroupId: varMgIds.platformIdentity + parSubscriptionIds: parPlatformIdentityMgSubs + } +} + +// Landing Zone Management Groups +module modLandingZonesMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parLandingZonesMgSubs)) { + name: varDeploymentNames.modLandingZonesMgSubPlacement + scope: managementGroup(varMgIds.landingZones) + params: { + parTargetManagementGroupId: varMgIds.landingZones + parSubscriptionIds: parLandingZonesMgSubs + } +} + +module modLandingZonesCorpMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parLandingZonesCorpMgSubs)) { + name: varDeploymentNames.modLandingZonesCorpMgSubPlacement + scope: managementGroup(varMgIds.landingZonesCorp) + params: { + parTargetManagementGroupId: varMgIds.landingZonesCorp + parSubscriptionIds: parLandingZonesCorpMgSubs + } +} + +module modLandingZonesOnlineMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parLandingZonesOnlineMgSubs)) { + name: varDeploymentNames.modLandingZonesOnlineMgSubPlacement + scope: managementGroup(varMgIds.landingZonesOnline) + params: { + parTargetManagementGroupId: varMgIds.landingZonesOnline + parSubscriptionIds: parLandingZonesOnlineMgSubs + } +} + +// Confidential Landing Zone Management Groups +module modLandingZonesConfidentialCorpMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parLandingZonesConfidentialCorpMgSubs)) { + name: varDeploymentNames.modLandingZonesConfidentialCorpMgSubPlacement + scope: managementGroup(varMgIds.landingZonesConfidentialCorp) + params: { + parTargetManagementGroupId: varMgIds.landingZonesConfidentialCorp + parSubscriptionIds: parLandingZonesConfidentialCorpMgSubs + } +} + +module modLandingZonesConfidentialOnlineMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parLandingZonesConfidentialOnlineMgSubs)) { + name: varDeploymentNames.modLandingZonesConfidentialOnlineMgSubPlacement + scope: managementGroup(varMgIds.landingZonesConfidentialOnline) + params: { + parTargetManagementGroupId: varMgIds.landingZonesConfidentialOnline + parSubscriptionIds: parLandingZonesConfidentialOnlineMgSubs + } +} + +// Custom Children Landing Zone Management Groups +module modLandingZonesMgChildrenSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = [for mg in items(parLandingZoneMgChildrenSubs): if (!empty(parLandingZoneMgChildrenSubs)) { + name: take('modLandingZonesMgChildrenSubPlacement-${uniqueString(mg.key, string(length(mg.value.subscriptions)), deployment().name)}', 64) + scope: managementGroup('${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}') + params: { + parTargetManagementGroupId: '${parTopLevelManagementGroupPrefix}-landingzones-${mg.key}' + parSubscriptionIds: mg.value.subscriptions + } +}] + +// Decommissioned Management Group +module modDecommissionedMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parDecommissionedMgSubs)) { + name: varDeploymentNames.modDecommissionedMgSubPlacement + scope: managementGroup(varMgIds.decommissioned) + params: { + parTargetManagementGroupId: varMgIds.decommissioned + parSubscriptionIds: parDecommissionedMgSubs + } +} + +// Sandbox Management Group +module modSandboxMgSubPlacement '../../modules/subscriptionPlacement/subscriptionPlacement.bicep' = if (!empty(parSandboxMgSubs)) { + name: varDeploymentNames.modSandboxMgSubPlacement + scope: managementGroup(varMgIds.sandbox) + params: { + parTargetManagementGroupId: varMgIds.sandbox + parSubscriptionIds: parSandboxMgSubs + } +} + +// 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 + name: 'pid-${varCuaid}-${uniqueString(deployment().location)}' + params: {} +} diff --git a/tests/pipelines/base-unit-validate.yml b/tests/pipelines/base-unit-validate.yml index 5077fb48c..907c53f20 100644 --- a/tests/pipelines/base-unit-validate.yml +++ b/tests/pipelines/base-unit-validate.yml @@ -201,6 +201,14 @@ jobs: script: | az deployment mg validate --template-file infra-as-code/bicep/orchestration/hubPeeredSpoke/hubPeeredSpoke.bicep --parameters @infra-as-code/bicep/orchestration/hubPeeredSpoke/parameters/hubPeeredSpoke.parameters.all.json parPeeredVnetSubscriptionId="$(subscriptionId)" parHubVirtualNetworkId="/subscriptions/$(subscriptionId)/resourceGroups/$(ResourceGroupName)/providers/Microsoft.Network/virtualNetworks/alz-hub-$(Location)" parTopLevelManagementGroupPrefix="$(ManagementGroupPrefix)" --location $(Location) --management-group-id $(ManagementGroupPrefix) --name $(ManagementGroupPrefix) + - task: Bash@3 + displayName: Az CLI Validate subPlacementAll orchestration module + name: validate_sub_placement_all + inputs: + targetType: 'inline' + script: | + az deployment mg validate --template-file infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep --parameters @infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json parTopLevelManagementGroupPrefix="$(ManagementGroupPrefix)" parPlatformConnectivityMgSubs='["$(subscriptionId)"]' --location $(Location) --management-group-id $(ManagementGroupPrefix) --name "$(ManagementGroupPrefix)-subPlacement" + - job: bicep_cleanup dependsOn: bicep_validate displayName: Cleanup Bicep Validate Deployment for PR diff --git a/tests/pipelines/bicep-build-to-validate.yml b/tests/pipelines/bicep-build-to-validate.yml index bab71b194..2f5d88530 100644 --- a/tests/pipelines/bicep-build-to-validate.yml +++ b/tests/pipelines/bicep-build-to-validate.yml @@ -32,7 +32,8 @@ jobs: git_diff5=$(git diff --name-only HEAD^ HEAD infra-as-code/bicep/modules/policy/assignments/alzDefaults/alzDefaultPolicyAssignments.bicep) git_diff6=$(git diff --name-only HEAD^ HEAD infra-as-code/bicep/modules/subscriptionPlacement/subscriptionPlacement.bicep) git_diff7=$(git diff --name-only HEAD^ HEAD infra-as-code/bicep/modules/roleAssignments/roleAssignmentManagementGroup.bicep) - if [[ $git_diff1 != '' ]] || [[ $git_diff2 != '' ]] || [[ $git_diff3 != '' ]] || [[ $git_diff4 != '' ]] || [[ $git_diff5 != '' ]] || [[ $git_diff6 != '' ]] || [[ $git_diff7 != '' ]] + git_diff8=$(git diff --name-only HEAD^ HEAD infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep) + if [[ $git_diff1 != '' ]] || [[ $git_diff2 != '' ]] || [[ $git_diff3 != '' ]] || [[ $git_diff4 != '' ]] || [[ $git_diff5 != '' ]] || [[ $git_diff6 != '' ]] || [[ $git_diff7 != '' ]] || [[ $git_diff8 != '' ]] then echo "##vso[task.setvariable variable=gitManagementOutput]setmgmt" fi echo @@ -177,7 +178,7 @@ jobs: inputs: targetType: 'inline' script: | - az deployment mg create --template-file infra-as-code/bicep/modules/subscriptionPlacement/subscriptionPlacement.bicep --parameters @infra-as-code/bicep/modules/subscriptionPlacement/parameters/subscriptionPlacement.parameters.min.json parTargetManagementGroupId=$(ManagementGroupPrefix)-platform-connectivity parSubscriptionIds='["$(subscriptionId)"]' --location $(Location) --management-group-id $(ManagementGroupPrefix) + az deployment mg create --template-file infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep --parameters @infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.min.json parTopLevelManagementGroupPrefix="$(ManagementGroupPrefix)" parPlatformConnectivityMgSubs='["$(subscriptionId)"]' --location $(Location) --management-group-id $(ManagementGroupPrefix) - task: AzurePowerShell@5 displayName: Az PwSh alzDefaultPolicyAssignments for PR diff --git a/tests/pipelines/mc-base-unit-validate.yml b/tests/pipelines/mc-base-unit-validate.yml index 6a83a0e42..025128f43 100644 --- a/tests/pipelines/mc-base-unit-validate.yml +++ b/tests/pipelines/mc-base-unit-validate.yml @@ -182,6 +182,14 @@ jobs: script: | az deployment mg validate --template-file infra-as-code/bicep/orchestration/hubPeeredSpoke/hubPeeredSpoke.bicep --parameters @infra-as-code/bicep/orchestration/hubPeeredSpoke/parameters/hubPeeredSpoke.parameters.all.json parPeeredVnetSubscriptionId="$(subscriptionId)" parHubVirtualNetworkId="/subscriptions/$(subscriptionId)/resourceGroups/$(ResourceGroupName)/providers/Microsoft.Network/virtualNetworks/alz-hub-eastus" parTopLevelManagementGroupPrefix="$(ManagementGroupPrefix)" parLocation=$(Location) --location $(Location) --management-group-id $(ManagementGroupPrefix) --name $(ManagementGroupPrefix) + - task: Bash@3 + displayName: Az CLI Validate subPlacementAll orchestration module + name: validate_sub_placement_all + inputs: + targetType: 'inline' + script: | + az deployment mg validate --template-file infra-as-code/bicep/orchestration/subPlacementAll/subPlacementAll.bicep --parameters @infra-as-code/bicep/orchestration/subPlacementAll/parameters/subPlacementAll.parameters.all.json parTopLevelManagementGroupPrefix="$(ManagementGroupPrefix)" parPlatformConnectivityMgSubs='["$(subscriptionId)"]' --location $(Location) --management-group-id $(ManagementGroupPrefix) --name "$(ManagementGroupPrefix)-subPlacement" + - job: bicep_cleanup dependsOn: bicep_validate displayName: Cleanup Bicep Validate Deployment for PR