diff --git a/docs/wiki/[User-Guide]-Starter-Module-Complete-Hub-And-Spoke.md b/docs/wiki/[User-Guide]-Starter-Module-Complete-Hub-And-Spoke.md new file mode 100644 index 00000000..9180f990 --- /dev/null +++ b/docs/wiki/[User-Guide]-Starter-Module-Complete-Hub-And-Spoke.md @@ -0,0 +1,115 @@ +# Complete Hub and Spoke Scenario Documentation + +The "Complete Hub and Spoke" scenario uses Terraform to fully customize Azure Landing Zone deployment. The scenario emphasizes a hub-and-spoke network topology and includes modules for management groups, connectivity, and security components. + +## Recommended Modules + +The following modules are key components for the "Complete Hub and Spoke" architecture: + +### `caf-enterprise-scale` + +The Cloud Adoption Framework's `caf-enterprise-scale` Terraform module sets up a scalable management group hierarchy, policy assignments, and compliance settings. + + + +```hcl +module "enterprise_scale" { + source = "Azure/caf-enterprise-scale/azurerm" + version = "~> 5.2.0" + + # ... +} + +``` + +### `hubnetworking` + +The `hubnetworking` Terraform module creates the hub-and-spoke network topology including the virtual networks and optionally deploys and configures network components like Azure Firewall. + + + +```hcl +module "hubnetworking" { + source = "Azure/hubnetworking/azurerm" + version = "~> 1.1.0" + + # ... +} + +``` + +### `azurerm_firewall_policy` + +This resource creates an Azure Firewall Policy, which enables customization of firewall rules and settings. + + + +```hcl +resource "azurerm_firewall_policy" "this" { + # ... +} + +``` + +### `azurerm_firewall_policy_rule_collection_group` + +Assigns rule collection groups to the firewall policy to control network traffic flow. + + +```hcl +resource "azurerm_firewall_policy_rule_collection_group" "example" { + # ... +} + +``` + +### `virtual_network_gateway` + +Establishes a Virtual Network Gateway for secure VPN connections and can also be used for ExpressRoute connectivity. + + + +```hcl +module "virtual_network_gateway" { + # ... +} + +``` + +### Azure Bastion and Jumpbox VM + +For secure RDP/SSH access to virtual machines, the Azure Bastion service is provisioned and a separate virtual machine is deployed to function as a jumpbox. + + + +```hcl +module "azure_bastion" { + # ... +} + +module "vmjumpbox" { + # ... +} + +``` + +## Implementation Overview + +- The central hub is the Azure VNet that acts as the connectivity focal point to which different spokes (VNets) will connect. +- The `caf-enterprise-scale` module will define and enforce governance, compliance, and management across all VNets. +- The `hubnetworking` module allows for the configuration of the central hub, including the deployment of Azure Firewall for enhanced security and firewall policies. +- An Azure Firewall Policy is defined and associated with the Azure Firewall to implement the required rule sets for traffic filtering. +- A Virtual Network Gateway is configured, enabling VPN or ExpressRoute for communication between Azure and on-premises networks. +- Azure Bastion provides secure and seamless RDP and SSH connectivity to Azure VMs without public IP addresses, directly through the Azure portal. +- A Jumpbox VM (Virtual Machine) is deployed to facilitate secure management tasks within the Azure environment. + +## Deployment Process + +To deploy the "Complete Hub and Spoke" scenario: + +1. Customize the `enterprise_scale` and `hubnetworking` modules in your Terraform files according to your organizational structure and networking requirements. +2. Define your Azure Firewall policies and rulesets within the `azurerm_firewall_policy` and `azurerm_firewall_policy_rule_collection_group` resources. +3. Deploy the Virtual Network Gateway, Azure Bastion, and Jumpbox VM as per your connectivity and management access needs. +4. Apply the Terraform configuration to provision the resources in your Azure environment. + +Please consider referencing Terraform and Azure documentation for in-depth guidance on module usage and attribute definitions to ensure the deployment aligns with best practices and your organization's architectural requirements. \ No newline at end of file diff --git a/docs/wiki/[User-Guide]-Starter-Module-Complete-Virtual-Wan.md b/docs/wiki/[User-Guide]-Starter-Module-Complete-Virtual-Wan.md new file mode 100644 index 00000000..713f06c8 --- /dev/null +++ b/docs/wiki/[User-Guide]-Starter-Module-Complete-Virtual-Wan.md @@ -0,0 +1,101 @@ + +# Complete Virtual WAN Deployment Scenario Documentation + +The "Complete Virtual WAN (vWAN)" scenario uses Terraform to create a scalable and automated Azure network infrastructure. This sophisticated configuration emphasizes a global transit network strategy that incorporates governance, connectivity, and security elements within a unified managed network service. + +## Key Terraform Modules and Resources + +### `caf-enterprise-scale` + +The `caf-enterprise-scale` module establishes the governance structure for Azure by setting up a management group hierarchy, policy assignments, and ensuring compliance. + +```hcl +module "enterprise_scale" { + source = "Azure/caf-enterprise-scale/azurerm" + version = "~> 5.2.0" + + # ... +} +``` + +### `hubnetworking` + +The `hubnetworking` module configures a shared virtual network which is utilized for centralized services like jumpbox and Azure Bastion. + +```hcl +module "hubnetworking" { + source = "Azure/hubnetworking/azurerm" + version = "~> 1.1.0" + + # ... +} +``` + +### `vwan` + +The `vwan` module implements the Azure Virtual WAN service, facilitating a hub-and-spoke architecture that enables automated routing and global connectivity. + +```hcl +module "vwan" { + source = "Azure/avm-ptn-virtualwan/azurerm" + version = "~> 0.5.0" + + # ... +} +``` + +### `azurerm_virtual_hub_connection` + +This resource establishes a connection between the virtual hubs within the vWAN and virtual networks, promoting seamless interconnectivity and centralized network management. + +```hcl +resource "azurerm_virtual_hub_connection" "example_connection" { + # ... +} +``` + +### `azurerm_firewall_policy` + +This resource defines a firewall policy for the virtual WAN, offering advanced routing and security settings at a WAN scope. + +```hcl +resource "azurerm_firewall_policy" "this" { + # ... +} +``` + +### Azure Bastion and Jumpbox VM + +Incorporating Azure Bastion within the vWAN infrastructure allows for secure RDP/SSH connectivity across the network without the need for public IP addresses. A separate jumpbox VM is used for secure administrative access. + +```hcl +module "azure_bastion" { + # ... +} + +module "vmjumpbox" { + # ... +} +``` + +## Virtual WAN vs Traditional Hub and Spoke + +The vWAN architecture provides an orchestrated and optimized network connectivity solution compared to traditional hub-and-spoke configurations. Key differences include: + +- Centralized routing and security controls across various segments of the network, including branch-to-branch connectivity. +- Global traffic transit with automated routing leveraging the Microsoft Global Network. +- A unified operational model and significantly simplified network management experience. +- Consistent policy and security enforcement across the entire network footprint. + +This approach is well-suited for enterprises looking to simplify complex networking scenarios, especially for those requiring robust, global networking capabilities. + +## Deployment Procedure + +To implement the "Complete vWAN" scenario: + +1. Tailor the `enterprise_scale`, `hubnetworking`, and `vwan` modules within your Terraform configurations to reflect your network architecture and requirements. +2. Establish Azure Firewall policies and rules at the WAN-level to maintain security and governance. +3. Setup Azure Bastion for central, secure access to VMs, and configure a jumpbox VM for network management operations. +4. Execute the Terraform plan to provision the defined Azure environment resources. + +When deploying, consult the detailed Terraform and Azure documentation for guidance on utilizing these modules and resources, ensuring that the deployment adheres to your organization's network strategy and best practices. \ No newline at end of file diff --git a/templates/complete_hub_and_spoke/config.yaml b/templates/complete_hub_and_spoke/config.yaml new file mode 100644 index 00000000..802c6159 --- /dev/null +++ b/templates/complete_hub_and_spoke/config.yaml @@ -0,0 +1,70 @@ +# 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: +# `default_location`: This is an Azure location sourced from the `default_location` variable. This can be used to set the location 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`. +--- +archetypes: # `caf-enterprise-scale` module, add inputs as listed on the module registry where necessary. + root_name: es + root_id: Enterprise-Scale + 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_corp_landing_zones: true + deploy_online_landing_zones: true + default_location: ${default_location} + disable_telemetry: true + deploy_management_resources: true + configure_management_resources: + location: ${default_location} + settings: + security_center: + config: + email_security_contact: "security_contact@replace_me" + advanced: + asc_export_resource_group_name: rg-asc-export + custom_settings_by_resource_type: + azurerm_resource_group: + management: + name: rg-management + azurerm_log_analytics_workspace: + management: + name: log-management + azurerm_automation_account: + management: + name: aa-management +connectivity: + hubnetworking: # `hubnetworking` module, add inputs as listed on the module registry where necessary. + hub_virtual_networks: + primary: + name: vnet-hub + resource_group_name: rg-connectivity + location: ${default_location} + address_space: + - 10.0.0.0/16 + subnets: + AzureBastionSubnet: + name: "AzureBastionSubnet" + address_prefixes: ["10.0.3.0/27"] + assign_generated_route_table: false + SharedSubnet: + name: "SharedSubnet" + address_prefixes: ["10.0.4.0/27"] + firewall: + name: fw-hub + sku_name: AZFW_VNet + sku_tier: Standard + subnet_address_prefix: 10.0.1.0/24 + zones: ["1", "2", "3"] + default_ip_configuration: + public_ip_config: + zones: ["1", "2", "3"] + name: "pip-hub" + virtual_network_gateway: # `avm-ptn-vnetgateway` module, add inputs as listed on the module registry where necessary. + name: vgw-hub + subnet_address_prefix: 10.0.2.0/24 + vwan: # `avm-ptn-virtualwan` module, add inputs as listed on the module registry where necessary. diff --git a/templates/complete_hub_and_spoke/data.tf b/templates/complete_hub_and_spoke/data.tf new file mode 100644 index 00000000..d5783ec3 --- /dev/null +++ b/templates/complete_hub_and_spoke/data.tf @@ -0,0 +1 @@ +data "azurerm_client_config" "core" {} diff --git a/templates/complete_hub_and_spoke/locals.tf b/templates/complete_hub_and_spoke/locals.tf new file mode 100644 index 00000000..3c41282f --- /dev/null +++ b/templates/complete_hub_and_spoke/locals.tf @@ -0,0 +1,66 @@ +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.yaml" : basename(var.configuration_file_path) + config_file_split = split(".", local.config_file_name) + const_yaml = "yaml" + const_yml = "yml" +} +locals { + config = (local.config_file_extension == local.const_yaml ? + yamldecode(templatefile("${path.module}/${local.config_file_name}", local.config_template_file_variables)) : + jsondecode(templatefile("${path.module}/${local.config_file_name}", local.config_template_file_variables)) + ) + config_template_file_variables = { + default_location = var.default_location + default_postfix = var.default_postfix + root_parent_management_group_id = var.root_parent_management_group_id == "" ? data.azurerm_client_config.core.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 + } +} +locals { + archetypes = try(merge(local.config.archetypes, {}), {}) +} +locals { + hub_virtual_networks = { + for key, hub_virtual_network in try(merge(local.config.connectivity.hubnetworking.hub_virtual_networks, {}), {}) : key => { + name = hub_virtual_network.name + resource_group_name = hub_virtual_network.resource_group_name + location = hub_virtual_network.location + address_space = hub_virtual_network.address_space + subnets = hub_virtual_network.subnets + + // If the `firewall` block exists, merge the new policy ID, otherwise keep the firewall block as-is + firewall = merge(hub_virtual_network.firewall, { + firewall_policy_id = azurerm_firewall_policy.this.id + }) + + // Maintain all other configuration as is + virtual_network_gateway = hub_virtual_network.virtual_network_gateway + // ... any other fields that your network structure has + } + } + + module_hubnetworking = { + 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.hubnetworking[0].virtual_networks[key].id + } + ) + if can(hub_virtual_network.virtual_network_gateway) + } +} +locals { + module_vwan = try(merge(local.config.connectivity.vwan, {}), {}) +} + diff --git a/templates/complete_hub_and_spoke/main.tf b/templates/complete_hub_and_spoke/main.tf new file mode 100644 index 00000000..a8800b28 --- /dev/null +++ b/templates/complete_hub_and_spoke/main.tf @@ -0,0 +1,234 @@ +module "enterprise_scale" { + source = "Azure/caf-enterprise-scale/azurerm" + version = "~> 5.2.0" + + count = length(local.archetypes) > 0 ? 1 : 0 + + disable_telemetry = try(local.archetypes.disable_telemetry, true) + default_location = try(local.archetypes.default_location, var.default_location) + root_parent_id = try(local.archetypes.root_parent_id, data.azurerm_client_config.core.tenant_id) + archetype_config_overrides = try(local.archetypes.archetype_config_overrides, {}) + configure_connectivity_resources = try(local.archetypes.configure_connectivity_resources, {}) + configure_identity_resources = try(local.archetypes.configure_identity_resources, {}) + configure_management_resources = try(local.archetypes.configure_management_resources, {}) + create_duration_delay = try(local.archetypes.create_duration_delay, {}) + custom_landing_zones = try(local.archetypes.custom_landing_zones, {}) + custom_policy_roles = try(local.archetypes.custom_policy_roles, {}) + default_tags = try(local.archetypes.default_tags, {}) + deploy_connectivity_resources = try(local.archetypes.deploy_connectivity_resources, false) + deploy_core_landing_zones = try(local.archetypes.deploy_core_landing_zones, true) + deploy_corp_landing_zones = try(local.archetypes.deploy_corp_landing_zones, false) + deploy_demo_landing_zones = try(local.archetypes.deploy_demo_landing_zones, false) + deploy_diagnostics_for_mg = try(local.archetypes.deploy_diagnostics_for_mg, false) + deploy_identity_resources = try(local.archetypes.deploy_identity_resources, false) + deploy_management_resources = try(local.archetypes.deploy_management_resources, false) + deploy_online_landing_zones = try(local.archetypes.deploy_online_landing_zones, false) + deploy_sap_landing_zones = try(local.archetypes.deploy_sap_landing_zones, false) + destroy_duration_delay = try(local.archetypes.destroy_duration_delay, {}) + disable_base_module_tags = try(local.archetypes.disable_base_module_tags, false) + library_path = try(local.archetypes.library_path, "") + policy_non_compliance_message_default = try(local.archetypes.policy_non_compliance_message_default, "This resource {enforcementMode} be compliant with the assigned policy.") + policy_non_compliance_message_default_enabled = try(local.archetypes.policy_non_compliance_message_default_enabled, true) + policy_non_compliance_message_enabled = try(local.archetypes.policy_non_compliance_message_enabled, true) + policy_non_compliance_message_enforced_replacement = try(local.archetypes.policy_non_compliance_message_enforced_replacement, "must") + policy_non_compliance_message_enforcement_placeholder = try(local.archetypes.policy_non_compliance_message_enforcement_placeholder, "{enforcementMode}") + policy_non_compliance_message_not_enforced_replacement = try(local.archetypes.policy_non_compliance_message_not_enforced_replacement, "should") + policy_non_compliance_message_not_supported_definitions = try(local.archetypes.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.archetypes.resource_custom_timeouts, {}) + root_id = try(local.archetypes.root_id, "es") + root_name = try(local.archetypes.root_name, "Enterprise-Scale") + strict_subscription_association = try(local.archetypes.strict_subscription_association, true) + subscription_id_connectivity = try(local.archetypes.subscription_id_connectivity, var.subscription_id_connectivity) + subscription_id_identity = try(local.archetypes.subscription_id_identity, var.subscription_id_identity) + subscription_id_management = try(local.archetypes.subscription_id_management, var.subscription_id_management) + subscription_id_overrides = try(local.archetypes.subscription_id_overrides, {}) + template_file_variables = try(local.archetypes.template_file_variables, {}) + + providers = { + azurerm = azurerm + azurerm.connectivity = azurerm.connectivity + azurerm.management = azurerm.management + } +} + +module "hubnetworking" { + source = "Azure/hubnetworking/azurerm" + version = "~> 1.1.0" + + count = length(local.hub_virtual_networks) > 0 ? 1 : 0 + + hub_virtual_networks = local.module_hubnetworking.hub_virtual_networks + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + module.enterprise_scale + ] +} +resource "azurerm_firewall_policy" "this" { + location = var.default_location + name = "vhub-avm-vwan-fw-policy" + resource_group_name = "rg-connectivity" + provider = azurerm.connectivity +} + +resource "azurerm_firewall_policy_rule_collection_group" "example" { + name = "example-fwpolicy-rcg" + firewall_policy_id = azurerm_firewall_policy.this.id + priority = 500 + application_rule_collection { + name = "app_rule_collection1" + priority = 500 + action = "Deny" + rule { + name = "app_rule_collection1_rule1" + protocols { + type = "Http" + port = 80 + } + protocols { + type = "Https" + port = 443 + } + source_addresses = ["10.0.0.0/16"] + destination_fqdns = ["*.microsoft.com"] + } + } + + network_rule_collection { + name = "network_rule_collection1" + priority = 400 + action = "Allow" + rule { + name = "network_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.5.0.0/16"] + destination_addresses = ["10.0.0.0/24"] + destination_ports = ["*"] + } + } + + network_rule_collection { + name = "network_rule_collection2" + priority = 300 + action = "Deny" + rule { + name = "network_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.0.0.0/16"] + destination_addresses = ["192.168.1.1", "192.168.1.2"] + destination_ports = ["*"] + } + } + +} + +module "virtual_network_gateway" { + source = "Azure/avm-ptn-vnetgateway/azurerm" + version = "~> 0.3.0" + + for_each = local.module_virtual_network_gateway + + location = each.value.location + name = each.value.name + sku = try(each.value.sku, null) + 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) + enable_telemetry = false + 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) + + providers = { + azurerm = azurerm.connectivity + } +} + +resource "azurerm_public_ip" "bastion_public_ip" { + name = "bastion-pip-${var.default_postfix}" + location = var.default_location + resource_group_name = "rg-connectivity" + allocation_method = "Static" + sku = "Standard" + depends_on = [ + module.hubnetworking + ] + provider = azurerm.connectivity + +} + + +module "azure_bastion" { + source = "Azure/avm-res-network-bastionhost/azurerm" + version = "~> 0.3.0" + + name = "bastion-${var.default_postfix}" + location = var.default_location + resource_group_name = "rg-connectivity" + + ip_configuration = { + name = "bastion-ipconfig" + subnet_id = module.hubnetworking[0].virtual_networks["primary"].subnets_name_id["AzureBastionSubnet"] + public_ip_address_id = azurerm_public_ip.bastion_public_ip.id + } + + sku = "Standard" # Or "Basic" as per your requirement + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + azurerm_public_ip.bastion_public_ip + ] +} + + +module "vmjumpbox" { + source = "Azure/avm-res-compute-virtualmachine/azurerm" + version = "0.15.0" + + # Enable or disable telemetry, defining the variable if needed + enable_telemetry = false + location = var.default_location + resource_group_name = "rg-connectivity" + name = "vm-jumpbox" + sku_size = "Standard_D2s_v3" + zone = 1 + + # Set up the network interface and connect it to the SharedSubnet + network_interfaces = { + nic1 = { + name = "jumphost-nic-${var.default_postfix}" + ip_configurations = { + ipconfig1 = { + name = "jumphost-nic-${var.default_postfix}-ipconfig" + private_ip_subnet_resource_id = module.hubnetworking[0].virtual_networks["primary"].subnets_name_id["SharedSubnet"] + } + } + } + } + providers = { + azurerm = azurerm.connectivity + } + depends_on = [ + module.hubnetworking + ] +} diff --git a/templates/complete_hub_and_spoke/terraform.tf b/templates/complete_hub_and_spoke/terraform.tf new file mode 100644 index 00000000..3310cf42 --- /dev/null +++ b/templates/complete_hub_and_spoke/terraform.tf @@ -0,0 +1,26 @@ +terraform { + required_version = "~> 1.6" + required_providers { + azurerm = "~> 3.88" + } + # backend "azurerm" {} +} + +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_hub_and_spoke/terraform.tfvars b/templates/complete_hub_and_spoke/terraform.tfvars new file mode 100644 index 00000000..0014a82b --- /dev/null +++ b/templates/complete_hub_and_spoke/terraform.tfvars @@ -0,0 +1,4 @@ +subscription_id_connectivity="8dfc81b4-9732-4b10-88ad-07cf9a644863" +subscription_id_identity="8cbe85ef-65aa-4539-a13c-4476f32f9610" +subscription_id_management="5fe15bcc-5275-4922-8181-217ee69e0e9b" +default_location="swedencentral" \ No newline at end of file diff --git a/templates/complete_hub_and_spoke/variables.tf b/templates/complete_hub_and_spoke/variables.tf new file mode 100644 index 00000000..e8059e42 --- /dev/null +++ b/templates/complete_hub_and_spoke/variables.tf @@ -0,0 +1,37 @@ +variable "default_location" { + type = 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 = "" + 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_virtual_wan/config.yaml b/templates/complete_virtual_wan/config.yaml new file mode 100644 index 00000000..19d739e6 --- /dev/null +++ b/templates/complete_virtual_wan/config.yaml @@ -0,0 +1,102 @@ +# 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: +# `default_location`: This is an Azure location sourced from the `default_location` variable. This can be used to set the location 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`. +--- +archetypes: # `caf-enterprise-scale` module, add inputs as listed on the module registry where necessary. + root_name: es + root_id: Enterprise-Scale + 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_corp_landing_zones: true + deploy_online_landing_zones: true + default_location: ${default_location} + disable_telemetry: true + deploy_management_resources: true + configure_management_resources: + location: ${default_location} + settings: + security_center: + config: + email_security_contact: "security_contact@replace_me" + advanced: + asc_export_resource_group_name: rg-asc-export + custom_settings_by_resource_type: + azurerm_resource_group: + management: + name: rg-management + azurerm_log_analytics_workspace: + management: + name: log-management + azurerm_automation_account: + management: + name: aa-management +connectivity: + hubnetworking: # `hubnetworking` module, add inputs as listed on the module registry where necessary. + hub_virtual_networks: + shared_services: + resource_group_lock_enabled: false + name: vnet-shared-services + resource_group_name: rg-connectivity + location: ${default_location} + address_space: + - 10.5.0.0/16 + subnets: + AzureBastionSubnet: + name: "AzureBastionSubnet" + address_prefixes: ["10.5.1.0/27"] + assign_generated_route_table: false + SharedSubnet: + name: "SharedSubnet" + address_prefixes: ["10.5.2.0/27"] + assign_generated_route_table: false + vwan: # `avm-ptn-virtualwan` module, add inputs as listed on the module registry where necessary. + create_resource_group: false + enable_telemetry: false + virtual_network_connections: null + custom_routing_table: # Define if not already existing in your yaml + rt_bastion: + name: "rt-bastion" + routes: + - name: "Internet_Direct" + address_prefixes: ["0.0.0.0/0"] + next_hop: "Internet" + - name: "To_VM_Subnets" + address_prefixes: ["11.0.0.0/16"] # Replace with actual VM subnet IP ranges + next_hop: "10.0.1.4" # Replace with actual Azure Firewall private IP + routing_intents: + private-routing-intent: + name: "private-routing-intent" + virtual_hub_key: "vhub1" # The key used in the virtual_hubs must match + routing_policies: + - name: "routing-policy-private" + destinations: ["PrivateTraffic"] + next_hop_firewall_key: "fw_default" + - name: "routing-policy-internet" + destinations: ["Internet"] + next_hop_firewall_key: "fw_default" + location: ${default_location} + virtual_wan_name: "vwan" + resource_group_name: "rg-connectivity" + allow_branch_to_branch_traffic: true + disable_vpn_encryption: false + virtual_hubs: + vhub1: + location: ${default_location} + name: "vhub1" + address_prefix: "10.0.0.0/24" + firewall: # New firewall configuration + fw_default: + virtual_hub_key: "vhub1" + sku_name: "AZFW_Hub" + sku_tier: "Standard" + tags: + environment: "avm-vwan-testing" + deployment: "terraform" + diff --git a/templates/complete_virtual_wan/data.tf b/templates/complete_virtual_wan/data.tf new file mode 100644 index 00000000..d5783ec3 --- /dev/null +++ b/templates/complete_virtual_wan/data.tf @@ -0,0 +1 @@ +data "azurerm_client_config" "core" {} diff --git a/templates/complete_virtual_wan/locals.tf b/templates/complete_virtual_wan/locals.tf new file mode 100644 index 00000000..2bdef208 --- /dev/null +++ b/templates/complete_virtual_wan/locals.tf @@ -0,0 +1,68 @@ +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.yaml" : basename(var.configuration_file_path) + config_file_split = split(".", local.config_file_name) + const_yaml = "yaml" + const_yml = "yml" +} +locals { + config = (local.config_file_extension == local.const_yaml ? + yamldecode(templatefile("${path.module}/${local.config_file_name}", local.config_template_file_variables)) : + jsondecode(templatefile("${path.module}/${local.config_file_name}", local.config_template_file_variables)) + ) + config_template_file_variables = { + default_location = var.default_location + default_postfix = var.default_postfix + root_parent_management_group_id = var.root_parent_management_group_id == "" ? data.azurerm_client_config.core.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 + } +} +locals { + archetypes = try(merge(local.config.archetypes, {}), {}) +} +locals { + hub_virtual_networks = try(merge(local.config.connectivity.hubnetworking.hub_virtual_networks, {}), {}) + module_hubnetworking = { + 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.hubnetworking[0].virtual_networks[key].id + } + ) + if can(hub_virtual_network.virtual_network_gateway) + } +} +locals { + module_vwan = try(merge(local.config.connectivity.vwan, {}), {}) +} + +locals { + firewalls = { + "fw_default" = { + name = "fw-avm-vwan", + sku_name = local.module_vwan.firewall["fw_default"].sku_name, + sku_tier = local.module_vwan.firewall["fw_default"].sku_tier, + firewall_policy_id = azurerm_firewall_policy.this.id + tags = local.module_vwan.firewall["fw_default"].tags, + virtual_hub_key = local.module_vwan.firewall["fw_default"].virtual_hub_key + } + } +} + + +locals { + custom_routing_table = { + name = "rt-bastion", # Change this to the actual name specified in your YAML + routes = yamldecode(file("${path.module}/config.yaml")).connectivity.vwan.custom_routing_table.rt_bastion.routes + } +} diff --git a/templates/complete_virtual_wan/main.tf b/templates/complete_virtual_wan/main.tf new file mode 100644 index 00000000..a086e447 --- /dev/null +++ b/templates/complete_virtual_wan/main.tf @@ -0,0 +1,279 @@ +/** + * This Terraform configuration file sets up the infrastructure for a complete virtual WAN deployment. + * It uses modules to provision resources such as enterprise scale, hub networking, and virtual WAN. + * The configuration includes various settings and options for customization. + * + * Modules: + * - `module.enterprise_scale`: Sets up the gouvernance components such as azure policies and management groups + * - `module.hubnetworking`: Configures a shared virtual network to be used for jumpbox and bastion. + * - `module.vwan`: Deploys the virtual WAN infrastructure. + * + * Resources: + * - `azurerm_virtual_hub_connection.example_connection`: Establishes a connection between the virtual hub and a the shared virtual network. + * - `azurerm_firewall_policy.this`: Creates a firewall policy for the virtual WAN. + * - `azurerm_firewall_policy_rule_collection_group.example`: Defines rule collections for the firewall policy. + * - `azurerm_virtual_machine.bastion`: Deploys a bastion host for secure remote access. + * - `azurerm_virtual_machine.jumpbox`: Deploys a jumpbox for administrative access. + * + * Note: This code is part of a larger Terraform project and may have dependencies on other files and modules. + */ + + +module "enterprise_scale" { + source = "Azure/caf-enterprise-scale/azurerm" + version = "~> 5.2.0" + + count = length(local.archetypes) > 0 ? 1 : 0 + + disable_telemetry = try(local.archetypes.disable_telemetry, true) + default_location = try(local.archetypes.default_location, var.default_location) + root_parent_id = try(local.archetypes.root_parent_id, data.azurerm_client_config.core.tenant_id) + archetype_config_overrides = try(local.archetypes.archetype_config_overrides, {}) + configure_connectivity_resources = try(local.archetypes.configure_connectivity_resources, {}) + configure_identity_resources = try(local.archetypes.configure_identity_resources, {}) + configure_management_resources = try(local.archetypes.configure_management_resources, {}) + create_duration_delay = try(local.archetypes.create_duration_delay, {}) + custom_landing_zones = try(local.archetypes.custom_landing_zones, {}) + custom_policy_roles = try(local.archetypes.custom_policy_roles, {}) + default_tags = try(local.archetypes.default_tags, {}) + deploy_connectivity_resources = try(local.archetypes.deploy_connectivity_resources, false) + deploy_core_landing_zones = try(local.archetypes.deploy_core_landing_zones, true) + deploy_corp_landing_zones = try(local.archetypes.deploy_corp_landing_zones, false) + deploy_demo_landing_zones = try(local.archetypes.deploy_demo_landing_zones, false) + deploy_diagnostics_for_mg = try(local.archetypes.deploy_diagnostics_for_mg, false) + deploy_identity_resources = try(local.archetypes.deploy_identity_resources, false) + deploy_management_resources = try(local.archetypes.deploy_management_resources, false) + deploy_online_landing_zones = try(local.archetypes.deploy_online_landing_zones, false) + deploy_sap_landing_zones = try(local.archetypes.deploy_sap_landing_zones, false) + destroy_duration_delay = try(local.archetypes.destroy_duration_delay, {}) + disable_base_module_tags = try(local.archetypes.disable_base_module_tags, false) + library_path = try(local.archetypes.library_path, "") + policy_non_compliance_message_default = try(local.archetypes.policy_non_compliance_message_default, "This resource {enforcementMode} be compliant with the assigned policy.") + policy_non_compliance_message_default_enabled = try(local.archetypes.policy_non_compliance_message_default_enabled, true) + policy_non_compliance_message_enabled = try(local.archetypes.policy_non_compliance_message_enabled, true) + policy_non_compliance_message_enforced_replacement = try(local.archetypes.policy_non_compliance_message_enforced_replacement, "must") + policy_non_compliance_message_enforcement_placeholder = try(local.archetypes.policy_non_compliance_message_enforcement_placeholder, "{enforcementMode}") + policy_non_compliance_message_not_enforced_replacement = try(local.archetypes.policy_non_compliance_message_not_enforced_replacement, "should") + policy_non_compliance_message_not_supported_definitions = try(local.archetypes.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.archetypes.resource_custom_timeouts, {}) + root_id = try(local.archetypes.root_id, "es") + root_name = try(local.archetypes.root_name, "Enterprise-Scale") + strict_subscription_association = try(local.archetypes.strict_subscription_association, true) + subscription_id_connectivity = try(local.archetypes.subscription_id_connectivity, var.subscription_id_connectivity) + subscription_id_identity = try(local.archetypes.subscription_id_identity, var.subscription_id_identity) + subscription_id_management = try(local.archetypes.subscription_id_management, var.subscription_id_management) + subscription_id_overrides = try(local.archetypes.subscription_id_overrides, {}) + template_file_variables = try(local.archetypes.template_file_variables, {}) + + providers = { + azurerm = azurerm + azurerm.connectivity = azurerm.connectivity + azurerm.management = azurerm.management + } +} + +module "hubnetworking" { + source = "Azure/hubnetworking/azurerm" + version = "~> 1.1.0" + + count = length(local.hub_virtual_networks) > 0 ? 1 : 0 + + hub_virtual_networks = local.module_hubnetworking.hub_virtual_networks + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + module.enterprise_scale, + + ] +} + +module "vwan" { + source = "Azure/avm-ptn-virtualwan/azurerm" + version = "~> 0.5.0" + + count = length(local.module_vwan) > 0 ? 1 : 0 + + allow_branch_to_branch_traffic = try(local.config.module_vwan.allow_branch_to_branch_traffic, null) + create_resource_group = try(local.module_vwan.create_resource_group, null) + disable_vpn_encryption = try(local.module_vwan.disable_vpn_encryption, null) + enable_telemetry = try(local.module_vwan.enable_telemetry, null) + er_circuit_connections = try(local.module_vwan.er_circuit_connections, {}) + expressroute_gateways = try(local.module_vwan.expressroute_gateways, {}) + firewalls = { + for fw_key, fw_value in local.firewalls : fw_key => { + virtual_hub_key = fw_value.virtual_hub_key + name = fw_value.name + sku_name = fw_value.sku_name + sku_tier = fw_value.sku_tier + firewall_policy_id = azurerm_firewall_policy.this.id + tags = fw_value.tags + } + } + office365_local_breakout_category = try(local.module_vwan.office365_local_breakout_category, null) + location = try(local.module_vwan.location, null) + p2s_gateway_vpn_server_configurations = try(local.module_vwan.p2s_gateway_vpn_server_configurations, {}) + p2s_gateways = try(local.module_vwan.p2s_gateways, {}) + resource_group_name = try(local.module_vwan.resource_group_name, null) + virtual_hubs = try(local.module_vwan.virtual_hubs, null) + virtual_wan_name = try(local.module_vwan.virtual_wan_name, null) + type = try(local.module_vwan.type, null) + routing_intents = try(local.module_vwan.routing_intents, null) + resource_group_tags = try(local.module_vwan.resource_group_tags, null) + virtual_wan_tags = try(local.module_vwan.virtual_wan_tags, null) + vpn_gateways = try(local.module_vwan.vpn_gateways, {}) + vpn_site_connections = try(local.module_vwan.vpn_site_connections, {}) + vpn_sites = try(local.module_vwan.vpn_sites, {}) + tags = try(local.module_vwan.tags, {}) + virtual_network_connections = {} + + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + module.enterprise_scale, + module.hubnetworking + ] +} + +resource "azurerm_virtual_hub_connection" "example_connection" { + name = "example-connection" + virtual_hub_id = module.vwan[0].virtual_hub_id[0] + remote_virtual_network_id = module.hubnetworking[0].virtual_networks["shared_services"].id + depends_on = [ + module.vwan, + module.hubnetworking + ] + provider= azurerm.connectivity +} + + +resource "azurerm_firewall_policy" "this" { + location = try(local.module_vwan.location, null) + name = "vhub-avm-vwan-fw-policy" + resource_group_name = try(local.module_vwan.resource_group_name, null) + provider = azurerm.connectivity +} + +resource "azurerm_firewall_policy_rule_collection_group" "example" { + name = "example-fwpolicy-rcg" + firewall_policy_id = azurerm_firewall_policy.this.id + priority = 500 + application_rule_collection { + name = "app_rule_collection1" + priority = 500 + action = "Deny" + rule { + name = "app_rule_collection1_rule1" + protocols { + type = "Http" + port = 80 + } + protocols { + type = "Https" + port = 443 + } + source_addresses = ["10.0.0.0/16"] + destination_fqdns = ["*.microsoft.com"] + } + } + + network_rule_collection { + name = "network_rule_collection1" + priority = 400 + action = "Allow" + rule { + name = "network_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.5.0.0/16"] + destination_addresses = ["10.0.0.0/24"] + destination_ports = ["*"] + } + } + + network_rule_collection { + name = "network_rule_collection2" + priority = 300 + action = "Deny" + rule { + name = "network_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.0.0.0/16"] + destination_addresses = ["192.168.1.1", "192.168.1.2"] + destination_ports = ["*"] + } + } + +} + +resource "azurerm_public_ip" "bastion_public_ip" { + name = "bastion-pip-${var.default_postfix}" + location = var.default_location + resource_group_name = "rg-connectivity" + allocation_method = "Static" + sku = "Standard" + depends_on = [ + module.hubnetworking + ] + provider = azurerm.connectivity + +} + +module "azure_bastion" { + source = "Azure/avm-res-network-bastionhost/azurerm" + version = "~> 0.3.0" + + name = "bastion-${var.default_postfix}" + location = var.default_location + resource_group_name = "rg-connectivity" + + ip_configuration = { + name = "bastion-ipconfig" + subnet_id = module.hubnetworking[0].virtual_networks["shared_services"].subnets_name_id["AzureBastionSubnet"] + public_ip_address_id = azurerm_public_ip.bastion_public_ip.id + } + + sku = "Standard" # Or "Basic" as per your requirement + providers = { + azurerm = azurerm.connectivity + } + + depends_on = [ + azurerm_public_ip.bastion_public_ip + ] +} + +module "vmjumpbox" { + source = "Azure/avm-res-compute-virtualmachine/azurerm" + version = "0.15.0" + + # Enable or disable telemetry, defining the variable if needed + enable_telemetry = false + location = var.default_location + resource_group_name = "rg-connectivity" + name = "vm-jumpbox" + sku_size = "Standard_D2s_v3" + zone = 1 + + # Set up the network interface and connect it to the SharedSubnet + network_interfaces = { + nic1 = { + name = "jumphost-nic-${var.default_postfix}" + ip_configurations = { + ipconfig1 = { + name = "jumphost-nic-${var.default_postfix}-ipconfig" + private_ip_subnet_resource_id = module.hubnetworking[0].virtual_networks["shared_services"].subnets_name_id["SharedSubnet"] + } + } + } + } + providers = { + azurerm = azurerm.connectivity + } + depends_on = [ + module.hubnetworking + ] +} diff --git a/templates/complete_virtual_wan/terraform.tf b/templates/complete_virtual_wan/terraform.tf new file mode 100644 index 00000000..3310cf42 --- /dev/null +++ b/templates/complete_virtual_wan/terraform.tf @@ -0,0 +1,26 @@ +terraform { + required_version = "~> 1.6" + required_providers { + azurerm = "~> 3.88" + } + # backend "azurerm" {} +} + +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_virtual_wan/terraform.tfvars b/templates/complete_virtual_wan/terraform.tfvars new file mode 100644 index 00000000..0014a82b --- /dev/null +++ b/templates/complete_virtual_wan/terraform.tfvars @@ -0,0 +1,4 @@ +subscription_id_connectivity="8dfc81b4-9732-4b10-88ad-07cf9a644863" +subscription_id_identity="8cbe85ef-65aa-4539-a13c-4476f32f9610" +subscription_id_management="5fe15bcc-5275-4922-8181-217ee69e0e9b" +default_location="swedencentral" \ No newline at end of file diff --git a/templates/complete_virtual_wan/variables.tf b/templates/complete_virtual_wan/variables.tf new file mode 100644 index 00000000..e8059e42 --- /dev/null +++ b/templates/complete_virtual_wan/variables.tf @@ -0,0 +1,37 @@ +variable "default_location" { + type = 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 = "" + 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" +}