From e4651c77b98cf18860370c245a9a62a41b4cc826 Mon Sep 17 00:00:00 2001 From: Zhiyan Xu Date: Tue, 15 Oct 2024 12:05:46 -0700 Subject: [PATCH 1/6] Feature to add Sovereign Landing Zone template. --- .../.gitignore | 1 + .../microsoft_cloud_for_sovereignty/README.md | 236 ++++++ .../microsoft_cloud_for_sovereignty/data.tf | 6 + .../microsoft_cloud_for_sovereignty/locals.tf | 437 ++++++++++ .../main.bootstrap.tf | 112 +++ .../main.compliance.tf | 23 + .../main.dashboard.tf | 33 + .../main.platform.tf | 291 +++++++ .../main.policyExemption.tf | 19 + .../main.policyRemediation.tf | 14 + .../outputs.tf | 30 + .../policySetParameterSampleFile.json | 11 + .../templates/default_dashboard.tpl | 791 ++++++++++++++++++ .../terraform.tf | 67 ++ .../variables.tf | 360 ++++++++ 15 files changed, 2431 insertions(+) create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/.gitignore create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/data.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.compliance.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.platform.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyExemption.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyRemediation.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/outputs.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/policy_parameters/policySetParameterSampleFile.json create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/templates/default_dashboard.tpl create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf create mode 100644 templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/.gitignore b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/.gitignore new file mode 100644 index 0000000..907c3ff --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/.gitignore @@ -0,0 +1 @@ +.alzlib \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md new file mode 100644 index 0000000..a63e5fd --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md @@ -0,0 +1,236 @@ +# Azure Landing Zones Accelerator Starter Module for Terraform - Sovereign Landing Zone + +This module is part of the Azure Landing Zones Accelerator solution. It is Sovereign Landing Zone implementation of the Azure Landing Zones Platform Landing Zone for Terraform. + +It deploys the Sovereign Landing Zone (SLZ) with an equivalent compliance posture as to our [Bicep implementation](https://aka.ms/slz/bicep). + +The module deploys the following resources: + +- Management group hierarchy +- Management group scope for confidential computing resources +- Azure Policy definitions and assignments +- Sovereign Baseline Policy Initiatives +- Role definitions +- Management resources, including Log Analytics workspace and Automation account +- Hub virtual network including Azure Bastion and Azure Firewall +- DDOS protection plan +- Private DNS zones + +## Usage + +The module is intended to be used with the [Azure Landing Zones Accelerator](https://aka.ms/alz/accelerator/docs). Head over there to get started and review the microsoft_cloud_for_sovereignty starter module during Phase 2. A copy of the `inputs.yaml` file to use can be found [here](https://aka.ms/slz/terraform/inputs). + +## Inputs.yaml Parameters + +The inputs listed as `Required` are those that must be reviewed and potetially customized, if they are allowed during the public preview. All other values are suitable defaults, but may be changed as needed. + +| Input | Required | Type | Default Value | Description | +| - | -- | --- | ---- | ----- | +| `iac` | Required | String | `"terraform"` | For public preview, only `"terraform"` is supported. | +| `bootstrap` | Required | String | `"alz_local"` | For public preview, only `"alz_local"` is supported. | +| `starter` | Required | String | `"microsoft_cloud_for_sovereignty"` | This value denotes to use the Sovereign Landing Zone. | +| `bootstrap_location` | Required | Location | | For public preview, use the same value as the `default_location`. As of the current release, it is required but not used. | +| `starter_locations` | Required | Locations | | For public preview, use the same value as the `default_location`. As of the current release, it is required but not used. | +| `root_parent_management_group_id` | | MG ID | | The Management Group ID to deploy the SLZ under. By default, it will be deployed at the tenant root group level. | +| `subscription_id_management` | | Sub ID | | This is the UUID value for a previously created management subscription. If left blank, a new subscription will be created. | +| `subscription_id_identity` | | Sub ID | | This is the UUID value for a previously created identity subscription. If left blank, a new subscription will be created. | +| `subscription_id_connectivity` | | Sub ID | | This is the UUID value for a previously created connectivity subscription. If left blank, a new subscription will be created. | +| `target_directory` | | File Path | `""` | Local file path for the resulting Terraform to be deployed to. By default it is created under the current working directory in a directory named `local-output`. | +| `create_bootstrap_resources_in_azure` | Required | Boolean | `false` | For public preview, only `false` is supported. | +| `bootstrap_subscription_id` | | Sub ID | | For public preview, bootstrap is not is supported. | +| `service_name` | | String | slz | For public preview, bootstrap naming is not is supported. | +| `environment_name` | | String | mgmt | For public preview, bootstrap naming is not is supported. | +| `postfix_number` | | Numeric | 1 | For public preview, bootstrap naming is not is supported. | +| `apply_alz_archetypes_via_architecture_definition_template` | | Boolean | `true` | Set to `true` to deploy the default ALZ policy suite. | +| `allowed_locations` | Required | List | | This is a list of Azure regions all workloads running outside of the Confidential Management Group scopes are allowed to be deployed into. | +| `allowed_locations_for_confidential_computing` | Required | List | | This is a list of Azure regions all workloads running inside of the Confidential Management Group scopes are allowed to be deployed into. | +| `az_firewall_policies_enabled` | | Boolean | `true` | Set to `true` to deploy a default Azure Firewall Policy resource if `enable_firewall` is also `true`. | +| `bastion_outbound_ssh_rdp_ports` | | List | `["22", "3389"]` | List of outbound remote access ports to enable on the Azure Bastion NSG if `deploy_bastion` is also `true`. | +| `custom_subnets` | | Map | See `inputs.yaml` for default object. | Map of subnets and their configurations to create within the hub network. | +| `customer` | | String | `"Country/Region"` | Customer name to use when branding the compliance dashboard. | +| `customer_policy_sets` | | Map | See the Custom Compliance section below for details. | Map of customer specified policy initiatives to apply alongside the SLZ. | +| `default_location` | Required | Location | | This is the Azure region to deploy all SLZ resources into. | +| `default_postfix` | | String | | Postfix value to append to all resources. | +| `default_prefix` | Required | String | `mcfs` | Prefix value to append to all resources. | +| `deploy_bastion` | | Boolean | `true` | Set to `true` to deploy Azure Bastion within the hub network. | +| `deploy_ddos_protection` | | Boolean | `true` | Set to `true` to deploy Azure DDoS Protection within the hub network. | +| `deploy_hub_network` | | Boolean | `true` | Set to `true` to deploy the hub network. | +| `deploy_log_analytics_workspace` | | Boolean | `true` | Set to `true` to deploy Azure Log Analytics Workspace. | +| `enable_firewall` | | Boolean | `true` | Set to `true` to deploy Azure Firewall within the hub network. | +| `enable_telemetry` | | Boolean | `true` | Set to `false` to opt out of telemetry tracking. We use telemetry data to understand usage rates to help prioritize future development efforts. | +| `express_route_gateway_config` | | Map | `{name: "noconfigEr"}` | Leave as default to not deploy an ExpressRoute Gateway. See the Network Connectivity section below for details. | +| `hub_network_address_prefix` | | CIDR | "10.20.0.0/16" | This is the CIDR to use for the hub network. | +| `landing_zone_management_group_children` | | Map | | See the Customize Application Landing Zones section below for details. | +| `log_analytics_workspace_retention_in_days` | | Numeric | 365 | Number of days to retain logs in the Log Analytics Workspace. | +| `ms_defender_for_cloud_email_security_contact` | | Email | `security_contact@replaceme.com` | Email address to use for Microsoft Defender for Cloud. | +| `policy_assignment_enforcement_mode` | | String | `Default` | The enforcement mode to use for the Sovereign Baseline Policy initiatives. | +| `policy_effect` | | String | `Deny` | The effect to use for the Sovereign Baseline Policy initiatives, when policies support multiple effects. | +| `policy_exemptions` | | Map | See the Custom Compliance section below for details. | Map of customer specified policy exemptions to use alongside the SLZ. | +| `subscription_billing_scope` | | String | | Only required if you have not provided existing subscription IDs for management, connectivity, and identity. | +| `tags` | | Map | See the Custom Tagging section below for details. | Set of tags to apply to all resources deployed. | +| `use_premium_firewall` | | Boolean | `true` | Set to `true` to deploy Premium SKU of the Azure Firewall if `enable_firewall` is also `true`. | +| `vpn_gateway_config` | | Map | `{name: "noconfigEr"}` | Leave as default to not deploy an VPN Gateway. See the Network Connectivity section below for details. | +| `bootstrap_module_version` | | String | `v4.0.5` | For public preview, only `"v4.0.5"` is supported. | +| `starter_module_version` | | String | `latest` | For public preview, only `"latest"` is supported. | + +## Custom Compliance + +### Custom Policy Sets + +An example of the format for the `customer_policy_sets` map is as follows: + +```yaml +customer_policy_sets: { + assignment1: { + policySetDefinitionId: "/providers/Microsoft.Authorization/policySetDefinitions/d5264498-16f4-418a-b659-fa7ef418175f", + policySetAssignmentName: "FedRAMPHigh", + policySetAssignmentDisplayName: "FedRAMP High", + policySetAssignmentDescription: "FedRAMP High", + policySetManagementGroupAssignmentScope: "/providers/Microsoft.management/managementGroups/", + policyParameterFilePath: "./policy_parameters/policySetParameterSampleFile.json" + } +} +``` + +### Policy Exemptions + +An example of the format for the `policy_exemptions` map is as follows: + +```yaml +policy_exemptions: { + policy_exemption1: { + name: "globalexemption", + display_name: "global", + description: "test", + management_group_id: "/providers/Microsoft.management/managementGroups/", + policy_assignment_id: "/providers/microsoft.management/managementGroups//providers/microsoft.Authorization/policyassignments/enforce-sovereign-global", + policy_definition_reference_ids: ["AllowedLocations"] + } +} +``` + +## Customize Application Landing Zones + +### Landing Zone Management Group Children + +An example of the format for the `landing_zone_management_group_children` map is as follows: + +```yaml +landing_zone_management_group_children: { + child1: { + id: "child1", + display_name: "Landing zone child one" + } +} +``` + +## Custom Tagging + +### Tags + +An example of the format for the `tags` map is as follows: + +```yaml +tags: { + Environment: "Production", + ServiceName: "SLZ" +} +``` + +## Network Connectivity + +### ExpressRoute Gateway Config + +An example of the format for the `express_route_gateway_config` map is as follows: + +```yaml +express_route_gateway_config: { + name: "express_route", + gatewayType: "ExpressRoute", + sku: "ErGw1AZ", + vpnType: "RouteBased", + vpnGatewayGeneration: null, + enableBgp: false, + activeActive: false, + enableBgpRouteTranslationForNat: false, + enableDnsForwarding: false, + asn: 65515, + bgpPeeringAddress: "", + peerWeight: 5, + vpnClientConfiguration: { + vpnAddressSpace: ["10.2.0.0/24"] + } +} +``` + +### VPN Gateway Config + +An example of the format for the `vpn_gateway_config` map is as follows: + +```yaml +vpn_gateway_config: { + name: "vpn_gateway", + gatewayType: "Vpn", + sku: "VpnGw1", + vpnType: "RouteBased", + vpnGatewayGeneration: "Generation1", + enableBgp: false, + activeActive: false, + enableBgpRouteTranslationForNat: false, + enableDnsForwarding: false, + bgpPeeringAddress: "", + asn: 65515, + peerWeight: 5, + vpnClientConfiguration: { + vpnAddressSpace: ["10.2.0.0/24"] + } +} +``` + +## Known Issues + +The following are known issues with the Public Preview release for the SLZ. + +### Multiple Resources Destroyed and Recreated During Second Execution + +Occasionally, terraform will attempt to recreate many resources under a subscription despite no resource configurations being changed. A temporary work around can be done by updating `locals.tf` with the following: + +```terraform +locals { + subscription_id_management = "management_subscription_id" + subscription_id_connectivity = "connectivity_subscription_id" + subscription_id_identity = "identity_subscription_id" +} +``` + +### Multiple Inputs for Location + +The inputs for `bootstrap_location` and `starter_locations` and `default_location` must be identical. In a future release, we will have defaults and overrides for these values. + +### Terraform Plan or Apply Fails After Updating tfvars + +Any updates should be made to the `inputs.yaml` file and the tfvars will be updated upon executing the `Deploy-Accelerator` PowerShell command. + +### Invalid Hub Network Address Prefix or Subnet Address Prefix + +There is no validation done to ensure subnets fall within the hub network CIDR or that subnets do not overlap. These issues will be uncovered during apply. + +### Unable to Build Authorizer for Resource Manager API + +It is necessary to rerun `az login` after creating subscriptions for terraform to pick up that they exist. + +### Unable to Update Address Prefixes + +Updating the address prefix on either the hub network or subnets is not supported at this time. + +### Unable to Change Top Level or Sub Level Management Group Names + +Modifying the Top Level or Sub Level Management Group name is not supported at this time. + +### Tags are Not Applied to All Resources + +Certain resources are not receiving the default tags. This will be addressed in a future release. + +### Default Compliance Score is not 100% + +Certain resources will show as being out of compliance by default. This will be addressed in a future release. diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/data.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/data.tf new file mode 100644 index 0000000..ab4cb81 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/data.tf @@ -0,0 +1,6 @@ +# This allows us to get the tenant id +data "azurerm_client_config" "current" {} + +data "azuread_domains" "default" { + only_initial = true +} diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf new file mode 100644 index 0000000..3ca6a4a --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf @@ -0,0 +1,437 @@ +locals { + subscription_id_management = var.subscription_id_management != "" ? var.subscription_id_management : module.subscription_management_creation[0].subscription_id + subscription_id_connectivity = var.subscription_id_connectivity != "" ? var.subscription_id_connectivity : module.subscription_connectivity_creation[0].subscription_id + subscription_id_identity = var.subscription_id_identity != "" ? var.subscription_id_identity : module.subscription_identity_creation[0].subscription_id +} + +locals { + # Management group timeouts + slz_management_group_timeouts = { + role_definition = { + create = "60m" + } + } + + # Management group retriable errors configuration + slz_management_group_retries = { + role_definitions = { + error_message_regex = ["The specified role definition with ID '.+' does not exist."] + interval = 5 + max_interval_seconds = 30 + } + } +} + +locals { + tenant_id = data.azurerm_client_config.current.tenant_id + root_parent_management_group_id = var.root_parent_management_group_id == "" ? local.tenant_id : var.root_parent_management_group_id + management_group_resource_id_format = "/providers/Microsoft.Management/managementGroups/%s" + landingzones_management_group_id = module.slz_management_groups.management_group_resource_ids["${var.default_prefix}-landingzones${var.default_postfix}"] + + management_management_group_id = "${var.default_prefix}-platform-management${var.default_postfix}" + management_subscription_display_name = "${var.default_prefix}-management${var.default_postfix}" + + connectivity_management_group_id = "${var.default_prefix}-platform-connectivity${var.default_postfix}" + connectivity_subscription_display_name = "${var.default_prefix}-connectivity${var.default_postfix}" + + identity_management_group_id = "${var.default_prefix}-platform-identity${var.default_postfix}" + identity_subscription_display_name = "${var.default_prefix}-identity${var.default_postfix}" + + confidential_corp_management_group_id = format(local.management_group_resource_id_format, "${var.default_prefix}-landingzones-confidential-corp${var.default_postfix}") + confidential_online_management_group_id = format(local.management_group_resource_id_format, "${var.default_prefix}-landingzones-confidential-online${var.default_postfix}") + + architecture_name = "slz" + azure_bastion_public_ip_name = "${var.default_prefix}-bas-${var.default_location}${var.default_postfix}-PublicIP${var.default_postfix}" + azure_bastion_name = "${var.default_prefix}-bas-${var.default_location}${var.default_postfix}" + automation_account_name = "${var.default_prefix}-automation-account-${var.default_location}${var.default_postfix}" + ddos_plan_name = "${var.default_prefix}-ddos-plan${var.default_postfix}" + firewall_policy_name = "${var.default_prefix}-azfwpolicy-${var.default_location}" + firewall_policy_id = var.az_firewall_policies_enabled ? "/subscriptions/${local.subscription_id_connectivity}/resourceGroups/${local.hub_rg_name}/providers/Microsoft.Network/firewallPolicies/${local.firewall_policy_name}" : null + firewall_sku_name = "AZFW_VNet" + gateway_public_ip_name = "${var.default_prefix}-%s-PublicIP${var.default_postfix}" + hub_rg_name = "${var.default_prefix}-rg-hub-network-${var.default_location}${var.default_postfix}" + hub_vnet_name = "${var.default_prefix}-hub-${var.default_location}${var.default_postfix}" + hub_vnet_resource_id = "/subscriptions/${local.subscription_id_connectivity}/resourceGroups/${local.hub_rg_name}/providers/Microsoft.Network/virtualNetworks/${local.hub_vnet_name}" + log_analytics_workspace_name = "${var.default_prefix}-log-analytics-${var.default_location}${var.default_postfix}" + log_analytics_resource_group_name = "${var.default_prefix}-rg-logging-${var.default_location}${var.default_postfix}" + nsg_name = "${var.default_prefix}-nsg-AzureBastionSubnet-${var.default_location}${var.default_postfix}" + route_table_name = "${var.default_prefix}-rt-${var.default_location}${var.default_postfix}" + + # Telemetry partner ID : + partner_id_uuid = "2c12b9d4-df50-4186-bd31-ae6686b633d2" # static uuid generated for SLZ + partner_id = format("%s:%s", local.partner_id_uuid, random_uuid.partner_data_uuid.result) + public_ip_allocation_method = "Static" + public_ip_sku = "Standard" + routing_address_space = ["0.0.0.0/0"] + subnet_id_format = "${local.hub_vnet_resource_id}/subnets/%s" + +} + +locals { + allowed_locations_list = [ + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazilsouth", + "brazilsoutheast", + "brazilus", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "eastasia", + "eastus", + "eastus2", + "eastus2euap", + "eastusstg", + "francecentral", + "francesouth", + "germanynorth", + "germanywestcentral", + "israelcentral", + "italynorth", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "koreacentral", + "koreasouth", + "northcentralus", + "northeurope", + "norwayeast", + "norwaywest", + "polandcentral", + "qatarcentral", + "southafricanorth", + "southafricawest", + "southcentralus", + "southcentralusstg", + "southeastasia", + "southindia", + "swedencentral", + "switzerlandnorth", + "switzerlandwest", + "uaecentral", + "uaenorth", + "uksouth", + "ukwest", + "westcentralus", + "westeurope", + "westindia", + "westus", + "westus2", + "westus3" + ] + + allowed_locations_for_confidential_computing_list = [ + "australiacentral", + "australiacentral2", + "australiaeast", + "australiasoutheast", + "brazilsouth", + "brazilsoutheast", + "brazilus", + "canadacentral", + "canadaeast", + "centralindia", + "centralus", + "centraluseuap", + "eastasia", + "eastus", + "eastus2", + "eastus2euap", + "eastusstg", + "francecentral", + "francesouth", + "germanynorth", + "germanywestcentral", + "israelcentral", + "italynorth", + "japaneast", + "japanwest", + "jioindiacentral", + "jioindiawest", + "koreacentral", + "koreasouth", + "northcentralus", + "northeurope", + "norwayeast", + "norwaywest", + "polandcentral", + "qatarcentral", + "southafricanorth", + "southafricawest", + "southcentralus", + "southcentralusstg", + "southeastasia", + "southindia", + "swedencentral", + "switzerlandnorth", + "switzerlandwest", + "uaecentral", + "uaenorth", + "uksouth", + "ukwest", + "westcentralus", + "westeurope", + "westindia", + "westus", + "westus2", + "westus3" + ] +} + +locals { + route = { + name = "udr-default-azfw" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "10.20.254.4" + } + + ddos_protection_plan_id = var.deploy_ddos_protection ? module.ddos_protection_plan[0].resource.id : null + hubnetworks_subnets = { for k, v in var.custom_subnets : + k => { + address_prefixes = [v.address_prefixes] + name = v.name + networkSecurityGroupId = v.networkSecurityGroupId + routeTableId = v.routeTableId + } if(k != "AzureFirewallSubnet" && var.enable_firewall) || (var.enable_firewall == false) + } + + gateway_config = [var.vpn_gateway_config, var.express_route_gateway_config] + gateway_config_map = { for i, gateway in local.gateway_config : i => gateway if(gateway.name != "noconfigVpn" && gateway.name != "noconfigEr") } + + nsg_rules = { + "security_rule_01" = { + name = "AllowHttpsInbound" + priority = 120 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "Internet" + destination_address_prefix = "*" + } + "security_rule_02" = { + name = "AllowGatewayManagerInbound" + priority = 130 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "GatewayManager" + destination_address_prefix = "*" + } + "security_rule_03" = { + name = "AllowAzureLoadBalancerInbound" + priority = 140 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "AzureLoadBalancer" + destination_address_prefix = "*" + } + "security_rule_04" = { + name = "AllowBastionHostCommunication" + priority = 150 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_ranges = ["8080", "5701"] + source_address_prefix = "VirtualNetwork" + destination_address_prefix = "VirtualNetwork" + } + "security_rule_05" = { + name = "DenyAllInbound" + priority = 4096 + direction = "Inbound" + access = "Deny" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + "security_rule_06" = { + name = "AllowSshRdpOutbound" + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_ranges = var.bastion_outbound_ssh_rdp_ports + source_address_prefix = "*" + destination_address_prefix = "VirtualNetwork" + } + "security_rule__07" = { + name = "AllowAzureCloudOutbound" + priority = 110 + direction = "Outbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "*" + destination_address_prefix = "AzureCloud" + } + "security_rule__08" = { + name = "AllowBastionCommunication" + priority = 120 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_ranges = ["8080", "5701"] + source_address_prefix = "VirtualNetwork" + destination_address_prefix = "VirtualNetwork" + } + "security_rule_09" = { + name = "AllowGetSessionInformation" + priority = 130 + direction = "Outbound" + access = "Allow" + protocol = "*" + source_port_range = "*" + destination_port_range = "80" + source_address_prefix = "*" + destination_address_prefix = "Internet" + } + "security_rule_10" = { + name = "DenyAllOutbound" + priority = 4096 + direction = "Outbound" + access = "Deny" + protocol = "*" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + } + } +} + +locals { + customer_policy_sets = { for k, v in var.customer_policy_sets : k => { + policySetDefinitionId = v.policySetDefinitionId + policySetAssignmentName = v.policySetAssignmentName + policySetAssignmentDisplayName = v.policySetAssignmentDisplayName + policySetAssignmentDescription = v.policySetAssignmentDescription + policySetManagementGroupAssignmentScope = v.policySetManagementGroupAssignmentScope + policyAssignmentParameters = v.policyParameterFilePath == "" ? "" : file(v.policyParameterFilePath) + } + } +} + +locals { + default_policy_exemptions = { + "Confidential-Online-Location-Exemption" = { + name = "Confidential-Online-Location-Exemption" + display_name = "Confidential-Online-Location-Exemption" + description = "Exempt the confidential online management group from the SLZ Global location policies. The confidential management groups have their own location restrictions and this may result in a conflict if both sets are included." + management_group_id = local.confidential_online_management_group_id + policy_assignment_id = "${local.confidential_online_management_group_id}/providers/microsoft.authorization/policyassignments/enforce-sovereign-conf" + policy_definition_reference_ids = ["AllowedLocationsForResourceGroups", "AllowedLocations"] + exemption_category = "Waiver" + } + "Confidential-Corp-Location-Exemption" = { + name = "Confidential-Corp-Location-Exemption" + display_name = "Confidential-Corp-Location-Exemption" + description = "Exempt the confidential corp management group from the SLZ Global Policies location policies. The confidential management groups have their own location restrictions and this may result in a conflict if both sets are included." + management_group_id = local.confidential_corp_management_group_id + policy_assignment_id = "${local.confidential_corp_management_group_id}/providers/microsoft.authorization/policyassignments/enforce-sovereign-conf" + policy_definition_reference_ids = ["AllowedLocationsForResourceGroups", "AllowedLocations"] + exemption_category = "Waiver" + } + } + + custom_policy_exemptions = { for v in var.policy_exemptions : v.name => { + name = v.name + display_name = v.display_name + description = v.description + management_group_id = v.management_group_id + policy_assignment_id = v.policy_assignment_id + policy_definition_reference_ids = v.policy_definition_reference_ids + exemption_category = v.exemption_category + } + } + + policy_exemptions = merge(local.default_policy_exemptions, local.custom_policy_exemptions) + policy_assignment_resource_ids = module.slz_management_groups.policy_assignment_resource_ids + policy_set_definition_resource_ids = module.slz_management_groups.policy_set_definition_resource_ids + policy_set_definition_name = ["deploy-diag-logs", "deploy-mdfc-config-h224"] + + slz_default_policy_values = { + policyEffect = jsonencode({ value = var.policy_effect }) + listOfAllowedLocations = jsonencode({ value = var.allowed_locations }) + allowedLocationsForConfidentialComputing = jsonencode({ value = var.allowed_locations_for_confidential_computing }) + ddos_protection_plan_id = jsonencode({ value = "" }) + ddos_protection_plan_effect = jsonencode({ value = var.deploy_ddos_protection ? "Audit" : "Disabled" }) + emailSecurityContact = jsonencode({ value = var.ms_defender_for_cloud_email_security_contact }) + } +} + +locals { + log_analytics_workspace_solutions = [ + { + "product" : "OMSGallery/AgentHealthAssessment", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/AntiMalware", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/ChangeTracking", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/Security", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/ServiceMap", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/SQLAssessment", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/Updates", + "publisher" : "Microsoft" + }, + { + "product" : "OMSGallery/VMInsights", + "publisher" : "Microsoft" + } + ] +} + +locals { + az_portal_link = "https://portal.azure.com" + management_group_link = "${local.az_portal_link}/#view/Microsoft_Azure_Resources/ManagmentGroupDrilldownMenuBlade/~/overview/tenantId/${local.tenant_id}/mgId/${var.default_prefix}${var.default_postfix}/mgDisplayName/Sovereign%20Landing%20Zone/mgCanAddOrMoveSubscription~/true/mgParentAccessLevel/Owner/defaultMenuItemId/overview/drillDownMode~/true" + management_group_info = "If you want to learn more about your management group, please click the following link.\n\n${local.management_group_link}\n\n" + + signed_in_user = data.azurerm_client_config.current.client_id + dashboard_resource_group_name = "${var.default_prefix}-rg-dashboards-${var.default_location}${var.default_postfix}" + dashboard_name = "${var.default_prefix}-Sovereign-Landing-Zone-Dashboard-${var.default_location}${var.default_postfix}" + dashboard_template_file_path = "${path.root}/templates/default_dashboard.tpl" + template_file_variables = { root_prefix = var.default_prefix, root_postfix = var.default_postfix, customer = "${var.customer}" } + dashboard_display_name = local.dashboard_name + dashboard_tags = null + default_template_file_variables = { name = local.dashboard_name } + all_template_file_variables = merge(local.default_template_file_variables, local.template_file_variables) + template_file_path = "./lib/templates/default_dashboard.tpl" + + domain_name = data.azuread_domains.default.domains.0.domain_name + dashboard_link = "${local.az_portal_link}/#@${local.domain_name}/dashboard/arm/subscriptions/${var.subscription_id_management}/resourceGroups/${local.dashboard_resource_group_name}/providers/Microsoft.Portal/dashboards/${local.dashboard_name}" + dashboard_info = "Now your compliance dashboard is ready for you to get insights. If you want to learn more, please click the following link.\n\n${local.dashboard_link}\n\n" +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf new file mode 100644 index 0000000..803089a --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Deploys the Management Groups and Subscriptions for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +resource "random_uuid" "partner_data_uuid" { +} + +module "slz_management_groups" { + source = "Azure/avm-ptn-alz/azurerm" + version = "0.9.0-beta2" + parent_resource_id = local.root_parent_management_group_id + architecture_name = local.architecture_name + location = var.default_location + enable_telemetry = var.enable_telemetry + policy_default_values = local.slz_default_policy_values + partner_id = local.partner_id + timeouts = local.slz_management_group_timeouts + retries = local.slz_management_group_retries +} + +resource "azurerm_management_group" "child" { + for_each = var.landing_zone_management_group_children + display_name = each.value.displayName + name = each.value.id + parent_management_group_id = local.landingzones_management_group_id + + depends_on = [module.slz_management_groups] +} + +module "subscription_management_creation" { + count = var.subscription_id_management == "" ? 1 : 0 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + + subscription_alias_enabled = true + subscription_billing_scope = var.subscription_billing_scope + subscription_display_name = local.management_subscription_display_name + subscription_alias_name = local.management_subscription_display_name + subscription_workload = "Production" + subscription_management_group_association_enabled = true + subscription_management_group_id = local.management_management_group_id + depends_on = [module.slz_management_groups] +} + +module "subscription_management_move" { + count = var.subscription_id_management == "" ? 0 : 1 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + subscription_display_name = local.management_subscription_display_name + subscription_alias_name = local.management_subscription_display_name + subscription_workload = "Production" + subscription_id = var.subscription_id_management + subscription_management_group_association_enabled = true + subscription_management_group_id = local.management_management_group_id + depends_on = [module.subscription_management_creation] +} + +module "subscription_identity_creation" { + count = var.subscription_id_identity == "" ? 1 : 0 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + subscription_alias_enabled = true + subscription_billing_scope = var.subscription_billing_scope + subscription_display_name = local.identity_subscription_display_name + subscription_alias_name = local.identity_subscription_display_name + subscription_workload = "Production" + subscription_management_group_association_enabled = true + subscription_management_group_id = local.identity_management_group_id + depends_on = [module.slz_management_groups] +} + +module "subscription_identity_move" { + count = var.subscription_id_identity == "" ? 0 : 1 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + subscription_workload = "Production" + subscription_id = var.subscription_id_identity + subscription_display_name = local.identity_subscription_display_name + subscription_alias_name = local.identity_subscription_display_name + subscription_management_group_association_enabled = true + subscription_management_group_id = local.identity_management_group_id + depends_on = [module.subscription_identity_creation] +} + +module "subscription_connectivity_creation" { + count = var.subscription_id_connectivity == "" ? 1 : 0 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + subscription_alias_enabled = true + subscription_billing_scope = var.subscription_billing_scope + subscription_display_name = local.connectivity_subscription_display_name + subscription_alias_name = local.connectivity_subscription_display_name + subscription_workload = "Production" + subscription_management_group_association_enabled = true + subscription_management_group_id = local.connectivity_management_group_id + depends_on = [module.slz_management_groups] +} + +module "subscription_connectivity_move" { + count = var.subscription_id_connectivity == "" ? 0 : 1 + source = "Azure/lz-vending/azurerm//modules/subscription" + version = "~> 4.1.2" + subscription_workload = "Production" + subscription_id = var.subscription_id_connectivity + subscription_display_name = local.connectivity_subscription_display_name + subscription_alias_name = local.connectivity_subscription_display_name + subscription_management_group_association_enabled = true + subscription_management_group_id = local.connectivity_management_group_id + depends_on = [module.subscription_connectivity_creation] +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.compliance.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.compliance.tf new file mode 100644 index 0000000..ea209af --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.compliance.tf @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Deploys the policies for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +resource "azurerm_management_group_policy_assignment" "custom_policy" { + for_each = local.customer_policy_sets + + name = substr(each.value.policySetAssignmentName, 0, 24) + management_group_id = each.value.policySetManagementGroupAssignmentScope + policy_definition_id = each.value.policySetDefinitionId + display_name = each.value.policySetAssignmentDisplayName + description = each.value.policySetAssignmentDescription + parameters = each.value.policyAssignmentParameters + enforce = lower(var.policy_assignment_enforcement_mode) == "default" ? true : false + identity { + type = "SystemAssigned" + } + location = var.default_location + + depends_on = [module.slz_management_groups] +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf new file mode 100644 index 0000000..dc2e1d4 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Deploys the dashboard for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +module "dashboard_rg" { + source = "Azure/avm-res-resources-resourcegroup/azurerm" + version = "0.1.0" + location = var.default_location + name = local.dashboard_resource_group_name + enable_telemetry = var.enable_telemetry + providers = { + azurerm = azurerm.management + } +} + +module "avm_res_portal_dashboard" { + source = "Azure/avm-res-portal-dashboard/azurerm" + version = "0.1.0" + location = var.default_location + name = local.dashboard_name + resource_group_name = module.dashboard_rg.name + template_file_path = local.dashboard_template_file_path + template_file_variables = local.template_file_variables + enable_telemetry = var.enable_telemetry + + depends_on = [module.subscription_management_creation, module.dashboard_rg] + + providers = { + azurerm = azurerm.management + } +} diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.platform.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.platform.tf new file mode 100644 index 0000000..c289431 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.platform.tf @@ -0,0 +1,291 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Deploys platform for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +module "alz_management" { + source = "Azure/avm-ptn-alz-management/azurerm" + version = "0.3.0" + count = var.deploy_log_analytics_workspace ? 1 : 0 + + automation_account_name = local.automation_account_name + location = var.default_location + log_analytics_workspace_name = local.log_analytics_workspace_name + resource_group_name = local.log_analytics_resource_group_name + enable_telemetry = var.enable_telemetry + log_analytics_workspace_retention_in_days = var.log_analytics_workspace_retention_in_days + log_analytics_solution_plans = local.log_analytics_workspace_solutions + tags = var.tags + + providers = { + azurerm = azurerm.management + } + depends_on = [module.subscription_management_creation] +} + +module "hub_rg" { + source = "Azure/avm-res-resources-resourcegroup/azurerm" + version = "0.1.0" + + location = var.default_location + name = local.hub_rg_name + enable_telemetry = var.enable_telemetry + providers = { + azurerm = azurerm.connectivity + } +} + +module "firewall_policy" { + count = var.deploy_hub_network && var.az_firewall_policies_enabled && var.enable_firewall ? 1 : 0 + source = "Azure/avm-res-network-firewallpolicy/azurerm" + version = "0.2.3" + + name = local.firewall_policy_name + location = var.default_location + resource_group_name = module.hub_rg.name + enable_telemetry = var.enable_telemetry + + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hub_rg] +} + +module "hubnetworks" { + source = "Azure/avm-ptn-hubnetworking/azurerm" + version = "0.1.0" + count = var.deploy_hub_network ? 1 : 0 + + hub_virtual_networks = { + hub = { + name = local.hub_vnet_name + address_space = [var.hub_network_address_prefix] + location = var.default_location + resource_group_name = local.hub_rg_name + resource_group_creation_enabled = false + resource_group_lock_enabled = false + mesh_peering_enabled = true + route_table_name = local.route_table_name + routing_address_space = local.routing_address_space + ddos_protection_plan_id = local.ddos_protection_plan_id + firewall = var.enable_firewall ? { + sku_name = local.firewall_sku_name + sku_tier = var.use_premium_firewall ? "Premium" : "Standard" + subnet_address_prefix = var.custom_subnets["AzureFirewallSubnet"].address_prefixes + firewall_policy_id = local.firewall_policy_id + } : null + } + } + + enable_telemetry = var.enable_telemetry + providers = { + azurerm = azurerm.connectivity + } + depends_on = [ + module.hub_rg, + module.firewall_policy + ] +} + +resource "azurerm_subnet" "custom_subnets" { + for_each = var.deploy_hub_network ? local.hubnetworks_subnets : {} + + name = each.value.name + virtual_network_name = local.hub_vnet_name + resource_group_name = local.hub_rg_name + address_prefixes = each.value.address_prefixes + + provider = azurerm.connectivity + depends_on = [module.hubnetworks] +} + +resource "azurerm_subnet_network_security_group_association" "subnets_to_network_security_group" { + for_each = var.deploy_hub_network ? { for k, v in var.custom_subnets : k => v if v.networkSecurityGroupId != "" } : {} + + subnet_id = format(local.subnet_id_format, each.value.name) + network_security_group_id = each.value.networkSecurityGroupId + + provider = azurerm.connectivity + depends_on = [module.hubnetworks, azurerm_subnet.custom_subnets] +} + +resource "azurerm_subnet_route_table_association" "subnets_to_route_table" { + for_each = var.deploy_hub_network ? { for k, v in var.custom_subnets : k => v if v.routeTableId != "" } : {} + + subnet_id = format(local.subnet_id_format, each.value.name) + route_table_id = each.value.routeTableId + + provider = azurerm.connectivity + depends_on = [module.hubnetworks, azurerm_subnet.custom_subnets] +} + +module "gateway_public_ip" { + for_each = var.deploy_hub_network ? local.gateway_config_map : {} + source = "Azure/avm-res-network-publicipaddress/azurerm" + version = "0.1.2" + + allocation_method = local.public_ip_allocation_method + location = var.default_location + name = format(local.gateway_public_ip_name, each.value.name) + resource_group_name = local.hub_rg_name + sku = local.public_ip_sku + zones = lower(each.value.gatewayType) == "vpn" ? [] : [1, 2, 3] + enable_telemetry = var.enable_telemetry + + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hub_rg] +} + +resource "azurerm_virtual_network_gateway" "vnet_gateway" { + for_each = var.deploy_hub_network ? local.gateway_config_map : {} + + resource_group_name = local.hub_rg_name + name = each.value.name + location = var.default_location + tags = var.tags + active_active = each.value.activeActive + enable_bgp = each.value.enableBgp + type = each.value.gatewayType + vpn_type = each.value.vpnType + dns_forwarding_enabled = each.value.enableDnsForwarding + generation = lower(each.value.gatewayType) == "vpn" ? each.value.vpnGatewayGeneration : "None" + sku = each.value.sku + + dynamic "vpn_client_configuration" { + for_each = lower(each.value.gatewayType) == "vpn" ? [each.value.vpnClientConfiguration] : [] + content { + address_space = lookup(vpn_client_configuration.value, "vpnAddressSpace", null) + vpn_client_protocols = lookup(vpn_client_configuration.value, "vpnClientProtocols", null) + vpn_auth_types = lookup(vpn_client_configuration.value, "vpnAuthenticationTypes", null) + aad_tenant = lookup(vpn_client_configuration.value, "aadTenant", null) + aad_audience = lookup(vpn_client_configuration.value, "aadAudience", null) + aad_issuer = lookup(vpn_client_configuration.value, "aadIssuer", null) + radius_server_address = lookup(vpn_client_configuration.value, "radiusServerAddress", null) + radius_server_secret = lookup(vpn_client_configuration.value, "radiusServerSecret", null) + } + } + + ip_configuration { + name = "vnetGatewayConfig" + subnet_id = format(local.subnet_id_format, "GatewaySubnet") + public_ip_address_id = (each.value.name != "noconfigVpn" && each.value != "noconfigEr") ? module.gateway_public_ip[each.key].public_ip_id : "na" + } + + provider = azurerm.connectivity + depends_on = [module.hubnetworks, module.gateway_public_ip, azurerm_subnet.custom_subnets] +} + +module "private_dns_zones" { + count = var.deploy_hub_network ? 1 : 0 + source = "Azure/avm-ptn-network-private-link-private-dns-zones/azurerm" + version = "0.4.0" + + location = var.default_location + resource_group_name = local.hub_rg_name + resource_group_creation_enabled = false + virtual_network_resource_ids_to_link_to = { + "vnet" = { + vnet_resource_id = local.hub_vnet_resource_id + } + } + + enable_telemetry = var.enable_telemetry + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hubnetworks] +} + +module "ddos_protection_plan" { + count = var.deploy_hub_network && var.deploy_ddos_protection ? 1 : 0 + source = "Azure/avm-res-network-ddosprotectionplan/azurerm" + version = "0.2.0" + + resource_group_name = local.hub_rg_name + name = local.ddos_plan_name + location = var.default_location + enable_telemetry = var.enable_telemetry + tags = var.tags + + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hub_rg] +} + +module "nsg" { + count = var.deploy_hub_network && var.deploy_bastion ? 1 : 0 + source = "Azure/avm-res-network-networksecuritygroup/azurerm" + version = "v0.2.0" + + resource_group_name = local.hub_rg_name + name = local.nsg_name + location = var.default_location + security_rules = local.nsg_rules + enable_telemetry = var.enable_telemetry + + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hub_rg] +} + +module "azure_bastion_public_ip" { + count = var.deploy_hub_network && var.deploy_bastion ? 1 : 0 + source = "Azure/avm-res-network-publicipaddress/azurerm" + version = "0.1.2" + + allocation_method = local.public_ip_allocation_method + location = var.default_location + name = local.azure_bastion_public_ip_name + resource_group_name = local.hub_rg_name + sku = local.public_ip_sku + enable_telemetry = var.enable_telemetry + + providers = { + azurerm = azurerm.connectivity + } + depends_on = [module.hub_rg] +} + +module "azure_bastion" { + count = var.deploy_hub_network && var.deploy_bastion ? 1 : 0 + source = "Azure/avm-res-network-bastionhost/azurerm" + version = "0.3.0" + + name = local.azure_bastion_name + resource_group_name = local.hub_rg_name + location = var.default_location + copy_paste_enabled = true + file_copy_enabled = false + sku = "Standard" + ip_configuration = { + name = "IpConf" + subnet_id = format(local.subnet_id_format, "AzureBastionSubnet") + public_ip_address_id = var.deploy_bastion ? module.azure_bastion_public_ip[0].public_ip_id : null + } + ip_connect_enabled = true + scale_units = 4 + shareable_link_enabled = true + tunneling_enabled = true + kerberos_enabled = true + enable_telemetry = var.enable_telemetry + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [module.hub_rg, module.hubnetworks, module.azure_bastion_public_ip, azurerm_subnet.custom_subnets] +} + +resource "azurerm_subnet_network_security_group_association" "nsg_link_bastion_subnet" { + count = var.deploy_hub_network && var.deploy_bastion ? 1 : 0 + subnet_id = format(local.subnet_id_format, "AzureBastionSubnet") + network_security_group_id = module.nsg[0].resource_id + + provider = azurerm.connectivity + depends_on = [azurerm_subnet.custom_subnets, module.nsg] +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyExemption.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyExemption.tf new file mode 100644 index 0000000..6357aea --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyExemption.tf @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Creates policy exemptions for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +resource "azurerm_management_group_policy_exemption" "policy_exemptions" { + for_each = local.policy_exemptions + + name = each.value.name + display_name = each.value.display_name + description = each.value.description + management_group_id = each.value.management_group_id + policy_assignment_id = each.value.policy_assignment_id + policy_definition_reference_ids = each.value.policy_definition_reference_ids + exemption_category = each.value.exemption_category + + depends_on = [module.slz_management_groups, azurerm_management_group_policy_assignment.custom_policy] +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyRemediation.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyRemediation.tf new file mode 100644 index 0000000..becd2cd --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.policyRemediation.tf @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Creates policy remediations for the Sovereign Landing Zone +AUTHOR/S: Cloud for Sovereignty +*/ +resource "azurerm_management_group_policy_remediation" "policy_remediation" { + for_each = { for k, v in local.policy_assignment_resource_ids : k => v if !contains(local.policy_set_definition_name, lower(split("/", k)[1])) } + name = substr(lower(split("/", each.key)[1]), 0, 64) + management_group_id = join("/", slice(split("/", each.value), 0, 5)) + policy_assignment_id = each.value + + depends_on = [module.slz_management_groups, azurerm_management_group_policy_assignment.custom_policy, azurerm_management_group_policy_exemption.policy_exemptions] +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/outputs.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/outputs.tf new file mode 100644 index 0000000..69edac8 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/outputs.tf @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Outputs for the Sovereign Landing Zone Depoloyment +AUTHOR/S: Cloud for Sovereignty +*/ +output "subscription_id_management" { + description = "The identifier of the Management Subscription." + value = local.subscription_id_management +} + +output "subscription_id_connectivity" { + description = "The identifier of the Connectivity Subscription." + value = local.subscription_id_connectivity +} + +output "subscription_id_identity" { + description = "The identifier of the Identity Subscription." + value = local.subscription_id_identity +} + +output "management_group_info" { + description = "The management group information with a link to management group's azure portal." + value = local.management_group_info +} + +output "dashboard_info" { + description = "The dashboard information with a link to portal dashboard." + value = local.dashboard_info +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/policy_parameters/policySetParameterSampleFile.json b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/policy_parameters/policySetParameterSampleFile.json new file mode 100644 index 0000000..4e57e91 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/policy_parameters/policySetParameterSampleFile.json @@ -0,0 +1,11 @@ +{ + "stringParameterName": { + "value": "value" + }, + "objectParameterName": { + "value": {} + }, + "arrayParameterName": { + "value": [] + } +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/templates/default_dashboard.tpl b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/templates/default_dashboard.tpl new file mode 100644 index 0000000..3ebf1c6 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/templates/default_dashboard.tpl @@ -0,0 +1,791 @@ +{ + "lenses": { + "0": { + "order": 0, + "parts": { + "0": { + "metadata": { + "inputs": [], + "settings": { + "content": { + "settings": { + "content": "\r\n", + "markdownSource": 1, + "markdownUri": null, + "subtitle": "${customer}", + "title": "Sovereign landing zone dashboard" + } + } + }, + "type": "Extension/HubsExtension/PartType/MarkdownPart" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 0, + "y": 0 + } + }, + "1": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Overall resources compliance score" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tolower(properties.resourceType), stateWeight = tolong(properties.stateWeight)\r\n| summarize maxStateWeight = max(stateWeight) by resourceId, resourceType\r\n| project resourceId, resourceType,\r\n complianceState = case(\r\n maxStateWeight == 300, \"NonCompliant\",\r\n maxStateWeight == 200, \"Compliant\",\r\n maxStateWeight == 100, \"Conflict\",\r\n maxStateWeight == 50, \"Exempt\",\r\n \"UnknownResource\"\r\n )\r\n| summarize counts = count() by complianceState\r\n| summarize compliantCount = sumif(counts, complianceState == 'Compliant' or complianceState == 'Exempt'), nonCompliantCount = sumif(counts, complianceState == 'Conflict' or complianceState == 'NonCompliant')\r\n| extend totalNum = toint(compliantCount + nonCompliantCount)\r\n| extend compliancePercentageVal = iff(totalNum == 0, todouble(100), 100 * todouble(compliantCount) / totalNum)\r\n| project ['Compliance percentage (includes compliant and exempt)'] = strcat(tostring(round(compliancePercentageVal, 1)), '% (', tostring(compliantCount),' out of ', tostring(totalNum), ')')\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Percent of resources compliant with all policies", + "title": "Overall resources compliance score" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQuerySingleValueTile" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 8, + "y": 0 + } + }, + "2": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Overall data residency compliance score" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tostring(properties.policyDefinitionId), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) == \"so.1 - data residency\"\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tolower(properties.resourceType), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, resourceType\r\n| project resourceId, resourceType, complianceState = iff(max_stateWeight == 300, 'NonCompliant', iff(max_stateWeight == 200, 'Compliant', iff(max_stateWeight == 100 , 'Conflict', iff(max_stateWeight == 50, 'Exempt', 'UnknownResource'))))\r\n| summarize counts = count() by complianceState\r\n| summarize compliantCount = sumif(counts, complianceState == 'Compliant' or complianceState == 'Exempt'), nonCompliantCount = sumif(counts, complianceState == 'Conflict' or complianceState == 'NonCompliant')\r\n| extend totalNum = toint(compliantCount + nonCompliantCount)\r\n| extend compliancePercentageVal = iff(totalNum == 0, todouble(100), 100 * todouble(compliantCount) / totalNum)\r\n| project ['Data residency compliance percentage (includes compliant and exempt)'] = strcat(tostring(round(compliancePercentageVal, 1)), '% (', tostring(compliantCount),' out of ', tostring(totalNum), ')')\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Percent of resources compliant with data residency policies", + "title": "Overall data residency compliance score" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQuerySingleValueTile" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 0, + "y": 2 + } + }, + "3": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Overall confidential compliance score" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n|where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tolower(properties.policyDefinitionId), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) in (\"so.3 - customer-managed keys\", \"so.4 - azure confidential computing\")\r\n| project properties, policyDefinitionId, tostring(parsed_policy_groups)\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tolower(properties.resourceType), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, resourceType\r\n| project resourceId, resourceType, complianceState = iff(max_stateWeight == 300, 'NonCompliant', iff(max_stateWeight == 200, 'Compliant', iff(max_stateWeight == 100 , 'Conflict', iff(max_stateWeight == 50, 'Exempt', 'UnknownResource'))))\r\n| summarize counts = count() by complianceState\r\n| summarize compliantCount = sumif(counts, complianceState == 'Compliant' or complianceState == 'Exempt'), nonCompliantCount = sumif(counts, complianceState == 'Conflict' or complianceState == 'NonCompliant')\r\n| extend totalNum = toint(compliantCount + nonCompliantCount)\r\n| extend compliancePercentageVal = iff(totalNum == 0, todouble(100), 100 * todouble(compliantCount) / totalNum)\r\n| project ['Confidentiality compliance percentage (includes compliant and exempt)'] = strcat(tostring(round(compliancePercentageVal, 1)), '% (', tostring(compliantCount),' out of ', tostring(totalNum), ')')\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Percent of resources compliant with encryption and confidential computing policies", + "title": "Overall confidential compliance score" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQuerySingleValueTile" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 8, + "y": 2 + } + }, + "4": { + "metadata": { + "inputs": [], + "settings": { + "content": { + "settings": { + "content": "", + "markdownSource": 1, + "markdownUri": null, + "subtitle": "", + "title": "Policy compliance" + } + } + }, + "type": "Extension/HubsExtension/PartType/MarkdownPart" + }, + "position": { + "colSpan": 16, + "rowSpan": 1, + "x": 0, + "y": 4 + } + }, + "5": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resource compliance by state" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId\r\n| project resourceId, complianceState = iff(max_stateWeight == 300, 'NonCompliant', iff(max_stateWeight == 200, 'Compliant', iff(max_stateWeight == 100 , 'Conflict', iff(max_stateWeight == 50, 'Exempt', 'Unknown'))))\r\n| summarize counts = count() by complianceState\r\n" + }, + { + "isOptional": true, + "name": "chartType", + "value": 2 + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Hover over bar to see percent of resources in each state", + "title": "Resource compliance by state" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryChartTile" + }, + "position": { + "colSpan": 6, + "rowSpan": 8, + "x": 0, + "y": 5 + } + }, + "6": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resource compliance percentage by subscription" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates'\r\n| extend policyAssignmentScope = tolower(properties.policyAssignmentScope)\r\n| where policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and policyAssignmentScope endswith ''\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId),subscriptionId = tostring(properties.subscriptionId), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, subscriptionId\r\n| join kind=inner (\r\n resourcecontainers\r\n | where type == 'microsoft.resources/subscriptions'\r\n | project subscriptionId, subscriptionName = name\r\n ) on subscriptionId\r\n| summarize counts = count() by subscriptionId, subscriptionName, max_stateWeight\r\n| summarize nonCompliantCount = sumif(counts, max_stateWeight == 300), compliantCount = sumif(counts, max_stateWeight == 200), conflictCount = sumif(counts, max_stateWeight == 100), exemptCount = sumif(counts, max_stateWeight == 50) by subscriptionId, subscriptionName\r\n| extend totalResources = todouble(nonCompliantCount + compliantCount + conflictCount + exemptCount)\r\n| extend totalCompliantResources = todouble(compliantCount + exemptCount)\r\n| extend compliancePercentage = iff(totalResources == 0 or (totalCompliantResources == 0 and nonCompliantCount == 0), todouble(100), 100 * totalCompliantResources / totalResources)\r\n| project subscriptionName, compliancePercentageEx = toint(round(compliancePercentage, 1))\r\n| order by compliancePercentageEx asc\r\n" + }, + { + "isOptional": true, + "name": "chartType", + "value": 1 + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Hover over bar to see subscription name and its compliance percentage", + "title": "Resource compliance percentage by subscription" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryChartTile" + }, + "position": { + "colSpan": 10, + "rowSpan": 4, + "x": 6, + "y": 5 + } + }, + "7": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resource compliance percentage by policy initiative" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policySetInitiative = tostring(properties.policySetDefinitionName), resourceId = tolower(properties.resourceId), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, policySetInitiative\r\n| summarize counts = count() by policySetInitiative, max_stateWeight\r\n| summarize nonCompliantCount = sumif(counts, max_stateWeight == 300), compliantCount = sumif(counts, max_stateWeight == 200), conflictCount = sumif(counts, max_stateWeight == 100), exemptCount = sumif(counts, max_stateWeight == 50) by policySetInitiative\r\n| extend totalResources = todouble(nonCompliantCount + compliantCount + conflictCount + exemptCount)\r\n| extend totalCompliantResources = todouble(compliantCount + exemptCount)\r\n| extend compliancePercentage = iff(totalResources == 0 or (totalCompliantResources == 0 and nonCompliantCount == 0), todouble(100), 100 * totalCompliantResources / totalResources)\r\n| project policySetInitiative, compliancePercentageEx = toint(round(compliancePercentage, 1))\r\n| order by compliancePercentageEx asc\r\n" + }, + { + "isOptional": true, + "name": "chartType", + "value": 1 + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Hover over bar to see policy initiative name and its compliance percentage", + "title": "Resource compliance percentage by policy initiative" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryChartTile" + }, + "position": { + "colSpan": 10, + "rowSpan": 4, + "x": 6, + "y": 9 + } + }, + "8": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resources compliance percentage by policy group name" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tolower(properties.policyDefinitionId), policyGroups = properties.policyDefinitionGroupNames, policySetDefinitionName = tolower(properties.policySetDefinitionName)\r\n| mv-expand parsed_policy_groups = policyGroups\r\n| where parsed_policy_groups hasprefix \"so.\"\r\n| extend parsed_policy_groups = trim('so.',tostring(parsed_policy_groups))\r\n| project properties, policyDefinitionId, parsed_policy_groups\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, tostring(parsed_policy_groups)\r\n| summarize counts = count() by tostring(parsed_policy_groups), max_stateWeight\r\n| summarize nonCompliantCount = sumif(counts, max_stateWeight == 300), compliantCount = sumif(counts, max_stateWeight == 200), conflictCount = sumif(counts, max_stateWeight == 100), exemptCount = sumif(counts, max_stateWeight == 50) by tostring(parsed_policy_groups)\r\n| extend totalResources = todouble(nonCompliantCount + compliantCount + conflictCount + exemptCount)\r\n| extend totalCompliantResources = todouble(compliantCount + exemptCount)\r\n| extend compliancePercentage = iff(totalResources == 0 or (totalCompliantResources == 0 and nonCompliantCount == 0), todouble(100), 100 * totalCompliantResources / totalResources)\r\n| project toupper(parsed_policy_groups), compliancePercentageEx = toint(round(compliancePercentage, 1))\r\n| order by compliancePercentageEx asc\r\n" + }, + { + "isOptional": true, + "name": "chartType", + "value": 1 + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Hover over bar to see policy group name and its compliance percentage", + "title": "Resource compliance percentage by policy group name" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryChartTile" + }, + "position": { + "colSpan": 16, + "rowSpan": 4, + "x": 0, + "y": 13 + } + }, + "9": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Non-Compliant and exempt resources" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| where properties.complianceState in (\"NonCompliant\", \"Exempt\")\r\n| extend complianceState = tostring(properties.complianceState),resourceId = tolower(properties.resourceId), resourceType = tostring(properties.resourceType), policySetDefinitionName = tostring(properties.policySetDefinitionName), subscriptionId = tostring(properties.subscriptionId), policyDefinitionName = tostring(properties.policyDefinitionName)\r\n| distinct resourceId, policySetDefinitionName, complianceState, resourceType, subscriptionId, policyDefinitionName\r\n| join kind=leftouter (\r\n resources\r\n | project resourceId=tolower(id), resourceName=name, resourceGroup\r\n ) on resourceId\r\n| join kind=inner (\r\n resourcecontainers\r\n | where type == 'microsoft.resources/subscriptions'\r\n | project subscriptionId, subscriptionName = name\r\n ) on subscriptionId\r\n|join kind=inner (\r\n PolicyResources\r\n | where type == \"microsoft.authorization/policydefinitions\"\r\n | extend policyName = tostring(properties.displayName)\r\n | project policyName, policyDefinitionName = name\r\n ) on policyDefinitionName\r\n| extend ['Resource name']= iff(resourceName==\"\", subscriptionName, resourceName)\r\n| project ['Compliance state']=complianceState, ['Policy initiative']=policySetDefinitionName,['Policy definition']=policyName, ['Resource type']=resourceType, ['Resource name'] , ['Resource group']=resourceGroup, ['Subscription']=subscriptionName\r\n| order by ['Compliance state'] desc, ['Resource type'], ['Resource name'] asc\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "List of non-compliant and exempt resources for all policies", + "title": "Non-Compliant and exempt resources" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryGridTile" + }, + "position": { + "colSpan": 16, + "rowSpan": 5, + "x": 0, + "y": 17 + } + }, + "10": { + "metadata": { + "inputs": [], + "settings": { + "content": { + "settings": { + "content": "", + "markdownSource": 1, + "markdownUri": null, + "subtitle": "", + "title": "Data residency compliance" + } + } + }, + "type": "Extension/HubsExtension/PartType/MarkdownPart" + }, + "position": { + "colSpan": 16, + "rowSpan": 1, + "x": 0, + "y": 22 + } + }, + "11": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Non-compliant resources by location" + }, + { + "isOptional": true, + "name": "query", + "value": "policyResources\r\n| where type == 'microsoft.policyinsights/policystates'\r\n| extend resourceId = tolower(properties.resourceId), policyAssignmentScope = tolower(properties.policyAssignmentScope), complianceState = tostring(properties.complianceState)\r\n| where policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and policyAssignmentScope endswith '' and complianceState == 'NonCompliant'\r\n| mv-expand parsed_policy_groups = parse_json(tolower(properties.policyDefinitionGroupNames))\r\n| where tostring(parsed_policy_groups) == \"so.1 - data residency\"\r\n| join kind=inner (\r\n resources\r\n | where isnotnull(location)\r\n | project resourceId=tolower(id), resourceName=name, resourceGroup, resourcelocation = location\r\n ) on resourceId\r\n| project resourcelocation, complianceState\r\n| summarize counts = count() by resourcelocation\r\n" + }, + { + "isOptional": true, + "name": "chartType", + "value": 1 + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "These resources are in non-compliant locations per the data residency policy", + "title": "Non-Compliant resources by location" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryChartTile" + }, + "position": { + "colSpan": 5, + "rowSpan": 5, + "x": 0, + "y": 23 + } + }, + "12": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resources exempt from data residency policies" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and tostring(properties.complianceState) == \"Exempt\" and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyAssignmentScope = tolower(properties.policyAssignmentScope), complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tostring(properties.resourceType), subscriptionId = tostring(properties.subscriptionId), policyDefinitionId = tostring(properties.policyDefinitionId), resourceLocation = tolower(properties.resourceLocation), policySetDefinitionName = tostring(properties.policySetDefinitionName), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) == \"so.1 - data residency\"\r\n| join kind=leftouter (\r\n resources\r\n | project resourceId=tolower(id), resourceName=name, resourceGroup\r\n ) on resourceId\r\n| join kind=inner (\r\n resourcecontainers\r\n | where type == 'microsoft.resources/subscriptions'\r\n | project subscriptionId, subscriptionName = name\r\n ) on subscriptionId\r\n| project ['Compliance state']=complianceState, ['Policy initiative']=policySetDefinitionName, ['Resource type']=resourceType, ['Resource name']=resourceName, ['Resource location']=resourceLocation\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "These resources are exempt from data residency policies", + "title": "Resources exempt from data residency policies" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryGridTile" + }, + "position": { + "colSpan": 11, + "rowSpan": 5, + "x": 5, + "y": 23 + } + }, + "13": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resources outside of approved regions" + }, + { + "isOptional": true, + "name": "query", + "value": "policyResources\r\n| where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tostring(properties.resourceType), resourceLocation = tolower(properties.resourceLocation), policySetDefinitionName = tostring(properties.policySetDefinitionName), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| where (complianceState == 'NonCompliant' or complianceState == 'Exempt')\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) == \"so.1 - data residency\"\r\n| join kind=leftouter (\r\n resources\r\n | project resourceId=tolower(id), resourceName=name, resourceGroup\r\n ) on resourceId\r\n| project ['Compliance state']=complianceState, ['Policy initiative']=policySetDefinitionName, ['Resource type']=resourceType, ['Resource name']=resourceName, ['Resource location']=resourceLocation, ['Resource group']=resourceGroup\r\n| order by ['Compliance state'] desc, ['Resource type'], ['Resource name'] asc\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "These are the resources deployed outside of an approved region", + "title": "Resources outside of approved regions" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryGridTile" + }, + "position": { + "colSpan": 16, + "rowSpan": 5, + "x": 0, + "y": 28 + } + }, + "14": { + "metadata": { + "inputs": [], + "settings": { + "content": { + "settings": { + "content": "", + "markdownSource": 1, + "markdownUri": null, + "subtitle": "", + "title": "Confidential computing" + } + } + }, + "type": "Extension/HubsExtension/PartType/MarkdownPart" + }, + "position": { + "colSpan": 16, + "rowSpan": 1, + "x": 0, + "y": 33 + } + }, + "15": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resource compliance score for customer-managed keys policies" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n|where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tolower(properties.policyDefinitionId), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) == \"dashboard-storage security\"\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tolower(properties.resourceType), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, resourceType\r\n| project resourceId, resourceType, complianceState = iff(max_stateWeight == 300, 'NonCompliant', iff(max_stateWeight == 200, 'Compliant', iff(max_stateWeight == 100 , 'Conflict', iff(max_stateWeight == 50, 'Exempt', 'UnknownResource'))))\r\n| summarize counts = count() by complianceState\r\n| summarize compliantCount = sumif(counts, complianceState == 'Compliant' or complianceState == 'Exempt'), nonCompliantCount = sumif(counts, complianceState == 'Conflict' or complianceState == 'NonCompliant')\r\n| extend totalNum = toint(compliantCount + nonCompliantCount)\r\n| extend compliancePercentageVal = iff(totalNum == 0, todouble(100), 100 * todouble(compliantCount) / totalNum)\r\n| project ['Confidentiality compliance percentage (includes compliant and exempt)'] = strcat(tostring(round(compliancePercentageVal, 1)), '% (', tostring(compliantCount),' out of ', tostring(totalNum), ')')\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Percent of resources compliant with customer-managed keys policies", + "title": "Resource compliance score for customer-managed keys policies" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQuerySingleValueTile" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 0, + "y": 34 + } + }, + "16": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resource compliance score for confidential computing policies" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n|where type == 'microsoft.policyinsights/policystates' and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tolower(properties.policyDefinitionId), policyGroups = tolower(properties.policyDefinitionGroupNames), policySetDefinitionName = tolower(properties.policySetDefinitionName)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) in (\"so.4 - azure confidential computing\")\r\n| extend complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tolower(properties.resourceType), stateWeight = tolong(properties.stateWeight)\r\n| summarize max(stateWeight) by resourceId, resourceType\r\n| project resourceId, resourceType, complianceState = iff(max_stateWeight == 300, 'NonCompliant', iff(max_stateWeight == 200, 'Compliant', iff(max_stateWeight == 100 , 'Conflict', iff(max_stateWeight == 50, 'Exempt', 'UnknownResource'))))\r\n| summarize counts = count() by complianceState\r\n| summarize compliantCount = sumif(counts, complianceState == 'Compliant' or complianceState == 'Exempt'), nonCompliantCount = sumif(counts, complianceState == 'Conflict' or complianceState == 'NonCompliant')\r\n| extend totalNum = toint(compliantCount + nonCompliantCount)\r\n| extend compliancePercentageVal = iff(totalNum == 0, todouble(100), 100 * todouble(compliantCount) / totalNum)\r\n| project ['Confidentiality compliance percentage (includes compliant and exempt)'] = strcat(tostring(round(compliancePercentageVal, 1)), '% (', tostring(compliantCount),' out of ', tostring(totalNum), ')')\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "Percent of resources compliant with confidential computing policies", + "title": "Resource compliance score for confidential computing policies" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQuerySingleValueTile" + }, + "position": { + "colSpan": 8, + "rowSpan": 2, + "x": 8, + "y": 34 + } + }, + "17": { + "metadata": { + "inputs": [ + { + "isOptional": true, + "name": "chartType" + }, + { + "isOptional": true, + "name": "isShared" + }, + { + "isOptional": true, + "name": "queryId" + }, + { + "isOptional": true, + "name": "partTitle", + "value": "Resources exempt from confidential computing policies" + }, + { + "isOptional": true, + "name": "query", + "value": "PolicyResources\r\n| where type == 'microsoft.policyinsights/policystates' and tostring(properties.complianceState) == \"Exempt\" and properties.policyAssignmentScope startswith '/providers/Microsoft.Management/managementGroups/${root_prefix}' and properties.policyAssignmentScope endswith '${root_postfix}'\r\n| extend policyDefinitionId = tolower(properties.policyDefinitionId),complianceState = tostring(properties.complianceState), resourceId = tolower(properties.resourceId), resourceType = tostring(properties.resourceType), policySetDefinitionName = tostring(properties.policySetDefinitionName),subscriptionId = tostring(properties.subscriptionId), policyGroups = tolower(properties.policyDefinitionGroupNames)\r\n| mv-expand parsed_policy_groups = parse_json(policyGroups)\r\n| where tostring(parsed_policy_groups) in (\"so.3 - customer-managed keys\", \"so.4 - azure confidential computing\")\r\n| join kind=leftouter (\r\n resources\r\n | project resourceId=tolower(id), resourceName=name, resourceGroup\r\n ) on resourceId\r\n| join kind=inner (\r\n resourcecontainers\r\n | where type == 'microsoft.resources/subscriptions'\r\n | project subscriptionId, subscriptionName = name\r\n ) on subscriptionId\r\n| project ['Compliance State']=complianceState, ['Policy initiative']=policySetDefinitionName, ['Policy definition id']=policyDefinitionId, ['Resource type']=resourceType, ['Resource name']=resourceName, ['Subscription id']=subscriptionId, ['Policy group']=tostring(parsed_policy_groups)\r\n" + }, + { + "isOptional": true, + "name": "queryScope", + "value": { + "scope": 0, + "values": [] + } + } + ], + "partHeader": { + "subtitle": "These resources are exempt from confidential computing policies", + "title": "Resources exempt from confidential computing policies" + }, + "settings": {}, + "type": "Extension/HubsExtension/PartType/ArgQueryGridTile" + }, + "position": { + "colSpan": 16, + "rowSpan": 5, + "x": 0, + "y": 36 + } + } + } + } + }, + "metadata": { + "model": { + "timeRange": { + "type": "MsPortalFx.Composition.Configuration.ValueTypes.TimeRange", + "value": { + "relative": { + "duration": 24, + "timeUnit": 1 + } + } + } + } + } +} diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf new file mode 100644 index 0000000..f6e59c1 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Outputs for the Sovereign Landing Zone Depoloyment +AUTHOR/S: Cloud for Sovereignty +*/ +terraform { + required_version = "~> 1.9" + required_providers { + alz = { + source = "azure/alz" + version = "0.15.1" + } + + azapi = { + source = "azure/azapi" + version = "2.0.0-beta" + } + + azuread = { + source = "hashicorp/azuread" + version = "2.41.0" + } + + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.107" + } + } +} + +# Include the additional policies and override archetypes +provider "alz" { + library_references = [ + { + path = "platform/slz", + ref = "2024.10.0" + }, + { + custom_url = "${path.root}/lib" + } + ] +} + +provider "azurerm" { + features {} +} + +provider "azuread" {} + +provider "azurerm" { + alias = "management" + subscription_id = local.subscription_id_management + features {} +} + +provider "azurerm" { + alias = "connectivity" + subscription_id = local.subscription_id_connectivity + features {} +} + +provider "azurerm" { + alias = "identity" + subscription_id = local.subscription_id_identity + features {} +} \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf new file mode 100644 index 0000000..04ae6a1 --- /dev/null +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf @@ -0,0 +1,360 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +/* +SUMMARY : Outputs for the Sovereign Landing Zone Depoloyment +AUTHOR/S: Cloud for Sovereignty +*/ +variable "default_location" { + type = string + description = "Location used for deploying Azure resources. (e.g 'uksouth')|azure_location" +} + +variable "default_prefix" { + type = string + default = "mcfs" + description = "Prefix added to all Azure resources created by the SLZ. (e.g 'mcfs')" + validation { + condition = length(var.default_prefix) >= 2 && length(var.default_prefix) <= 5 + error_message = "The prefix must be between 2 and 5 characters long." + } +} + +variable "default_postfix" { + type = string + default = "" + description = "The deployment postfix for Azure resources. (e.g 'dev')" + validation { + condition = length(var.default_postfix) >= 0 && length(var.default_postfix) <= 5 + error_message = "The prefix must be between 0 and 5 characters long." + } +} + +variable "root_parent_management_group_id" { + type = string + default = "" + 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. (e.g 'mcfs')|azure_name" +} + + +variable "subscription_billing_scope" { + type = string + default = "" + description = "The subscription billing scope. (e.g '/providers/Microsoft.Billing/billingAccounts/1234567/enrollmentAccounts/123456')" +} + +variable "customer" { + type = string + default = "Country/Region" + description = "The name of the organization deploying the SLZ to brand the compliance dashboard appropriately. (e.g 'Country/Region')" +} + +variable "allowed_locations" { + type = set(string) + default = [] + description = "Full list of Azure regions allowed by policy where resources can be deployed that should include at least the deployment location. (e.g ['eastus'])" + validation { + condition = alltrue([ + for location in var.allowed_locations : contains(local.allowed_locations_list, location) + ]) + error_message = "The deployment location must be one of the allowed values." + } +} + +variable "allowed_locations_for_confidential_computing" { + type = set(string) + default = [] + description = "Full list of Azure regions allowed by policy where Confidential computing resources can be deployed. This may be a completely different list from allowed_locations. (e.g ['eastus'])" + validation { + condition = alltrue([ + for location in var.allowed_locations_for_confidential_computing : contains(local.allowed_locations_for_confidential_computing_list, location) + ]) + error_message = "The deployment location of confidential computing resources must be one of the allowed values." + } +} + +variable "deploy_ddos_protection" { + type = bool + default = true + description = "Toggles deployment of Azure DDOS protection. True to deploy, otherwise false. (e.g true)" +} + +variable "deploy_hub_network" { + type = bool + default = true + description = "Toggles deployment of the hub VNET. True to deploy, otherwise false. (e.g true)" +} + +variable "enable_firewall" { + type = bool + default = true + description = "Toggles deployment of Azure Firewall. True to deploy, otherwise false if you don't want to change the existing firewall policies. (e.g true)" +} + +variable "use_premium_firewall" { + type = bool + default = true + description = "Toggles deployment of the Premium SKU for Azure Firewall and only used if enable_Firewall is enabled. True to use Premium SKU, otherwise false. (e.g true)" +} + +variable "az_firewall_policies_enabled" { + type = bool + default = true + description = "Set this to true for the initial deployment as one firewall policy is required. Set this to false in subsequent deployments if using custom policies. (e.g true)" +} + +variable "hub_network_address_prefix" { + type = string + default = "10.20.0.0/16" + description = "CIDR range for the hub VNET. (e.g '10.20.0.0/16')" + validation { + condition = can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$", var.hub_network_address_prefix)) + error_message = "The hub_network_address_prefix must be a valid CIDR range, such as '10.20.0.0/16'." + } +} + +variable "custom_subnets" { + type = map(object({ + name = string + address_prefixes = string + networkSecurityGroupId = optional(string, "") + routeTableId = optional(string, "") + })) + default = { + AzureBastionSubnet = { + name = "AzureBastionSubnet" + address_prefixes = "10.20.15.0/24" + networkSecurityGroupId = "" + routeTableId = "" + } + GatewaySubnet = { + name = "GatewaySubnet" + address_prefixes = "10.20.252.0/24" + networkSecurityGroupId = "" + routeTableId = "" + } + AzureFirewallSubnet = { + name = "AzureFirewallSubnet" + address_prefixes = "10.20.254.0/24" + networkSecurityGroupId = "" + routeTableId = "" + } + } + description = "List of other subnets to deploy on the hub VNET and their CIDR ranges." + validation { + condition = alltrue([ + (contains(keys(var.custom_subnets), "AzureBastionSubnet") && var.deploy_bastion || !var.deploy_bastion), + contains(keys(var.custom_subnets), "GatewaySubnet"), + contains(keys(var.custom_subnets), "AzureFirewallSubnet"), + alltrue([ + for subnet in var.custom_subnets : ( + can(subnet.name) && + can(subnet.address_prefixes) && + can(regex("^([0-9]{1,3}\\.){3}[0-9]{1,3}/[0-9]{1,2}$", subnet.address_prefixes)) + ) + ]) + ]) + error_message = "Must contain subnet objects for AzureBastionSubnet, GatewaySubnet and AzureFirewallSubnet. Each subnet object must contain 'name' and 'address_prefixes'." + } +} + +variable "log_analytics_workspace_retention_in_days" { + type = number + default = 365 + description = "Length of time, in days, to retain log files with usage enforced by ALZ policies." + validation { + condition = var.log_analytics_workspace_retention_in_days >= 30 && var.log_analytics_workspace_retention_in_days <= 730 + error_message = "The retention period must be between 30 and 730 days." + } +} + +variable "subscription_id_connectivity" { + type = string + default = "" + description = "The identifier of the Connectivity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + validation { + condition = length(var.subscription_id_connectivity) == 0 || can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.subscription_id_connectivity)) + error_message = "The subscription ID must be a valid GUID in the format '00000000-0000-0000-0000-000000000000'." + } +} + +variable "subscription_id_identity" { + type = string + default = "" + description = "The identifier of the Identity Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + validation { + condition = length(var.subscription_id_identity) == 0 || can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.subscription_id_identity)) + error_message = "The subscription ID must be a valid GUID in the format '00000000-0000-0000-0000-000000000000'." + } +} + +variable "subscription_id_management" { + type = string + default = "" + description = "The identifier of the Management Subscription. (e.g '00000000-0000-0000-0000-000000000000')" + validation { + condition = length(var.subscription_id_management) == 0 || can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.subscription_id_management)) + error_message = "The subscription ID must be a valid GUID in the format '00000000-0000-0000-0000-000000000000'." + } +} + +variable "policy_exemptions" { + type = map(object({ + name = string + display_name = string + description = string + management_group_id = string + policy_assignment_id = string + policy_definition_reference_ids = optional(list(string)) + exemption_category = optional(string, "Mitigated") + })) + default = {} + description = "(Optional) list of policy exemptions." +} + +variable "express_route_gateway_config" { + type = object({ + name = optional(string) + gatewayType = optional(string) + sku = optional(string) + vpnType = optional(string) + vpnGatewayGeneration = optional(string) + enableBgp = optional(bool) + activeActive = optional(bool) + enableBgpRouteTranslationForNat = optional(bool) + enableDnsForwarding = optional(bool) + asn = optional(number) + bgpPeeringAddress = optional(string) + peerWeight = optional(number) + vpnClientConfiguration = optional(object({ + vpnAddressSpace = optional(list(string)) + }), {}) + }) + default = { "name" : "noconfigEr" } + description = "(Optional) configuration options for the ExpressRoute Gateway." +} + +variable "vpn_gateway_config" { + type = object({ + name = optional(string) + sku = optional(string) + gatewayType = optional(string) + vpnType = optional(string) + vpnGatewayGeneration = optional(string) + enableBgp = optional(bool) + activeActive = optional(bool) + enableBgpRouteTranslationForNat = optional(bool) + enableDnsForwarding = optional(bool) + asn = optional(number) + bgpPeeringAddress = optional(string) + peerWeight = optional(number) + vpnClientConfiguration = optional(object({ + vpnAddressSpace = optional(list(string)) + }), {}) + }) + default = { "name" : "noconfigVpn" } + description = "(Optional) configuration options for the VPN Gateway." +} + +variable "deploy_bastion" { + type = bool + default = true + description = "Toggles deployment of Azure Bastion. True to deploy, otherwise false. (e.g true)" +} + +variable "landing_zone_management_group_children" { + type = map(object({ + id = string + displayName = string + })) + default = {} + description = "(Optional) array of child management groups to deploy under the SLZ Landing Zones management group." +} + +variable "architecture_definition_template_path" { + type = string + default = "" + description = "The path to the architecture definition template file to use." +} + +variable "architecture_definition_override_path" { + type = string + default = "" + description = "The path to the architecture definition file to use instead of the default." +} + +variable "apply_alz_archetypes_via_architecture_definition_template" { + type = bool + default = true + description = "Toggles assignment of ALZ policies. True to deploy, otherwise false. (e.g true)" +} + +variable "ms_defender_for_cloud_email_security_contact" { + type = string + default = "security_contact@replaceme.com" + description = "An e-mail address that you want Microsoft Defender for Cloud alerts to be sent to." + validation { + condition = can(regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", var.ms_defender_for_cloud_email_security_contact)) + error_message = "The email address must be a valid format." + } +} + +variable "bastion_outbound_ssh_rdp_ports" { + type = set(string) + default = ["22", "3389"] + description = "Array of outbound destination ports and ranges for Azure Bastion." +} + +variable "policy_effect" { + type = string + default = "Deny" + description = "The policy effect used in all assignments for the Sovereignty Baseline policy initiatives." + validation { + condition = contains(["Audit", "Deny", "Disabled"], var.policy_effect) + error_message = "Allowed values are 'Audit', 'Deny', and 'Disabled'." + } +} + +variable "policy_assignment_enforcement_mode" { + type = string + default = "Default" + description = "The enforcement mode used in all policy and initiative assignments." + validation { + condition = contains(["Default", "DoNotEnforce"], var.policy_assignment_enforcement_mode) + error_message = "Allowed values are 'Default' and 'DoNotEnforce'." + } +} + +variable "deploy_log_analytics_workspace" { + type = bool + default = true + description = "True to deploy LogAnalyticsWorkspace, otherwise false. (e.g true)" +} + +variable "customer_policy_sets" { + type = map(object({ + policySetDefinitionId = string + policySetAssignmentName = string + policySetAssignmentDisplayName = string + policySetAssignmentDescription = string + policySetManagementGroupAssignmentScope = string + policyParameterFilePath = optional(string, "") + })) + default = {} + description = "(Optional) array of customer specified policy assignments to the mentioned scope with the optional input parameter file. If scope is empty assigned at root." +} +variable "tags" { + type = map(string) + default = null + description = "(Optional) Tags that will be assigned to subscription and resources created by this deployment script." +} + +variable "enable_telemetry" { + type = bool + default = true + description = < Date: Tue, 15 Oct 2024 13:15:43 -0700 Subject: [PATCH 2/6] Update locals.. --- .../microsoft_cloud_for_sovereignty/locals.tf | 10 ++-------- .../microsoft_cloud_for_sovereignty/main.dashboard.tf | 2 +- .../microsoft_cloud_for_sovereignty/terraform.tf | 1 + 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf index 3ca6a4a..97b0370 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf @@ -365,7 +365,6 @@ locals { policy_exemptions = merge(local.default_policy_exemptions, local.custom_policy_exemptions) policy_assignment_resource_ids = module.slz_management_groups.policy_assignment_resource_ids - policy_set_definition_resource_ids = module.slz_management_groups.policy_set_definition_resource_ids policy_set_definition_name = ["deploy-diag-logs", "deploy-mdfc-config-h224"] slz_default_policy_values = { @@ -420,18 +419,13 @@ locals { management_group_link = "${local.az_portal_link}/#view/Microsoft_Azure_Resources/ManagmentGroupDrilldownMenuBlade/~/overview/tenantId/${local.tenant_id}/mgId/${var.default_prefix}${var.default_postfix}/mgDisplayName/Sovereign%20Landing%20Zone/mgCanAddOrMoveSubscription~/true/mgParentAccessLevel/Owner/defaultMenuItemId/overview/drillDownMode~/true" management_group_info = "If you want to learn more about your management group, please click the following link.\n\n${local.management_group_link}\n\n" - signed_in_user = data.azurerm_client_config.current.client_id dashboard_resource_group_name = "${var.default_prefix}-rg-dashboards-${var.default_location}${var.default_postfix}" dashboard_name = "${var.default_prefix}-Sovereign-Landing-Zone-Dashboard-${var.default_location}${var.default_postfix}" dashboard_template_file_path = "${path.root}/templates/default_dashboard.tpl" - template_file_variables = { root_prefix = var.default_prefix, root_postfix = var.default_postfix, customer = "${var.customer}" } - dashboard_display_name = local.dashboard_name - dashboard_tags = null + template_file_variables = { root_prefix = var.default_prefix, root_postfix = var.default_postfix, customer = var.customer } default_template_file_variables = { name = local.dashboard_name } all_template_file_variables = merge(local.default_template_file_variables, local.template_file_variables) - template_file_path = "./lib/templates/default_dashboard.tpl" - - domain_name = data.azuread_domains.default.domains.0.domain_name + domain_name = data.azuread_domains.default.domains[0].domain_name dashboard_link = "${local.az_portal_link}/#@${local.domain_name}/dashboard/arm/subscriptions/${var.subscription_id_management}/resourceGroups/${local.dashboard_resource_group_name}/providers/Microsoft.Portal/dashboards/${local.dashboard_name}" dashboard_info = "Now your compliance dashboard is ready for you to get insights. If you want to learn more, please click the following link.\n\n${local.dashboard_link}\n\n" } \ No newline at end of file diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf index dc2e1d4..fddcda0 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.dashboard.tf @@ -22,7 +22,7 @@ module "avm_res_portal_dashboard" { name = local.dashboard_name resource_group_name = module.dashboard_rg.name template_file_path = local.dashboard_template_file_path - template_file_variables = local.template_file_variables + template_file_variables = local.all_template_file_variables enable_telemetry = var.enable_telemetry depends_on = [module.subscription_management_creation, module.dashboard_rg] diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf index f6e59c1..96e4436 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf @@ -31,6 +31,7 @@ terraform { # Include the additional policies and override archetypes provider "alz" { + library_overwrite_enabled = true library_references = [ { path = "platform/slz", From b8d0740bde2b19191d3167d93ea2d4e44650a853 Mon Sep 17 00:00:00 2001 From: Zhiyan Xu Date: Tue, 15 Oct 2024 13:32:36 -0700 Subject: [PATCH 3/6] Update locals. --- .../microsoft_cloud_for_sovereignty/locals.tf | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf index 97b0370..b2b717a 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/locals.tf @@ -184,13 +184,6 @@ locals { } locals { - route = { - name = "udr-default-azfw" - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.20.254.4" - } - ddos_protection_plan_id = var.deploy_ddos_protection ? module.ddos_protection_plan[0].resource.id : null hubnetworks_subnets = { for k, v in var.custom_subnets : k => { @@ -363,9 +356,9 @@ locals { } } - policy_exemptions = merge(local.default_policy_exemptions, local.custom_policy_exemptions) - policy_assignment_resource_ids = module.slz_management_groups.policy_assignment_resource_ids - policy_set_definition_name = ["deploy-diag-logs", "deploy-mdfc-config-h224"] + policy_exemptions = merge(local.default_policy_exemptions, local.custom_policy_exemptions) + policy_assignment_resource_ids = module.slz_management_groups.policy_assignment_resource_ids + policy_set_definition_name = ["deploy-diag-logs", "deploy-mdfc-config-h224"] slz_default_policy_values = { policyEffect = jsonencode({ value = var.policy_effect }) @@ -425,7 +418,7 @@ locals { template_file_variables = { root_prefix = var.default_prefix, root_postfix = var.default_postfix, customer = var.customer } default_template_file_variables = { name = local.dashboard_name } all_template_file_variables = merge(local.default_template_file_variables, local.template_file_variables) - domain_name = data.azuread_domains.default.domains[0].domain_name - dashboard_link = "${local.az_portal_link}/#@${local.domain_name}/dashboard/arm/subscriptions/${var.subscription_id_management}/resourceGroups/${local.dashboard_resource_group_name}/providers/Microsoft.Portal/dashboards/${local.dashboard_name}" - dashboard_info = "Now your compliance dashboard is ready for you to get insights. If you want to learn more, please click the following link.\n\n${local.dashboard_link}\n\n" + domain_name = data.azuread_domains.default.domains[0].domain_name + dashboard_link = "${local.az_portal_link}/#@${local.domain_name}/dashboard/arm/subscriptions/${var.subscription_id_management}/resourceGroups/${local.dashboard_resource_group_name}/providers/Microsoft.Portal/dashboards/${local.dashboard_name}" + dashboard_info = "Now your compliance dashboard is ready for you to get insights. If you want to learn more, please click the following link.\n\n${local.dashboard_link}\n\n" } \ No newline at end of file From 37f71c888caea9489de784f8eabb78183f208b61 Mon Sep 17 00:00:00 2001 From: Zhiyan Xu Date: Tue, 15 Oct 2024 14:07:56 -0700 Subject: [PATCH 4/6] Update config.json. --- templates/.config/ALZ-Powershell.config.json | 5 +++++ .../microsoft_cloud_for_sovereignty/README.md | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/.config/ALZ-Powershell.config.json b/templates/.config/ALZ-Powershell.config.json index bde26de..e7f5fdd 100644 --- a/templates/.config/ALZ-Powershell.config.json +++ b/templates/.config/ALZ-Powershell.config.json @@ -24,6 +24,11 @@ "location": "complete_vnext", "short_name": "Complete vNext", "description": "vNext Complete Azure Landing Zones Configurable Deployment (Warning: This is a work in progress)" + }, + "microsoft_cloud_for_sovereignty": { + "location": "microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty", + "short_name": "Microsoft Cloud for Sovereignty", + "description": "Complete Sovereign Landing Zones Configurable Deployment (Warning: This is a work in progress)" }, "test": { "location": "test", diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md index a63e5fd..09fe1cf 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/README.md @@ -156,10 +156,7 @@ express_route_gateway_config: { enableDnsForwarding: false, asn: 65515, bgpPeeringAddress: "", - peerWeight: 5, - vpnClientConfiguration: { - vpnAddressSpace: ["10.2.0.0/24"] - } + peerWeight: 5 } ``` From 8a4dc09601ecbdb4a50c37173542cb3bc25e9745 Mon Sep 17 00:00:00 2001 From: Zhiyan Xu Date: Tue, 15 Oct 2024 14:20:49 -0700 Subject: [PATCH 5/6] Fix lint errors. --- .../microsoft_cloud_for_sovereignty/variables.tf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf index 04ae6a1..b923412 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/variables.tf @@ -270,18 +270,21 @@ variable "landing_zone_management_group_children" { description = "(Optional) array of child management groups to deploy under the SLZ Landing Zones management group." } +# tflint-ignore: terraform_unused_declarations variable "architecture_definition_template_path" { type = string default = "" description = "The path to the architecture definition template file to use." } +# tflint-ignore: terraform_unused_declarations variable "architecture_definition_override_path" { type = string default = "" description = "The path to the architecture definition file to use instead of the default." } +# tflint-ignore: terraform_unused_declarations variable "apply_alz_archetypes_via_architecture_definition_template" { type = bool default = true From 71ba6dafd95f84a10fbc6261980a555d1b81e885 Mon Sep 17 00:00:00 2001 From: Zhiyan Xu Date: Tue, 15 Oct 2024 14:31:23 -0700 Subject: [PATCH 6/6] update version. --- .../microsoft_cloud_for_sovereignty/main.bootstrap.tf | 3 +-- .../microsoft_cloud_for_sovereignty/terraform.tf | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf index 803089a..41be64c 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/main.bootstrap.tf @@ -4,8 +4,7 @@ SUMMARY : Deploys the Management Groups and Subscriptions for the Sovereign Landing Zone AUTHOR/S: Cloud for Sovereignty */ -resource "random_uuid" "partner_data_uuid" { -} +resource "random_uuid" "partner_data_uuid" {} module "slz_management_groups" { source = "Azure/avm-ptn-alz/azurerm" diff --git a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf index 96e4436..f4459b7 100644 --- a/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf +++ b/templates/microsoft_cloud_for_industry/microsoft_cloud_for_sovereignty/terraform.tf @@ -26,6 +26,11 @@ terraform { source = "hashicorp/azurerm" version = "~> 3.107" } + + random = { + source = "hashicorp/random" + version = "~> 3.6.3" + } } }