diff --git a/.gitignore b/.gitignore index 0bb6f9dc..48c0febe 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ templates/basic/terraform.tfvars templates/test_* .vscode/settings.json .vs -.alzlib \ No newline at end of file +.alzlib +terraform.tfvars diff --git a/templates/.config/ALZ-Powershell.config.json b/templates/.config/ALZ-Powershell.config.json index 266751e4..bde26ded 100644 --- a/templates/.config/ALZ-Powershell.config.json +++ b/templates/.config/ALZ-Powershell.config.json @@ -1,5 +1,10 @@ { "starter_modules": { + "complete_multi_region": { + "location": "complete_multi_region", + "short_name": "Complete Multi-Region", + "description": "Complete Azure Landing Zones Configurable Deployment with Multi-Region Support" + }, "complete": { "location": "complete", "short_name": "Complete", diff --git a/templates/complete_multi_region/config-hub-and-spoke-vnet.yaml b/templates/complete_multi_region/config-hub-and-spoke-vnet.yaml new file mode 100644 index 00000000..e36e1b12 --- /dev/null +++ b/templates/complete_multi_region/config-hub-and-spoke-vnet.yaml @@ -0,0 +1,164 @@ +# This file contains templated variables to avoid repeating the same hard-coded values. +# Templated variables are denoted by the dollar curly braces token. The following details each templated variable that you can use: +# `starter_location_01`: This the primary an Azure location sourced from the `starter_locations` variable. This can be used to set the location of resources. +# `starter_location_02` to `starter_location_10`: These are the secondary Azure locations sourced from the `starter_locations` variable. This can be used to set the location of resources. +# `starter_location_01_availability_zones` to `starter_location_10_availability_zones`: These are the availability zones for the Azure locations sourced from the `starter_locations` variable. This can be used to set the availability zones of resources. +# `default_postfix`: This is a string sourced from the variable `default_postfix`. This can be used to append to resource names for consistency. +# `root_parent_management_group_id`: This is the id of the management group that the ALZ hierarchy will be nested under. +# `subscription_id_identity`: The subscription ID of the subscription to deploy the identity resources to, sourced from the variable `subscription_id_identity`. +# `subscription_id_connectivity`: The subscription ID of the subscription to deploy the connectivity resources to, sourced from the variable `subscription_id_connectivity`. +# `subscription_id_management`: The subscription ID of the subscription to deploy the management resources to, sourced from the variable `subscription_id_management`. +--- +management_groups: # `caf-enterprise-scale` module, add inputs as listed on the module registry where necessary. + + # Base variables + root_name: alz + root_id: Azure-Landing-Zones + default_location: ${starter_location_01} + subscription_id_connectivity: ${subscription_id_connectivity} + subscription_id_identity: ${subscription_id_identity} + subscription_id_management: ${subscription_id_management} + root_parent_id: ${root_parent_management_group_id} + deploy_core_landing_zones: true + deploy_corp_landing_zones: true + deploy_online_landing_zones: true + deploy_management_resources: true + deploy_connectivity_resources: false # We are using the AVM patterns for connectivity + deploy_identity_resources: true + + # Management resource settings + configure_management_resources: + location: ${starter_location_01} + settings: + log_analytics: + enabled: true + config: + retention_in_days: 50 + enable_monitoring_for_vm: true + enable_monitoring_for_vmss: true + enabled_sentinel: true + enable_solution_for_change_tracking: true + enable_solution_for_vm_insights: true + enable_solution_for_container_insights: true + enable_sentinel: true + security_center: + config: + email_security_contact: "security_contact@replace_me" + enable_defender_for_app_services: true + enable_defender_for_arm: true + enable_defender_for_containers: true + enable_defender_for_cosmosdbs: true + enable_defender_for_cspm: true + enable_defender_for_key_vault: true + enable_defender_for_oss_databases: true + enable_defender_for_servers: true + enable_defender_for_servers_vulnerability_assessments: true + enable_defender_for_sql_servers: true + enable_defender_for_sql_server_vms: true + enable_defender_for_storage: true + advanced: + asc_export_resource_group_name: rg-asc-export-${starter_location_01} + custom_settings_by_resource_type: + azurerm_resource_group: + management: + name: rg-management-${starter_location_01} + azurerm_log_analytics_workspace: + management: + name: law-management-${starter_location_01} + azurerm_automation_account: + management: + name: aa-management-${starter_location_01} + + # Configure Private DNS Zone Resource Ids for Policy Assignments + configure_connectivity_resources: + settings: + dns: + config: + location: ${starter_location_01} + advanced: + custom_settings_by_resource_type: + azurerm_resource_group: + dns: + ${starter_location_01}: + name: rg-private-dns-${starter_location_01} + +# Connectivity settings +connectivity: + hub_and_spoke_vnet: # `avm-ptn-hubnetworking` module, add inputs as listed on the module registry where necessary. + hub_virtual_networks: + # Primary hub + primary: + name: vnet-hub-${starter_location_01} + resource_group_name: rg-connectivity-${starter_location_01} + location: ${starter_location_01} + address_space: + - 10.0.0.0/16 + firewall: + name: fw-hub-${starter_location_01} + sku_name: AZFW_VNet + sku_tier: Standard + subnet_address_prefix: 10.0.1.0/24 + zones: ${starter_location_01_availability_zones} + firewall_policy: + name: fwp-hub-${starter_location_01} + dns: + proxy_enabled: true + default_ip_configuration: + public_ip_config: + zones: ${starter_location_01_availability_zones} + name: pip-hub-fw-${starter_location_01} + ip_version: "IPv4" + virtual_network_gateway: # `avm-ptn-vnetgateway` module, add inputs as listed on the module registry where necessary. + name: vgw-hub-${starter_location_01} + subnet_address_prefix: 10.0.2.0/24 + ip_configurations: + default: + name: default + public_ip: + name: pip-hub-vgw-${starter_location_01} + zones: ${starter_location_01_availability_zones} + + # Secondary hub + secondary: + name: vnet-hub-${starter_location_02} + resource_group_name: rg-connectivity-${starter_location_02} + location: ${starter_location_02} + address_space: + - 10.1.0.0/16 + firewall: + name: fw-hub-${starter_location_02} + sku_name: AZFW_VNet + sku_tier: Standard + subnet_address_prefix: 10.1.1.0/24 + zones: ${starter_location_02_availability_zones} + firewall_policy: + name: fwp-hub-${starter_location_02} + dns: + proxy_enabled: true + default_ip_configuration: + public_ip_config: + zones: ${starter_location_02_availability_zones} + name: pip-hub-fw-${starter_location_02} + ip_version: "IPv4" + virtual_network_gateway: # `avm-ptn-vnetgateway` module, add inputs as listed on the module registry where necessary. + name: vgw-hub-${starter_location_02} + subnet_address_prefix: 10.1.2.0/24 + ip_configurations: + ipconfig1: + name: ipconfig1 + public_ip: + name: pip-hub-vgw-${starter_location_02} + zones: ${starter_location_02_availability_zones} + + private_dns: + resource_group_name: rg-private-dns-${starter_location_01} + locations: + primary: + location: ${starter_location_01} + is_primary: true # Deploys all zones + secondary: + location: ${starter_location_02} + is_primary: false # Only deploys regional zones + +# Configure root module settings +enable_telemetry: true diff --git a/templates/complete_multi_region/config-virtual-wan.yaml b/templates/complete_multi_region/config-virtual-wan.yaml new file mode 100644 index 00000000..1e4360cc --- /dev/null +++ b/templates/complete_multi_region/config-virtual-wan.yaml @@ -0,0 +1,140 @@ +# This file contains templated variables to avoid repeating the same hard-coded values. +# Templated variables are denoted by the dollar curly braces token. The following details each templated variable that you can use: +# `starter_location_01`: This the primary an Azure location sourced from the `starter_locations` variable. This can be used to set the location of resources. +# `starter_location_02` to `starter_location_10`: These are the secondary Azure locations sourced from the `starter_locations` variable. This can be used to set the location of resources. +# `starter_location_01_availability_zones` to `starter_location_10_availability_zones`: These are the availability zones for the Azure locations sourced from the `starter_locations` variable. This can be used to set the availability zones of resources. +# `default_postfix`: This is a string sourced from the variable `default_postfix`. This can be used to append to resource names for consistency. +# `root_parent_management_group_id`: This is the id of the management group that the ALZ hierarchy will be nested under. +# `subscription_id_identity`: The subscription ID of the subscription to deploy the identity resources to, sourced from the variable `subscription_id_identity`. +# `subscription_id_connectivity`: The subscription ID of the subscription to deploy the connectivity resources to, sourced from the variable `subscription_id_connectivity`. +# `subscription_id_management`: The subscription ID of the subscription to deploy the management resources to, sourced from the variable `subscription_id_management`. +--- +management_groups: # `caf-enterprise-scale` module, add inputs as listed on the module registry where necessary. + + # Base variables + root_name: alz + root_id: Azure-Landing-Zones + default_location: ${starter_location_01} + subscription_id_connectivity: ${subscription_id_connectivity} + subscription_id_identity: ${subscription_id_identity} + subscription_id_management: ${subscription_id_management} + root_parent_id: ${root_parent_management_group_id} + deploy_core_landing_zones: true + deploy_corp_landing_zones: true + deploy_online_landing_zones: true + deploy_management_resources: true + deploy_connectivity_resources: false # We are using the AVM patterns for connectivity + deploy_identity_resources: true + + # Management resource settings + configure_management_resources: + location: ${starter_location_01} + settings: + log_analytics: + enabled: true + config: + retention_in_days: 50 + enable_monitoring_for_vm: true + enable_monitoring_for_vmss: true + enabled_sentinel: true + enable_solution_for_change_tracking: true + enable_solution_for_vm_insights: true + enable_solution_for_container_insights: true + enable_sentinel: true + security_center: + config: + email_security_contact: "security_contact@replace_me" + enable_defender_for_app_services: true + enable_defender_for_arm: true + enable_defender_for_containers: true + enable_defender_for_cosmosdbs: true + enable_defender_for_cspm: true + enable_defender_for_key_vault: true + enable_defender_for_oss_databases: true + enable_defender_for_servers: true + enable_defender_for_servers_vulnerability_assessments: true + enable_defender_for_sql_servers: true + enable_defender_for_sql_server_vms: true + enable_defender_for_storage: true + advanced: + asc_export_resource_group_name: rg-asc-export-${starter_location_01} + custom_settings_by_resource_type: + azurerm_resource_group: + management: + name: rg-management-${starter_location_01} + azurerm_log_analytics_workspace: + management: + name: law-management-${starter_location_01} + azurerm_automation_account: + management: + name: aa-management-${starter_location_01} + + # Configure Private DNS Zone Resource Ids for Policy Assignments + configure_connectivity_resources: + settings: + dns: + config: + location: ${starter_location_01} + advanced: + custom_settings_by_resource_type: + azurerm_resource_group: + dns: + ${starter_location_01}: + name: rg-private-dns-${starter_location_01} + +# Connectivity settings +connectivity: + virtual_wan: # `avm-ptn-vwan` module, add inputs as listed on the module registry where necessary. + virtual_wan_name: vwan-hub-${starter_location_01} + resource_group_name: rg-connectivity-${starter_location_01} + location: ${starter_location_01} + + virtual_hubs: + primary: + name: vnet-hub-${starter_location_01} + location: ${starter_location_01} + address_prefix: 10.0.0.0/16 + private_dns_virtual_network_name: vnet-hub-private-dns-${starter_location_01} + private_dns_virtual_network_address_space: 10.2.0.0/24 + private_dns_virtual_network_subnet_address_space: 10.2.0.0/28 + dns_resolver_name: dpr-hub-${starter_location_01} + secondary: + name: vnet-hub-${starter_location_02} + location: ${starter_location_02} + address_prefix: 10.1.0.0/16 + private_dns_virtual_network_name: vnet-hub-private-dns-${starter_location_02} + private_dns_virtual_network_address_space: 10.3.0.0/24 + private_dns_virtual_network_subnet_address_space: 10.3.0.0/28 + dns_resolver_name: dpr-hub-${starter_location_02} + + firewalls: + primary: + virtual_hub_key: primary + name: fw-hub-${starter_location_01} + sku_name: AZFW_Hub + sku_tier: Standard + zones: ${starter_location_01_availability_zones} + firewall_policy: + name: fwp-hub-${starter_location_01} + + secondary: + virtual_hub_key: secondary + name: fw-hub-${starter_location_02} + sku_name: AZFW_Hub + sku_tier: Standard + zones: ${starter_location_02_availability_zones} + firewall_policy: + name: fwp-hub-${starter_location_02} + + private_dns: + resource_group_name: rg-private-dns-${starter_location_01} + locations: + primary: + location: ${starter_location_01} + is_primary: true # Deploys all zones + secondary: + location: ${starter_location_02} + is_primary: false # Only deploys regional zones + +# Configure root module settings +enable_telemetry: true diff --git a/templates/complete_multi_region/data.tf b/templates/complete_multi_region/data.tf new file mode 100644 index 00000000..0328d479 --- /dev/null +++ b/templates/complete_multi_region/data.tf @@ -0,0 +1,17 @@ +data "azurerm_client_config" "current" {} + +data "azapi_resource_action" "locations" { + type = "Microsoft.Resources/subscriptions@2022-12-01" + action = "locations" + method = "GET" + resource_id = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" + response_export_values = ["value"] +} + +locals { + regions = { for region in jsondecode(data.azapi_resource_action.locations.output).value : region.name => { + display_name = region.displayName + zones = try([for zone in region.availabilityZoneMappings : zone.logicalZone], []) + } if region.metadata.regionType == "Physical" + } +} diff --git a/templates/complete_multi_region/locals-config.tf b/templates/complete_multi_region/locals-config.tf new file mode 100644 index 00000000..82daab9c --- /dev/null +++ b/templates/complete_multi_region/locals-config.tf @@ -0,0 +1,42 @@ +locals { + config_file_extension = replace(lower(element(local.config_file_split, length(local.config_file_split) - 1)), local.const_yml, local.const_yaml) + config_file_name = var.configuration_file_path == "" ? "config-hub-and-spoke-vnet.yaml" : basename(var.configuration_file_path) + config_file_split = split(".", local.config_file_name) + const_yaml = "yaml" + const_yml = "yml" + + is_yaml = local.config_file_extension == local.const_yaml || local.config_file_extension == local.const_yml + config_file_content = templatefile("${path.module}/${local.config_file_name}", local.config_template_file_variables) + config = (local.is_yaml ? + yamldecode(local.config_file_content) : + jsondecode(local.config_file_content) + ) + + config_template_file_variables = { + starter_location_01 = var.starter_locations[0] + starter_location_02 = try(var.starter_locations[1], null) + starter_location_03 = try(var.starter_locations[2], null) + starter_location_04 = try(var.starter_locations[3], null) + starter_location_05 = try(var.starter_locations[4], null) + starter_location_06 = try(var.starter_locations[5], null) + starter_location_07 = try(var.starter_locations[6], null) + starter_location_08 = try(var.starter_locations[7], null) + starter_location_09 = try(var.starter_locations[8], null) + starter_location_10 = try(var.starter_locations[9], null) + starter_location_01_availability_zones = jsonencode(local.regions[var.starter_locations[0]].zones) + starter_location_02_availability_zones = jsonencode(try(local.regions[var.starter_locations[1]].zones, null)) + starter_location_03_availability_zones = jsonencode(try(local.regions[var.starter_locations[2]].zones, null)) + starter_location_04_availability_zones = jsonencode(try(local.regions[var.starter_locations[3]].zones, null)) + starter_location_05_availability_zones = jsonencode(try(local.regions[var.starter_locations[4]].zones, null)) + starter_location_06_availability_zones = jsonencode(try(local.regions[var.starter_locations[5]].zones, null)) + starter_location_07_availability_zones = jsonencode(try(local.regions[var.starter_locations[6]].zones, null)) + starter_location_08_availability_zones = jsonencode(try(local.regions[var.starter_locations[7]].zones, null)) + starter_location_09_availability_zones = jsonencode(try(local.regions[var.starter_locations[8]].zones, null)) + starter_location_10_availability_zones = jsonencode(try(local.regions[var.starter_locations[9]].zones, null)) + default_postfix = var.default_postfix + root_parent_management_group_id = var.root_parent_management_group_id == "" ? data.azurerm_client_config.current.tenant_id : var.root_parent_management_group_id + subscription_id_connectivity = var.subscription_id_connectivity + subscription_id_identity = var.subscription_id_identity + subscription_id_management = var.subscription_id_management + } +} diff --git a/templates/complete_multi_region/locals-hub-and-spoke-vnet.tf b/templates/complete_multi_region/locals-hub-and-spoke-vnet.tf new file mode 100644 index 00000000..066d97c3 --- /dev/null +++ b/templates/complete_multi_region/locals-hub-and-spoke-vnet.tf @@ -0,0 +1,3 @@ +locals { + vnet_gateway_default_skus = { for key, value in local.module_virtual_network_gateway : key => length(local.regions[value.location].zones) == 0 ? "Standard" : "ErGw1AZ" } +} diff --git a/templates/complete_multi_region/locals-private-dns.tf b/templates/complete_multi_region/locals-private-dns.tf new file mode 100644 index 00000000..cea11f09 --- /dev/null +++ b/templates/complete_multi_region/locals-private-dns.tf @@ -0,0 +1,31 @@ +locals { + private_dns_virtual_networks_hub_and_spoke_vnet = (local.hub_networking_enabled ? + { for key, value in try(local.module_hub_and_spoke_vnet.hub_virtual_networks, {}) : key => { vnet_resource_id = module.hub_and_spoke_vnet[0].virtual_networks[key].id } } : + {} + ) + private_dns_virtual_networks_virtual_wan = (local.virtual_wan_enabled ? + { for key, value in try(local.module_virtual_wan.virtual_hubs, {}) : key => { vnet_resource_id = module.virtual_network_private_dns[key].resource_id } } : + {} + ) + private_dns_virtual_networks = merge(local.private_dns_virtual_networks_hub_and_spoke_vnet, local.private_dns_virtual_networks_virtual_wan) + private_dns_secondary_zones = { + azure_data_explorer = { + zone_name = "privatelink.{regionName}.kusto.windows.net" + } + azure_batch_account = { + zone_name = "{regionName}.privatelink.batch.azure.com" + } + azure_batch_node_mgmt = { + zone_name = "{regionName}.service.privatelink.batch.azure.com" + } + azure_aks_mgmt = { + zone_name = "privatelink.{regionName}.azmk8s.io" + } + azure_acr_data = { + zone_name = "{regionName}.data.privatelink.azurecr.io" + } + azure_backup = { + zone_name = "privatelink.{regionCode}.backup.windowsazure.com" + } + } +} diff --git a/templates/complete_multi_region/locals-virtual-wan.tf b/templates/complete_multi_region/locals-virtual-wan.tf new file mode 100644 index 00000000..72036c6b --- /dev/null +++ b/templates/complete_multi_region/locals-virtual-wan.tf @@ -0,0 +1,17 @@ +locals { + virtual_wan_virtual_network_connections_input = try(local.module_virtual_wan.virtual_network_connections, {}) + virtual_wan_private_dns_virtual_network_connections = { for key, value in try(local.module_virtual_wan.virtual_hubs, {}) : "private_dns_vnet_${key}" => { + name = "private_dns_vnet_${key}" + virtual_hub_key = key + remote_virtual_network_id = module.virtual_network_private_dns[key].resource_id + } } + virtual_wan_virtual_network_connections = local.virtual_wan_enabled ? merge(local.virtual_wan_virtual_network_connections_input, local.virtual_wan_private_dns_virtual_network_connections) : {} + virtual_wan_firewalls = { for key, value in try(local.module_virtual_wan.firewalls, {}) : key => { + virtual_hub_key = value.virtual_hub_key + name = value.name + sku_name = value.sku_name + sku_tier = value.sku_tier + firewall_policy_id = module.firewall_policy[key].resource_id + tags = try(value.tags, null) + } } +} diff --git a/templates/complete_multi_region/locals.tf b/templates/complete_multi_region/locals.tf new file mode 100644 index 00000000..4f962798 --- /dev/null +++ b/templates/complete_multi_region/locals.tf @@ -0,0 +1,43 @@ +locals { + enable_telemetry = try(local.config.enable_telemetry, true) +} + +locals { + management_groups = try(merge(local.config.management_groups, {}), {}) +} + +locals { + hub_virtual_networks = try(merge(local.config.connectivity.hub_and_spoke_vnet.hub_virtual_networks, {}), {}) + module_hub_and_spoke_vnet = { + hub_virtual_networks = { + for key, hub_virtual_network in local.hub_virtual_networks : key => { + for argument, value in hub_virtual_network : argument => value if argument != "virtual_network_gateway" + } + } + } + module_virtual_network_gateway = { + for key, hub_virtual_network in local.hub_virtual_networks : key => merge( + hub_virtual_network.virtual_network_gateway, + { + location = hub_virtual_network.location + virtual_network_id = module.hub_and_spoke_vnet[0].virtual_networks[key].id + } + ) + if can(hub_virtual_network.virtual_network_gateway) + } +} + +locals { + module_virtual_wan = try(merge(local.config.connectivity.virtual_wan, {}), {}) +} + +locals { + module_private_dns = try(merge(local.config.connectivity.private_dns, {}), {}) +} + +locals { + management_groups_enabled = length(local.management_groups) > 0 + hub_networking_enabled = length(local.module_hub_and_spoke_vnet) > 0 + virtual_wan_enabled = length(local.module_virtual_wan) > 0 + private_dns_enabled = length(local.module_private_dns) > 0 +} diff --git a/templates/complete_multi_region/management-groups.tf b/templates/complete_multi_region/management-groups.tf new file mode 100644 index 00000000..b9469992 --- /dev/null +++ b/templates/complete_multi_region/management-groups.tf @@ -0,0 +1,52 @@ +module "management_groups" { + source = "Azure/caf-enterprise-scale/azurerm" + version = "6.1.0" + + count = local.management_groups_enabled ? 1 : 0 + + disable_telemetry = try(local.management_groups.disable_telemetry, !local.enable_telemetry) + default_location = try(local.management_groups.default_location, var.starter_locations[0]) + root_parent_id = try(local.management_groups.root_parent_id, data.azurerm_client_config.current.tenant_id) + archetype_config_overrides = try(local.management_groups.archetype_config_overrides, {}) + configure_connectivity_resources = try(local.management_groups.configure_connectivity_resources, {}) + configure_identity_resources = try(local.management_groups.configure_identity_resources, {}) + configure_management_resources = try(local.management_groups.configure_management_resources, {}) + create_duration_delay = try(local.management_groups.create_duration_delay, {}) + custom_landing_zones = try(local.management_groups.custom_landing_zones, {}) + custom_policy_roles = try(local.management_groups.custom_policy_roles, {}) + default_tags = try(local.management_groups.default_tags, {}) + deploy_connectivity_resources = try(local.management_groups.deploy_connectivity_resources, true) + deploy_core_landing_zones = try(local.management_groups.deploy_core_landing_zones, true) + deploy_corp_landing_zones = try(local.management_groups.deploy_corp_landing_zones, false) + deploy_demo_landing_zones = try(local.management_groups.deploy_demo_landing_zones, false) + deploy_diagnostics_for_mg = try(local.management_groups.deploy_diagnostics_for_mg, false) + deploy_identity_resources = try(local.management_groups.deploy_identity_resources, false) + deploy_management_resources = try(local.management_groups.deploy_management_resources, false) + deploy_online_landing_zones = try(local.management_groups.deploy_online_landing_zones, false) + deploy_sap_landing_zones = try(local.management_groups.deploy_sap_landing_zones, false) + destroy_duration_delay = try(local.management_groups.destroy_duration_delay, {}) + disable_base_module_tags = try(local.management_groups.disable_base_module_tags, false) + library_path = try(local.management_groups.library_path, "") + policy_non_compliance_message_default = try(local.management_groups.policy_non_compliance_message_default, "This resource {enforcementMode} be compliant with the assigned policy.") + policy_non_compliance_message_default_enabled = try(local.management_groups.policy_non_compliance_message_default_enabled, true) + policy_non_compliance_message_enabled = try(local.management_groups.policy_non_compliance_message_enabled, true) + policy_non_compliance_message_enforced_replacement = try(local.management_groups.policy_non_compliance_message_enforced_replacement, "must") + policy_non_compliance_message_enforcement_placeholder = try(local.management_groups.policy_non_compliance_message_enforcement_placeholder, "{enforcementMode}") + policy_non_compliance_message_not_enforced_replacement = try(local.management_groups.policy_non_compliance_message_not_enforced_replacement, "should") + policy_non_compliance_message_not_supported_definitions = try(local.management_groups.policy_non_compliance_message_not_supported_definitions, ["/providers/Microsoft.Authorization/policyDefinitions/1c6e92c9-99f0-4e55-9cf2-0c234dc48f99", "/providers/Microsoft.Authorization/policyDefinitions/1a5b4dca-0b6f-4cf5-907c-56316bc1bf3d", "/providers/Microsoft.Authorization/policyDefinitions/95edb821-ddaf-4404-9732-666045e056b4"]) + resource_custom_timeouts = try(local.management_groups.resource_custom_timeouts, {}) + root_id = try(local.management_groups.root_id, "alz") + root_name = try(local.management_groups.root_name, "Azure-Landing-Zones") + strict_subscription_association = try(local.management_groups.strict_subscription_association, true) + subscription_id_connectivity = try(local.management_groups.subscription_id_connectivity, var.subscription_id_connectivity) + subscription_id_identity = try(local.management_groups.subscription_id_identity, var.subscription_id_identity) + subscription_id_management = try(local.management_groups.subscription_id_management, var.subscription_id_management) + subscription_id_overrides = try(local.management_groups.subscription_id_overrides, {}) + template_file_variables = try(local.management_groups.template_file_variables, {}) + + providers = { + azurerm = azurerm + azurerm.connectivity = azurerm.connectivity + azurerm.management = azurerm.management + } +} diff --git a/templates/complete_multi_region/networking-hub-and-spoke-vnet.tf b/templates/complete_multi_region/networking-hub-and-spoke-vnet.tf new file mode 100644 index 00000000..69254dff --- /dev/null +++ b/templates/complete_multi_region/networking-hub-and-spoke-vnet.tf @@ -0,0 +1,54 @@ +module "hub_and_spoke_vnet" { + source = "Azure/avm-ptn-hubnetworking/azurerm" + version = "0.1.0" + + count = length(local.hub_virtual_networks) > 0 ? 1 : 0 + + hub_virtual_networks = local.module_hub_and_spoke_vnet.hub_virtual_networks + enable_telemetry = try(local.module_hub_and_spoke_vnet.enable_telemetry, local.enable_telemetry) + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + module.management_groups + ] +} + +module "virtual_network_gateway" { + source = "Azure/avm-ptn-vnetgateway/azurerm" + version = "0.3.1" + + for_each = local.module_virtual_network_gateway + + location = each.value.location + name = each.value.name + sku = try(each.value.sku, null) == null ? local.vnet_gateway_default_skus[each.key] : each.value.sku + type = try(each.value.type, null) + virtual_network_id = each.value.virtual_network_id + default_tags = try(each.value.default_tags, null) + subnet_creation_enabled = try(each.value.subnet_creation_enabled, null) + edge_zone = try(each.value.edge_zone, null) + express_route_circuits = try(each.value.express_route_circuits, null) + ip_configurations = try(each.value.ip_configurations, null) + local_network_gateways = try(each.value.local_network_gateways, null) + subnet_address_prefix = try(each.value.subnet_address_prefix, null) + tags = try(each.value.tags, null) + vpn_active_active_enabled = try(each.value.vpn_active_active_enabled, null) + vpn_bgp_enabled = try(each.value.vpn_bgp_enabled, null) + vpn_bgp_settings = try(each.value.vpn_bgp_settings, null) + vpn_generation = try(each.value.vpn_generation, null) + vpn_point_to_site = try(each.value.vpn_point_to_site, null) + vpn_type = try(each.value.vpn_type, null) + vpn_private_ip_address_enabled = try(each.value.vpn_private_ip_address_enabled, null) + route_table_bgp_route_propagation_enabled = try(each.value.route_table_bgp_route_propagation_enabled, null) + route_table_creation_enabled = try(each.value.route_table_creation_enabled, null) + route_table_name = try(each.value.route_table_name, null) + route_table_tags = try(each.value.route_table_tags, null) + enable_telemetry = try(each.value.enable_telemetry, local.enable_telemetry) + + providers = { + azurerm = azurerm.connectivity + } +} diff --git a/templates/complete_multi_region/networking-private-dns.tf b/templates/complete_multi_region/networking-private-dns.tf new file mode 100644 index 00000000..dc5bb509 --- /dev/null +++ b/templates/complete_multi_region/networking-private-dns.tf @@ -0,0 +1,34 @@ +module "private_dns_zones_resource_group" { + source = "Azure/avm-res-resources-resourcegroup/azurerm" + version = "0.1.0" + + count = local.private_dns_enabled ? 1 : 0 + + name = try(local.module_private_dns.resource_group_name, "rg-private-dns-${var.starter_locations[0]}") + location = try(local.module_private_dns.location, [for location in local.module_private_dns.locations : location if location.is_primary][0].location) + enable_telemetry = try(local.module_private_dns.enable_telemetry, local.enable_telemetry) + + providers = { + azurerm = azurerm.connectivity + } +} + +module "private_dns_zones" { + source = "Azure/avm-ptn-network-private-link-private-dns-zones/azurerm" + version = "0.4.0" + + for_each = local.private_dns_enabled ? try(local.module_private_dns.locations, {}) : {} + + location = each.value.location + resource_group_name = module.private_dns_zones_resource_group[0].name + resource_group_creation_enabled = false + virtual_network_resource_ids_to_link_to = local.private_dns_virtual_networks + private_link_private_dns_zones = each.value.is_primary ? null : local.private_dns_secondary_zones + enable_telemetry = try(local.module_private_dns.enable_telemetry, local.enable_telemetry) + + depends_on = [module.private_dns_zones_resource_group] + + providers = { + azurerm = azurerm.connectivity + } +} diff --git a/templates/complete_multi_region/networking-virtual-wan.tf b/templates/complete_multi_region/networking-virtual-wan.tf new file mode 100644 index 00000000..50baa653 --- /dev/null +++ b/templates/complete_multi_region/networking-virtual-wan.tf @@ -0,0 +1,142 @@ +module "virtual_wan_resource_group" { + source = "Azure/avm-res-resources-resourcegroup/azurerm" + version = "0.1.0" + + count = local.virtual_wan_enabled ? 1 : 0 + + name = try(local.module_virtual_wan.resource_group_name, "rg-connectivity-${var.starter_locations[0]}") + location = try(local.module_virtual_wan.location, var.starter_locations[0]) + enable_telemetry = try(local.module_virtual_wan.enable_telemetry, local.enable_telemetry) + + providers = { + azurerm = azurerm.connectivity + } +} + +module "firewall_policy" { + source = "Azure/avm-res-network-firewallpolicy/azurerm" + version = "0.2.3" + + for_each = local.virtual_wan_enabled ? try(local.module_virtual_wan.firewalls, {}) : {} + + name = each.value.firewall_policy.name + location = try(each.value.firewall_policy.location, try(local.module_virtual_wan.location, var.starter_locations[0])) + resource_group_name = try(local.module_virtual_wan.resource_group_name, module.virtual_wan_resource_group[0].name) + firewall_policy_sku = try(each.value.firewall_policy.sku, "Standard") + firewall_policy_auto_learn_private_ranges_enabled = try(each.value.firewall_policy.auto_learn_private_ranges_enabled, null) + firewall_policy_base_policy_id = try(each.value.firewall_policy.base_policy_id, null) + firewall_policy_dns = try(each.value.firewall_policy.dns, { + servers = [module.dns_resolver[each.value.virtual_hub_key].inbound_endpoint_ips["dns"]] + proxy_enabled = true + }) + firewall_policy_threat_intelligence_mode = try(each.value.firewall_policy.threat_intelligence_mode, "Alert") + firewall_policy_private_ip_ranges = try(each.value.firewall_policy.private_ip_ranges, null) + firewall_policy_threat_intelligence_allowlist = try(each.value.firewall_policy.threat_intelligence_allowlist, null) + tags = try(each.value.firewall_policy.tags, null) + enable_telemetry = try(local.module_virtual_wan.enable_telemetry, local.enable_telemetry) + + depends_on = [ + module.virtual_wan_resource_group + ] + + providers = { + azurerm = azurerm.connectivity + } +} + +module "virtual_wan" { + source = "Azure/avm-ptn-virtualwan/azurerm" + version = "0.5.0" + + count = local.virtual_wan_enabled ? 1 : 0 + + allow_branch_to_branch_traffic = try(local.module_virtual_wan.allow_branch_to_branch_traffic, null) + create_resource_group = try(local.module_virtual_wan.create_resource_group, false) + disable_vpn_encryption = try(local.module_virtual_wan.disable_vpn_encryption, false) + er_circuit_connections = try(local.module_virtual_wan.er_circuit_connections, {}) + expressroute_gateways = try(local.module_virtual_wan.expressroute_gateways, {}) + firewalls = local.virtual_wan_firewalls + office365_local_breakout_category = try(local.module_virtual_wan.office365_local_breakout_category, null) + location = try(local.module_virtual_wan.location, null) + p2s_gateway_vpn_server_configurations = try(local.module_virtual_wan.p2s_gateway_vpn_server_configurations, {}) + p2s_gateways = try(local.module_virtual_wan.p2s_gateways, {}) + resource_group_name = try(local.module_virtual_wan.resource_group_name, module.virtual_wan_resource_group[0].name) + virtual_hubs = try(local.module_virtual_wan.virtual_hubs, null) + virtual_network_connections = local.virtual_wan_virtual_network_connections + virtual_wan_name = try(local.module_virtual_wan.virtual_wan_name, null) + type = try(local.module_virtual_wan.type, null) + routing_intents = try(local.module_virtual_wan.routing_intents, null) + resource_group_tags = try(local.module_virtual_wan.resource_group_tags, null) + virtual_wan_tags = try(local.module_virtual_wan.virtual_wan_tags, null) + vpn_gateways = try(local.module_virtual_wan.vpn_gateways, {}) + vpn_site_connections = try(local.module_virtual_wan.vpn_site_connections, {}) + vpn_sites = try(local.module_virtual_wan.vpn_sites, null) + tags = try(local.module_virtual_wan.tags, null) + enable_telemetry = try(local.module_virtual_wan.enable_telemetry, local.enable_telemetry) + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + module.management_groups, + module.virtual_wan_resource_group + ] +} + +module "virtual_network_private_dns" { + source = "Azure/avm-res-network-virtualnetwork/azurerm" + version = "0.4.0" + + for_each = local.virtual_wan_enabled ? try(local.module_virtual_wan.virtual_hubs, {}) : {} + + address_space = [try(each.value.private_dns_virtual_network_address_space, null)] + location = try(each.value.location, var.starter_locations[0]) + name = try(each.value.private_dns_virtual_network_name, "vnet-private-dns-${each.value.location}") + resource_group_name = try(local.module_virtual_wan.resource_group_name, module.virtual_wan_resource_group[0].name) + enable_telemetry = try(local.module_virtual_wan.enable_telemetry, local.enable_telemetry) + subnets = { + dns = { + address_prefix = try(each.value.private_dns_virtual_network_subnet_address_space, null) + name = try(each.value.private_dns_virtual_network_subnet_name, "subnet-dns") + delegation = [{ + name = "Microsoft.Network.dnsResolvers" + service_delegation = { + name = "Microsoft.Network/dnsResolvers" + #actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"] + } + }] + } + } + + depends_on = [module.virtual_wan_resource_group] + + providers = { + azurerm = azurerm.connectivity + } +} + +module "dns_resolver" { + source = "Azure/avm-res-network-dnsresolver/azurerm" + version = "0.2.1" + + for_each = local.virtual_wan_enabled ? try(local.module_virtual_wan.virtual_hubs, {}) : {} + + location = try(each.value.location, var.starter_locations[0]) + name = try(each.value.dns_resolver_name, "dpr-hub-${each.value.location}") + resource_group_name = try(local.module_virtual_wan.resource_group_name, module.virtual_wan_resource_group[0].name) + virtual_network_resource_id = module.virtual_network_private_dns[each.key].resource_id + enable_telemetry = try(local.module_virtual_wan.enable_telemetry, local.enable_telemetry) + inbound_endpoints = { + dns = { + name = "dns" + subnet_name = module.virtual_network_private_dns[each.key].subnets.dns.name + } + } + + depends_on = [module.virtual_wan_resource_group] + + providers = { + azurerm = azurerm.connectivity + } +} diff --git a/templates/complete_multi_region/outputs.tf b/templates/complete_multi_region/outputs.tf new file mode 100644 index 00000000..2d69b0d6 --- /dev/null +++ b/templates/complete_multi_region/outputs.tf @@ -0,0 +1,12 @@ +# output "transformed_config_file" { +# value = local.config_file_content +# } + +output "firewall_ip_addresses" { + value = (local.hub_networking_enabled ? + { for key, value in try(module.hub_and_spoke_vnet[0].firewalls, {}) : key => value.private_ip_address } : + (local.virtual_wan_enabled ? + { for key, value in try(module.virtual_wan[0].fw, {}) : key => value.name } : {} + ) + ) # TODO: Output vWAN firewall IP addresses +} diff --git a/templates/complete_multi_region/terraform.tf b/templates/complete_multi_region/terraform.tf new file mode 100644 index 00000000..788d0588 --- /dev/null +++ b/templates/complete_multi_region/terraform.tf @@ -0,0 +1,38 @@ +terraform { + required_version = "~> 1.8" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.107" + } + azapi = { + source = "Azure/azapi" + version = "~> 1.13" + } + } + # backend "azurerm" {} +} + +provider "azapi" { + skip_provider_registration = true + subscription_id = var.subscription_id_management +} + +provider "azurerm" { + skip_provider_registration = true + features {} +} + +provider "azurerm" { + skip_provider_registration = true + alias = "management" + subscription_id = var.subscription_id_management + features {} +} + +provider "azurerm" { + skip_provider_registration = true + alias = "connectivity" + subscription_id = var.subscription_id_connectivity + features {} +} diff --git a/templates/complete_multi_region/variables.tf b/templates/complete_multi_region/variables.tf new file mode 100644 index 00000000..edd7584a --- /dev/null +++ b/templates/complete_multi_region/variables.tf @@ -0,0 +1,37 @@ +variable "starter_locations" { + type = list(string) + description = "The location for Azure resources. (e.g 'uksouth')|1|azure_location" +} + +variable "subscription_id_connectivity" { + type = string + description = "value of the subscription id for the Connectivity subscription|5|azure_subscription_id" +} + +variable "subscription_id_identity" { + type = string + description = "value of the subscription id for the Identity subscription|6|azure_subscription_id" +} + +variable "subscription_id_management" { + type = string + description = "value of the subscription id for the Management subscription|4|azure_subscription_id" +} + +variable "configuration_file_path" { + type = string + default = "config-hub-and-spoke-vnet.yaml" + description = "The path of the configuration file|7|configuration_file_path" +} + +variable "default_postfix" { + type = string + default = "landing-zone" + description = "The default postfix for Azure resources. (e.g 'landing-zone')|2|azure_name" +} + +variable "root_parent_management_group_id" { + type = string + default = "" + description = "This is the id of the management group that the ALZ hierarchy will be nested under, will default to the Tenant Root Group|3|azure_name" +} diff --git a/templates/complete_vnext/terraform.tf b/templates/complete_vnext/terraform.tf index 1a6539a3..36ee05ce 100644 --- a/templates/complete_vnext/terraform.tf +++ b/templates/complete_vnext/terraform.tf @@ -1,7 +1,14 @@ terraform { required_version = "~> 1.6" required_providers { - azurerm = "~> 3.88" + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.107" + } + azapi = { + source = "Azure/azapi" + version = "~> 1.13" + } } # backend "azurerm" {} } diff --git a/templates/test/terraform.tf b/templates/test/terraform.tf index d077fe42..fb67c7d3 100644 --- a/templates/test/terraform.tf +++ b/templates/test/terraform.tf @@ -2,15 +2,15 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "3.61.0" + version = "~> 3.107" } azapi = { source = "Azure/azapi" - version = "1.13.1" + version = "~> 1.13" } random = { source = "hashicorp/random" - version = "3.5.1" + version = "~> 3.5" } } # backend "azurerm" {} diff --git a/templates/test/terraform.tfvars b/templates/test/terraform.tfvars deleted file mode 100644 index 6406e955..00000000 --- a/templates/test/terraform.tfvars +++ /dev/null @@ -1,3 +0,0 @@ -subscription_id_connectivity = "00000000-0000-0000-0000-000000000000" -subscription_id_identity = "00000000-0000-0000-0000-000000000000" -subscription_id_management = "00000000-0000-0000-0000-000000000000"