From 9d57cd24511891e48bad5036936589c7cd6174f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Fri, 6 Oct 2023 16:07:08 +0200 Subject: [PATCH 01/11] lb refactored, todo examples --- examples/{vnet => lb}/.header.md | 0 examples/lb/README.md | 317 ++++++++++++++++++++++++ examples/lb/brownfield/main.tf | 46 ++++ examples/lb/example.tfvars | 87 +++++++ examples/{vnet => lb}/main.tf | 41 ++++ examples/{vnet => lb}/outputs.tf | 8 + examples/lb/variables.tf | 217 ++++++++++++++++ examples/{vnet => lb}/versions.tf | 0 examples/vnet/README.md | 195 --------------- examples/vnet/brownfield.tfvars | 32 --- examples/vnet/example.tfvars | 182 -------------- examples/vnet/main_test.go | 62 ----- examples/vnet/variables.tf | 106 -------- modules/loadbalancer/.header.md | 11 + modules/loadbalancer/README.md | 395 +++++++++++++++++++++++++----- modules/loadbalancer/main.tf | 120 +++++---- modules/loadbalancer/main_test.go | 11 - modules/loadbalancer/outputs.tf | 4 +- modules/loadbalancer/variables.tf | 321 +++++++++++------------- modules/loadbalancer/versions.tf | 2 +- 20 files changed, 1267 insertions(+), 890 deletions(-) rename examples/{vnet => lb}/.header.md (100%) create mode 100644 examples/lb/README.md create mode 100644 examples/lb/brownfield/main.tf create mode 100644 examples/lb/example.tfvars rename examples/{vnet => lb}/main.tf (51%) rename examples/{vnet => lb}/outputs.tf (96%) create mode 100644 examples/lb/variables.tf rename examples/{vnet => lb}/versions.tf (100%) delete mode 100644 examples/vnet/README.md delete mode 100644 examples/vnet/brownfield.tfvars delete mode 100644 examples/vnet/example.tfvars delete mode 100644 examples/vnet/main_test.go delete mode 100644 examples/vnet/variables.tf create mode 100644 modules/loadbalancer/.header.md delete mode 100644 modules/loadbalancer/main_test.go diff --git a/examples/vnet/.header.md b/examples/lb/.header.md similarity index 100% rename from examples/vnet/.header.md rename to examples/lb/.header.md diff --git a/examples/lb/README.md b/examples/lb/README.md new file mode 100644 index 00000000..fced2d91 --- /dev/null +++ b/examples/lb/README.md @@ -0,0 +1,317 @@ + +# VNET module sample + +A sample of using a VNET module with the new variables layout and usage of `optional` keyword. + +The `README` is also in new, document-style format. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`location`](#location) | `string` | The Azure region to use. +[`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`vnets`](#vnets) | `map` | A map defining VNETs. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`name_prefix`](#name_prefix) | `string` | A prefix that will be added to all created resources. +[`create_resource_group`](#create_resource_group) | `bool` | When set to `true` it will cause a Resource Group creation. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. + + + + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.2, < 2.0 + + +Providers used in this module: + +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`load_balancer` | - | ../../modules/loadbalancer | + + +Resources used in this module: + +- `resource_group` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `false`) when set to `true` will create a VNET, `false` will source an existing VNET. +- `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be a full resource name, including prefixes. +- `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET +- `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which the VNET will reside or is sourced from + +- `create_subnets` - (`bool`, optinoal, defaults to `true`) if `true`, create Subnets inside the Virtual Network, otherwise use source existing subnets +- `subnets` - (`map`, optional) map of Subnets to create or source, for details see [VNET module documentation](../../modules/vnet/README.md#subnets) + +- `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see [VNET module documentation](../../modules/vnet/README.md#network_security_groups) +- `route_tables` - (`map`, optional) map of Route Tables to create, for details see [VNET module documentation](../../modules/vnet/README.md#route_tables) + + +Type: + +```hcl +map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string), []) + resource_group_name = optional(string) + network_security_groups = optional(map(object({ + name = string + location = optional(string) + rules = optional(map(object({ + name = string + priority = number + direction = string + access = string + protocol = string + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + })), {}) + })), {}) + route_tables = optional(map(object({ + name = string + location = optional(string) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_in_ip_address = optional(string) + })) + })), {}) + create_subnets = optional(bool, true) + subnets = optional(map(object({ + name = string + address_prefixes = optional(list(string), []) + network_security_group_key = optional(string) + route_table_key = optional(string) + enable_storage_service_endpoint = optional(bool, false) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. + +Example: +```hcl +name_prefix = "test-" +``` + +NOTICE. This prefix is not applied to existing resources. If you plan to reuse i.e. a VNET please specify it's full name, even if it is also prefixed with the same value as the one in this property. + + +Type: string + +Default value: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. Name of the newly specified RG is controlled by `resource_group_name`. +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + + + +#### load_balancers + +A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. + +Following properties are available: + +- `name`: name of the Load Balancer resource. +- `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. +- `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. +- `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). +- `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. +- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. +- `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). +- `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: + - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener + - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure + - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG + - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener + - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer + - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet + - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: + - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule + - `port`: port used by the rule, for HA PORTS rule set this to `0` + +Example of a public Load Balancer: + +``` +"public_lb" = { + name = "https_app_lb" + network_security_group_name = "untrust_nsg" + network_security_allow_source_ips = ["1.2.3.4"] + avzones = ["1", "2", "3"] + frontend_ips = { + "https_app_1" = { + create_public_ip = true + rules = { + "balanceHttps" = { + protocol = "Tcp" + port = 443 + } + } + } + } +} +``` + +Example of a private Load Balancer with HA PORTS rule: + +``` +"private_lb" = { + name = "ha_ports_internal_lb + frontend_ips = { + "ha-ports" = { + vnet_key = "trust_vnet" + subnet_key = "trust_snet" + private_ip_address = "10.0.0.1" + rules = { + HA_PORTS = { + port = 0 + protocol = "All" + } + } + } + } +} +``` + + + +Type: + +```hcl +map(object({ + name = string + vnet_key = optional(string) + subnet_key = optional(string) + zones = optional(list(string), ["1", "2", "3"]) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gateway_load_balancer_frontend_ip_configuration_id = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number, 1024) + enable_tcp_reset = optional(bool, false) + idle_timeout_in_minutes = optional(number, 4) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + + + \ No newline at end of file diff --git a/examples/lb/brownfield/main.tf b/examples/lb/brownfield/main.tf new file mode 100644 index 00000000..55c22b8a --- /dev/null +++ b/examples/lb/brownfield/main.tf @@ -0,0 +1,46 @@ +terraform { + required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} + + +resource "azurerm_resource_group" "IPs" { + name = "fosix-lb-ips" + location = "North Europe" + # tags = var.tags +} + +resource "azurerm_public_ip" "this" { + for_each = { + sourced_frontend_zonal = ["1", "2", "3"] + sourced_frontend = null + } + + name = "fosix-${each.key}" + resource_group_name = azurerm_resource_group.IPs.name + sku = "Standard" + allocation_method = "Static" + location = azurerm_resource_group.IPs.location + zones = each.value + + # tags = var.tags +} + +resource "azurerm_network_security_group" "this" { + name = "fosix-existing-nsg" + resource_group_name = azurerm_resource_group.IPs.name + location = azurerm_resource_group.IPs.location + +} \ No newline at end of file diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars new file mode 100644 index 00000000..0693c02a --- /dev/null +++ b/examples/lb/example.tfvars @@ -0,0 +1,87 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "lb-refactor" +name_prefix = "fosix-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + + +# --- VNET PART --- # +vnets = { + "transit" = { + name = "transit" + address_space = ["10.0.0.0/25"] + network_security_groups = { + "public" = { name = "public" } + } + subnets = { + "private" = { + name = "private-snet" + address_prefixes = ["10.0.0.16/28"] + } + "public" = { + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group = "public" + } + } + } +} + +load_balancers = { + "public" = { + name = "public-lb" + # nsg_auto_rules_settings = { + # nsg_name = "fosix-existing-nsg" + # nsg_resource_group_name = "fosix-lb-ips" + # # nsg_vnet_key = "transit" + # # nsg_key = "public" + # source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer + # base_priority = 200 + # } + zones = ["1", "2", "3"] + frontend_ips = { + "default_front" = { + name = "default-public-frontend" + # public_ip_name = "frontend-pip" + # create_public_ip = true + public_ip_name = "fosix-sourced_frontend_zonal" + public_ip_resource_group = "fosix-lb-ips" + in_rules = { + "balanceHttp" = { + name = "HTTP" + protocol = "Tcp" + port = 80 + } + } + out_rules = { + default = { + name = "default-out" + protocol = "All" + } + } + } + } + } + "private" = { + name = "private-lb" + avzones = null + frontend_ips = { + "ha-ports" = { + name = "HA" + vnet_key = "transit" + subnet_key = "private" + private_ip_address = "10.0.0.21" + in_rules = { + HA_PORTS = { + name = "HA" + port = 0 + protocol = "All" + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/vnet/main.tf b/examples/lb/main.tf similarity index 51% rename from examples/vnet/main.tf rename to examples/lb/main.tf index 46c34da1..3a226004 100644 --- a/examples/vnet/main.tf +++ b/examples/lb/main.tf @@ -41,3 +41,44 @@ module "vnet" { tags = var.tags } + +module "load_balancer" { + source = "../../modules/loadbalancer" + + for_each = var.load_balancers + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + zones = each.value.zones + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, + null + ) + + frontend_ips = { + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) + } + + tags = var.tags + depends_on = [module.vnet] +} \ No newline at end of file diff --git a/examples/vnet/outputs.tf b/examples/lb/outputs.tf similarity index 96% rename from examples/vnet/outputs.tf rename to examples/lb/outputs.tf index 892ab61b..e1ec9160 100644 --- a/examples/vnet/outputs.tf +++ b/examples/lb/outputs.tf @@ -1,3 +1,11 @@ +# output "fronts" { +# value = local.fronts +# } + + + + + # output "username" { # description = "Initial administrative username to use for VM-Series." # value = var.vmseries_username diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf new file mode 100644 index 00000000..ba5b23ea --- /dev/null +++ b/examples/lb/variables.tf @@ -0,0 +1,217 @@ +### GENERAL +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "name_prefix" { + description = <<-EOF + A prefix that will be added to all created resources. + There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. + + Example: + ```hcl + name_prefix = "test-" + ``` + + NOTICE. This prefix is not applied to existing resources. If you plan to reuse i.e. a VNET please specify it's full name, even if it is also prefixed with the same value as the one in this property. + EOF + default = "" + type = string +} + +variable "create_resource_group" { + description = <<-EOF + When set to `true` it will cause a Resource Group creation. Name of the newly specified RG is controlled by `resource_group_name`. + When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + EOF + default = true + type = bool +} + +variable "resource_group_name" { + description = "Name of the Resource Group." + type = string +} + +### VNET +variable "vnets" { + description = <<-EOF + A map defining VNETs. + + For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + + - `create_virtual_network` - (`bool`, optional, defaults to `false`) when set to `true` will create a VNET, `false` will source an existing VNET. + - `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be a full resource name, including prefixes. + - `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET + - `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which the VNET will reside or is sourced from + + - `create_subnets` - (`bool`, optinoal, defaults to `true`) if `true`, create Subnets inside the Virtual Network, otherwise use source existing subnets + - `subnets` - (`map`, optional) map of Subnets to create or source, for details see [VNET module documentation](../../modules/vnet/README.md#subnets) + + - `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see [VNET module documentation](../../modules/vnet/README.md#network_security_groups) + - `route_tables` - (`map`, optional) map of Route Tables to create, for details see [VNET module documentation](../../modules/vnet/README.md#route_tables) + EOF + + type = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string), []) + resource_group_name = optional(string) + network_security_groups = optional(map(object({ + name = string + location = optional(string) + rules = optional(map(object({ + name = string + priority = number + direction = string + access = string + protocol = string + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + })), {}) + })), {}) + route_tables = optional(map(object({ + name = string + location = optional(string) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_in_ip_address = optional(string) + })) + })), {}) + create_subnets = optional(bool, true) + subnets = optional(map(object({ + name = string + address_prefixes = optional(list(string), []) + network_security_group_key = optional(string) + route_table_key = optional(string) + enable_storage_service_endpoint = optional(bool, false) + })), {}) + })) +} + +variable "load_balancers" { + description = <<-EOF + A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. + + Following properties are available: + + - `name`: name of the Load Balancer resource. + - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. + - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. + - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). + - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. + - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. + - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). + - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: + - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener + - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure + - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG + - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener + - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer + - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet + - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: + - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule + - `port`: port used by the rule, for HA PORTS rule set this to `0` + + Example of a public Load Balancer: + + ``` + "public_lb" = { + name = "https_app_lb" + network_security_group_name = "untrust_nsg" + network_security_allow_source_ips = ["1.2.3.4"] + avzones = ["1", "2", "3"] + frontend_ips = { + "https_app_1" = { + create_public_ip = true + rules = { + "balanceHttps" = { + protocol = "Tcp" + port = 443 + } + } + } + } + } + ``` + + Example of a private Load Balancer with HA PORTS rule: + + ``` + "private_lb" = { + name = "ha_ports_internal_lb + frontend_ips = { + "ha-ports" = { + vnet_key = "trust_vnet" + subnet_key = "trust_snet" + private_ip_address = "10.0.0.1" + rules = { + HA_PORTS = { + port = 0 + protocol = "All" + } + } + } + } + } + ``` + + EOF + default = {} + nullable = false + type = map(object({ + name = string + vnet_key = optional(string) + subnet_key = optional(string) + zones = optional(list(string), ["1", "2", "3"]) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gateway_load_balancer_frontend_ip_configuration_id = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number, 1024) + enable_tcp_reset = optional(bool, false) + idle_timeout_in_minutes = optional(number, 4) + })), {}) + })), {}) + })) +} \ No newline at end of file diff --git a/examples/vnet/versions.tf b/examples/lb/versions.tf similarity index 100% rename from examples/vnet/versions.tf rename to examples/lb/versions.tf diff --git a/examples/vnet/README.md b/examples/vnet/README.md deleted file mode 100644 index ae4ce653..00000000 --- a/examples/vnet/README.md +++ /dev/null @@ -1,195 +0,0 @@ - -# VNET module sample - -A sample of using a VNET module with the new variables layout and usage of `optional` keyword. - -The `README` is also in new, document-style format. - -## Module's Required Inputs - -Name | Type | Description ---- | --- | --- -[`location`](#location) | `string` | The Azure region to use. -[`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`vnets`](#vnets) | `map` | A map defining VNETs. - - -## Module's Optional Inputs - -Name | Type | Description ---- | --- | --- -[`tags`](#tags) | `map` | Map of tags to assign to the created resources. -[`name_prefix`](#name_prefix) | `string` | A prefix that will be added to all created resources. -[`create_resource_group`](#create_resource_group) | `bool` | When set to `true` it will cause a Resource Group creation. - - - - -## Module's Nameplate - - -Requirements needed by this module: - -- `terraform`, version: >= 1.2, < 2.0 - - -Providers used in this module: - -- `azurerm` - - -Modules used in this module: -Name | Version | Source | Description ---- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. - - -Resources used in this module: - -- `resource_group` (managed) -- `resource_group` (data) - -## Inputs/Outpus details - -### Required Inputs - - - -#### location - -The Azure region to use. - -Type: string - -[back to list](#modules-required-inputs) - - - -#### resource_group_name - -Name of the Resource Group. - -Type: string - -[back to list](#modules-required-inputs) - -#### vnets - -A map defining VNETs. - -For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - -- `create_virtual_network` - (`bool`, optional, defaults to `false`) when set to `true` will create a VNET, `false` will source an existing VNET. -- `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be a full resource name, including prefixes. -- `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET -- `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which the VNET will reside or is sourced from - -- `create_subnets` - (`bool`, optinoal, defaults to `true`) if `true`, create Subnets inside the Virtual Network, otherwise use source existing subnets -- `subnets` - (`map`, optional) map of Subnets to create or source, for details see [VNET module documentation](../../modules/vnet/README.md#subnets) - -- `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see [VNET module documentation](../../modules/vnet/README.md#network_security_groups) -- `route_tables` - (`map`, optional) map of Route Tables to create, for details see [VNET module documentation](../../modules/vnet/README.md#route_tables) - - -Type: - -```hcl -map(object({ - name = string - create_virtual_network = optional(bool, true) - address_space = optional(list(string), []) - resource_group_name = optional(string) - network_security_groups = optional(map(object({ - name = string - location = optional(string) - rules = optional(map(object({ - name = string - priority = number - direction = string - access = string - protocol = string - source_port_range = optional(string) - source_port_ranges = optional(list(string)) - destination_port_range = optional(string) - destination_port_ranges = optional(list(string)) - source_address_prefix = optional(string) - source_address_prefixes = optional(list(string)) - destination_address_prefix = optional(string) - destination_address_prefixes = optional(list(string)) - })), {}) - })), {}) - route_tables = optional(map(object({ - name = string - location = optional(string) - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_in_ip_address = optional(string) - })) - })), {}) - create_subnets = optional(bool, true) - subnets = optional(map(object({ - name = string - address_prefixes = optional(list(string), []) - network_security_group_key = optional(string) - route_table_key = optional(string) - enable_storage_service_endpoint = optional(bool, false) - })), {}) - })) -``` - - -[back to list](#modules-required-inputs) - - - -### Optional Inputs - - -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -[back to list](#modules-optional-inputs) - - -#### name_prefix - -A prefix that will be added to all created resources. -There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. - -Example: -```hcl -name_prefix = "test-" -``` - -NOTICE. This prefix is not applied to existing resources. If you plan to reuse i.e. a VNET please specify it's full name, even if it is also prefixed with the same value as the one in this property. - - -Type: string - -Default value: `` - -[back to list](#modules-optional-inputs) - -#### create_resource_group - -When set to `true` it will cause a Resource Group creation. Name of the newly specified RG is controlled by `resource_group_name`. -When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. - - -Type: bool - -Default value: `true` - -[back to list](#modules-optional-inputs) - - - - - \ No newline at end of file diff --git a/examples/vnet/brownfield.tfvars b/examples/vnet/brownfield.tfvars deleted file mode 100644 index 5f99eb82..00000000 --- a/examples/vnet/brownfield.tfvars +++ /dev/null @@ -1,32 +0,0 @@ -# --- GENERAL --- # -location = "North Europe" -resource_group_name = "transit-vnet-brownfield" -name_prefix = "fosix-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - - -# --- VNET PART --- # -vnets = { - simple = { - name = "simple-vnet" - address_space = ["10.100.0.0/24"] - } - subnetted = { - name = "subnetted-vnet" - address_space = ["10.100.1.0/24"] - subnets = { - subnet_a = { - name = "subnet_a" - address_prefixes = ["10.100.1.0/25"] - enable_storage_service_endpoint = true - } - subnet_b = { - name = "subnet_b" - address_prefixes = ["10.100.1.128/25"] - } - } - } -} diff --git a/examples/vnet/example.tfvars b/examples/vnet/example.tfvars deleted file mode 100644 index f1c2a951..00000000 --- a/examples/vnet/example.tfvars +++ /dev/null @@ -1,182 +0,0 @@ -# --- GENERAL --- # -location = "North Europe" -resource_group_name = "transit-vnet-common" -name_prefix = "fosix-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - - -# --- VNET PART --- # -vnets = { - simple = { - create_virtual_network = false - name = "simple-vnet" - resource_group_name = "fosix-transit-vnet-brownfield" - subnets = { - a_snet = { - name = "a_snet" - address_prefixes = ["10.100.0.0/24"] - } - } - } - subnetted = { - create_virtual_network = false - name = "subnetted-vnet" - resource_group_name = "fosix-transit-vnet-brownfield" - create_subnets = false - subnets = { - subnet_a = { name = "fosix-subnet_a" } - subnet_b = { name = "fosix-subnet_b" } - } - } - empty = { - name = "empty" - address_space = ["10.0.1.0/25"] - } - non-empty = { - name = "non-empty" - address_space = ["10.0.0.0/24"] - network_security_groups = { - "nsg" = { - name = "nsg" - rules = { - "a_rule" = { - name = "a_rule_name" - priority = 100 - direction = "Inbound" - access = "Allow" - protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] # TODO: whitelist public IP addresses that will be used to manage the appliances - source_port_range = "*" - destination_address_prefix = "10.0.0.0/28" - destination_port_ranges = ["22", "443"] - } - } - } - } - route_tables = { - "rt" = { - name = "a_udr" - routes = { - "udr" = { - name = "udr" - address_prefix = "10.0.0.0/8" - next_hop_type = "None" - } - } - } - } - subnets = { - "some_subnet" = { - name = "some-subnet" - address_prefixes = ["10.0.0.0/25"] - network_security_group_key = "nsg" - route_table_key = "rt" - } - } - } - # "transit" = { - # name = "transit" - # address_space = ["10.0.0.0/25"] - # network_security_groups = { - # "management" = { - # name = "mgmt-nsg" - # rules = { - # inbound = { - # name = "mgmt_allow_inbound" - # priority = 100 - # direction = "Inbound" - # access = "Allow" - # protocol = "Tcp" - # source_address_prefixes = ["1.2.3.4"] # TODO: whitelist public IP addresses that will be used to manage the appliances - # source_port_range = "*" - # destination_address_prefix = "10.0.0.0/28" - # destination_port_ranges = ["22", "443"] - # } - # } - # } - # "public" = { - # name = "public-nsg" - # } - # } - # route_tables = { - # "management" = { - # name = "mgmt-rt" - # routes = { - # "private_blackhole" = { - # name = "private_blackhole" - # address_prefix = "10.0.0.16/28" - # next_hop_type = "None" - # } - # "public_blackhole" = { - # name = "public_blackhole" - # address_prefix = "10.0.0.32/28" - # next_hop_type = "None" - # } - # } - # } - # "private" = { - # name = "private-rt" - # routes = { - # "default" = { - # name = "default" - # address_prefix = "0.0.0.0/0" - # next_hop_type = "VirtualAppliance" - # next_hop_in_ip_address = "10.0.0.30" - # } - # "mgmt_blackhole" = { - # name = "mgmt_blackhole" - # address_prefix = "10.0.0.0/28" - # next_hop_type = "None" - # } - # "public_blackhole" = { - # name = "public_blackhole" - # address_prefix = "10.0.0.32/28" - # next_hop_type = "None" - # } - # } - # } - # "public" = { - # name = "public-rt" - # routes = { - # "mgmt_blackhole" = { - # name = "mgmt_blackhole" - # address_prefix = "10.0.0.0/28" - # next_hop_type = "None" - # } - # "private_blackhole" = { - # name = "private_blackhole" - # address_prefix = "10.0.0.16/28" - # next_hop_type = "None" - # } - # } - # } - # } - # subnets = { - # "management" = { - # name = "mgmt-snet" - # address_prefixes = ["10.0.0.0/28"] - # network_security_group = "management" - # route_table = "management" - # enable_storage_service_endpoint = true - # } - # "private" = { - # name = "private-snet" - # address_prefixes = ["10.0.0.16/28"] - # route_table = "private" - # } - # "public" = { - # name = "public-snet" - # address_prefixes = ["10.0.0.32/28"] - # network_security_group = "public" - # route_table = "public" - # } - # "appgw" = { - # name = "appgw-snet" - # address_prefixes = ["10.0.0.48/28"] - # } - # } - # } -} diff --git a/examples/vnet/main_test.go b/examples/vnet/main_test.go deleted file mode 100644 index ba72cbc9..00000000 --- a/examples/vnet/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package common_vmseries - -import ( - "testing" - - "github.com/gruntwork-io/terratest/modules/logger" - "github.com/gruntwork-io/terratest/modules/terraform" - - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" -) - -func CreateTerraformOptions(t *testing.T) *terraform.Options { - // prepare random prefix - randomNames, _ := testskeleton.GenerateTerraformVarsInfo("azure") - - // define options for Terraform - terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ - TerraformDir: ".", - VarFiles: []string{"example.tfvars"}, - Vars: map[string]interface{}{ - "name_prefix": randomNames.NamePrefix, - "resource_group_name": randomNames.AzureResourceGroupName, - }, - Logger: logger.Default, - Lock: true, - Upgrade: true, - SetVarsAfterVarFiles: true, - }) - - return terraformOptions -} - -func TestValidate(t *testing.T) { - testskeleton.ValidateCode(t, nil) -} - -func TestPlan(t *testing.T) { - // define options for Terraform - terraformOptions := CreateTerraformOptions(t) - // prepare list of items to check - assertList := []testskeleton.AssertExpression{} - // plan test infrastructure and verify outputs - testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") -} - -func TestApply(t *testing.T) { - // define options for Terraform - terraformOptions := CreateTerraformOptions(t) - // prepare list of items to check - assertList := []testskeleton.AssertExpression{} - // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment - testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) -} - -func TestIdempotence(t *testing.T) { - // define options for Terraform - terraformOptions := CreateTerraformOptions(t) - // prepare list of items to check - assertList := []testskeleton.AssertExpression{} - // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment - testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) -} diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf deleted file mode 100644 index a0cea61d..00000000 --- a/examples/vnet/variables.tf +++ /dev/null @@ -1,106 +0,0 @@ -### GENERAL -variable "tags" { - description = "Map of tags to assign to the created resources." - default = {} - type = map(string) -} - -variable "location" { - description = "The Azure region to use." - type = string -} - -variable "name_prefix" { - description = <<-EOF - A prefix that will be added to all created resources. - There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. - - Example: - ```hcl - name_prefix = "test-" - ``` - - NOTICE. This prefix is not applied to existing resources. If you plan to reuse i.e. a VNET please specify it's full name, even if it is also prefixed with the same value as the one in this property. - EOF - default = "" - type = string -} - -variable "create_resource_group" { - description = <<-EOF - When set to `true` it will cause a Resource Group creation. Name of the newly specified RG is controlled by `resource_group_name`. - When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. - EOF - default = true - type = bool -} - -variable "resource_group_name" { - description = "Name of the Resource Group." - type = string -} - - -### VNET -variable "vnets" { - description = <<-EOF - A map defining VNETs. - - For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - - `create_virtual_network` - (`bool`, optional, defaults to `false`) when set to `true` will create a VNET, `false` will source an existing VNET. - - `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be a full resource name, including prefixes. - - `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET - - `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which the VNET will reside or is sourced from - - - `create_subnets` - (`bool`, optinoal, defaults to `true`) if `true`, create Subnets inside the Virtual Network, otherwise use source existing subnets - - `subnets` - (`map`, optional) map of Subnets to create or source, for details see [VNET module documentation](../../modules/vnet/README.md#subnets) - - - `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see [VNET module documentation](../../modules/vnet/README.md#network_security_groups) - - `route_tables` - (`map`, optional) map of Route Tables to create, for details see [VNET module documentation](../../modules/vnet/README.md#route_tables) - EOF - - type = map(object({ - name = string - create_virtual_network = optional(bool, true) - address_space = optional(list(string), []) - resource_group_name = optional(string) - network_security_groups = optional(map(object({ - name = string - location = optional(string) - rules = optional(map(object({ - name = string - priority = number - direction = string - access = string - protocol = string - source_port_range = optional(string) - source_port_ranges = optional(list(string)) - destination_port_range = optional(string) - destination_port_ranges = optional(list(string)) - source_address_prefix = optional(string) - source_address_prefixes = optional(list(string)) - destination_address_prefix = optional(string) - destination_address_prefixes = optional(list(string)) - })), {}) - })), {}) - route_tables = optional(map(object({ - name = string - location = optional(string) - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_in_ip_address = optional(string) - })) - })), {}) - create_subnets = optional(bool, true) - subnets = optional(map(object({ - name = string - address_prefixes = optional(list(string), []) - network_security_group_key = optional(string) - route_table_key = optional(string) - enable_storage_service_endpoint = optional(bool, false) - })), {}) - })) -} diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md new file mode 100644 index 00000000..148748c1 --- /dev/null +++ b/modules/loadbalancer/.header.md @@ -0,0 +1,11 @@ +# Load Balancer Module for Azure + +A Terraform module for deploying a Load Balancer for VM-Series firewalls. Supports both standalone and scale set deployments. Note, that due to that some properties are mutually exclusive. Please check the properties' description. + +The module creates a single load balancer and a single backend for it, but it allows multiple frontends. + +In case of a public load balancer, you can define outbound rules and use the frontend's public IP address to access the internet. If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix SNAT with outbound rules for a single backend. + +## Usage + +For usage see any of the reference architecture examples. diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index dcf6bfa9..e5937f3a 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -1,71 +1,344 @@ + # Load Balancer Module for Azure -A Terraform module for deploying a Load Balancer for VM-Series firewalls. Supports both standalone and scale set deployments. Supports either inbound or outbound configuration. +A Terraform module for deploying a Load Balancer for VM-Series firewalls. Supports both standalone and scale set deployments. Note, that due to that some properties are mutually exclusive. Please check the properties' description. The module creates a single load balancer and a single backend for it, but it allows multiple frontends. -In case of a public load balancer, reusing the same frontend for inbound and outbound rules is possible - to achieve this, a key in `outbound_rules` has to match a corresponding key from `frontend_ips`. +In case of a public load balancer, you can define outbound rules and use the frontend's public IP address to access the internet. If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix SNAT with outbound rules for a single backend. ## Usage For usage see any of the reference architecture examples. -## Reference - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_lb.lb](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb) | resource | -| [azurerm_lb_backend_address_pool.lb_backend](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool) | resource | -| [azurerm_lb_outbound_rule.out_rules](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_outbound_rule) | resource | -| [azurerm_lb_probe.probe](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe) | resource | -| [azurerm_lb_rule.in_rules](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule) | resource | -| [azurerm_network_security_rule.allow_inbound_ips](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [frontend\_ips](#input\_frontend\_ips) | A map of objects describing LB Frontend IP configurations, inbound and outbound rules. Used for both public or private load balancers.
Keys of the map are names of LB Frontend IP configurations.

Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. A key in this map is the name of the rule, while value is the actual rule configuration. To understand this structure please see examples below.

**Inbound rules.**

Here is a list of properties supported by each `in_rule`:

- `protocol` : required, communication protocol, either 'Tcp', 'Udp' or 'All'.
- `port` : required, communication port, this is both the front- and the backend port if `backend_port` is not given.
- `backend_port` : optional, this is the backend port to forward traffic to in the backend pool.
- `floating_ip` : optional, defaults to `true`, enables floating IP for this rule.
- `session_persistence` : optional, defaults to 5 tuple (Azure default), see `Session persistence/Load distribution` below for details.

Public LB

- `create_public_ip` : Optional. Set to `true` to create a public IP.
- `public_ip_name` : Ignored if `create_public_ip` is `true`. The existing public IP resource name to use.
- `public_ip_resource_group` : Ignored if `create_public_ip` is `true` or if `public_ip_name` is null. The name of the resource group which holds `public_ip_name`.

Example
frontend_ips = {
pip_existing = {
create_public_ip = false
public_ip_name = "my_ip"
public_ip_resource_group = "my_rg_name"
in_rules = {
HTTP = {
port = 80
protocol = "Tcp"
}
}
}
}
Forward to a different port on backend pool
frontend_ips = {
pip_existing = {
create_public_ip = false
public_ip_name = "my_ip"
public_ip_resource_group = "my_rg_name"
in_rules = {
HTTP = {
port = 80
backend_port = 8080
protocol = "Tcp"
}
}
}
}
Private LB

- `subnet_id` : Identifier of an existing subnet. This also trigger creation of an internal LB.
- `private_ip_address` : A static IP address of the Frontend IP configuration, has to be in limits of the subnet's (specified by `subnet_id`) address space. When not set, changes the address allocation from `Static` to `Dynamic`.

Example
frontend_ips = {
internal_fe = {
subnet_id = azurerm_subnet.this.id
private_ip_address = "192.168.0.10"
in_rules = {
HA_PORTS = {
port = 0
protocol = "All"
}
}
}
}
Session persistence/Load distribution

By default the Load Balancer uses a 5 tuple hash to map traffic to available servers. This can be controlled using `session_persistence` property defined inside a rule. Available values are:

- `Default` : this is the 5 tuple hash - this method is also used when no property is defined
- `SourceIP` : a 2 tuple hash is used
- `SourceIPProtocol` : a 3 tuple hash is used

Example
frontend_ips = {
rule_1 = {
create_public_ip = true
in_rules = {
HTTP = {
port = 80
protocol = "Tcp"
session_persistence = "SourceIP"
}
}
}
}
**Outbound rules.**

Each Frontend IP config can have outbound rules specified. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`.

Following properties are available:

- `protocol` : Protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted.
- `allocated_outbound_ports` : Number of ports allocated per instance. Defaults to `1024`.
- `enable_tcp_reset` : Ignored when `protocol` is set to `Udp`, defaults to `False` (Azure defaults).
- `idle_timeout_in_minutes` : Ignored when `protocol` is set to `Udp`. TCP connection timeout in case the connection is idle. Defaults to 4 minutes (Azure defaults).

Example:
frontend_ips = {
rule_1 = {
create_public_ip = true
in_rules = {
HTTP = {
port = 80
protocol = "Tcp"
session_persistence = "SourceIP"
}
}
out_rules = {
"outbound_tcp" = {
protocol = "Tcp"
allocated_outbound_ports = 2048
enable_tcp_reset = true
idle_timeout_in_minutes = 10
}
}
}
}
| `any` | n/a | yes | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of a pre-existing Resource Group to place the resources in. | `string` | n/a | yes | -| [location](#input\_location) | Region to deploy load balancer and dependencies. | `string` | n/a | yes | -| [backend\_name](#input\_backend\_name) | The name of the backend pool to create. All the frontends of the load balancer always use the same single backend. | `string` | `"vmseries_backend"` | no | -| [name](#input\_name) | The name of the load balancer. | `string` | n/a | yes | -| [probe\_name](#input\_probe\_name) | The name of the load balancer probe. | `string` | `"vmseries_probe"` | no | -| [probe\_port](#input\_probe\_port) | Health check port number of the load balancer probe. | `string` | `"80"` | no | -| [network\_security\_allow\_source\_ips](#input\_network\_security\_allow\_source\_ips) | List of IP CIDR ranges (such as `["192.168.0.0/16"]` or `["*"]`) from which the inbound traffic to all frontends should be allowed.
If it's empty, user is responsible for configuring a Network Security Group separately.
The list cannot include Azure tags like "Internet" or "Sql.EastUS". | `list(string)` | `[]` | no | -| [network\_security\_resource\_group\_name](#input\_network\_security\_resource\_group\_name) | Name of the Resource Group where the `network_security_group_name` resides. If empty, defaults to `resource_group_name`. | `string` | `""` | no | -| [network\_security\_group\_name](#input\_network\_security\_group\_name) | Name of the pre-existing Network Security Group (NSG) where to add auto-generated rules. Each NSG rule corresponds to a single `in_rule` on the load balancer.
User is responsible to associate the NSG with the load balancer's subnet, the module only supplies the rules.
If empty, user is responsible for configuring an NSG separately. | `string` | `null` | no | -| [network\_security\_base\_priority](#input\_network\_security\_base\_priority) | The base number from which the auto-generated priorities of the NSG rules grow.
Ignored if `network_security_group_name` is empty or if `network_security_allow_source_ips` is empty. | `number` | `1000` | no | -| [enable\_zones](#input\_enable\_zones) | If `false`, all the subnet-associated frontends and also all created Public IP addresses default to not to use Availability Zones (the `No-Zone` setting). It is intended for the regions that do not yet support Availability Zones. | `bool` | `true` | no | -| [tags](#input\_tags) | Azure tags to apply to the created resources. | `map(string)` | `{}` | no | -| [avzones](#input\_avzones) | Controls zones for load balancer's Fronted IP configurations. For:

* public IPs - these are regions in which the IP resource is available
* private IPs - this represents Zones to which Azure will deploy paths leading to this Frontend IP.

For public IPs, after provider version 3.x (Azure API upgrade) you need to specify all zones available in a region (typically 3), ie: for zone-redundant with 3 availability zone in current region value will be:
["1","2","3"]
| `list(string)` | `[]` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [backend\_pool\_id](#output\_backend\_pool\_id) | The identifier of the backend pool. | -| [frontend\_ip\_configs](#output\_frontend\_ip\_configs) | Map of IP addresses, one per each entry of `frontend_ips` input. Contains public IP address for the frontends that have it, private IP address otherwise. | -| [health\_probe](#output\_health\_probe) | The health probe object. | - +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the load balancer. +[`resource_group_name`](#resource_group_name) | `string` | Name of a pre-existing Resource Group to place the resources in. +[`location`](#location) | `string` | Region to deploy the resources. +[`frontend_ips`](#frontend_ips) | `map` | A map of objects describing LB Frontend IP configurations, inbound and outbound rules. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`zones`](#zones) | `list` | Controls zones for load balancer's Fronted IP configurations. +[`tags`](#tags) | `map` | Azure tags to apply to the created resources. +[`backend_name`](#backend_name) | `string` | The name of the backend pool to create. +[`health_probe`](#health_probe) | `object` | Backend's health probe definition. +[`nsg_auto_rules_settings`](#nsg_auto_rules_settings) | `object` | Controls automatic creation of NSG rules for all defined inbound rules. + + + +## Module's Outputs + +Name | Description +--- | --- +`backend_pool_id` | The identifier of the backend pool. +`frontend_ip_configs` | Map of IP addresses, one per each entry of `frontend_ips` input. Contains public IP address for the frontends that have it, private IP address otherwise. +`health_probe` | The health probe object. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.3, < 2.0 +- `azurerm`, version: ~> 3.25 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.25 + + + + +Resources used in this module: + +- `lb` (managed) +- `lb_backend_address_pool` (managed) +- `lb_outbound_rule` (managed) +- `lb_probe` (managed) +- `lb_rule` (managed) +- `network_security_rule` (managed) +- `public_ip` (managed) +- `public_ip` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the load balancer. + +Type: string + +[back to list](#modules-required-inputs) + +#### resource_group_name + +Name of a pre-existing Resource Group to place the resources in. + +Type: string + +[back to list](#modules-required-inputs) + +#### location + +Region to deploy the resources. + +Type: string + +[back to list](#modules-required-inputs) + + + +#### frontend_ips + +A map of objects describing LB Frontend IP configurations, inbound and outbound rules. + +Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. + +Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. To ease configuration they were grouped per Load Balancer type. + +Private Load Balancer: + +- `name` - (`string`, required) name of a frontend IP configuration +- `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer +- `private_ip_address` - (`string`, optional, defaults to `null`) when assigned it will become the IP address of the Load Balancer, when skipped the IP will be assigned from DHCP. +- `gateway_load_balancer_frontend_ip_configuration_id` - ???? +- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + +Public Load Balancer: + +- `name` - (`string`, required) name of a frontend IP configuration +- `public_ip_name` - (`string`, required) name of a public IP resource +- `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be created, otherwise an existing resource will be used; in both cases the name of the resource is controled by `public_ip_name` +- `public_ip_resource_group` - (`string`, optional, defaults to `null`) name of a Resource Group hosting an existing public IP resource +- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below +- `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below + +Below are the properties for the **inbound rules** map: + +- `name` - (`string`, required) a name of an inbound rule +- `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. +- `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports +- `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool +- `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. +- `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: + - `Default` : this is the 5 tuple hash + - `SourceIP` : a 2 tuple hash is used + - `SourceIPProtocol` : a 3 tuple hash is used +- `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) + +Below are the properties for outbound rules map. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: + +- `name` - (`string`, required) a name of an outbound rule +- `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted +- `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance +- `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` +- `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` + +Examples + +```hcl +# rules for a public LB, reusing an existing public IP and doing port translation +frontend_ips = { + pip_existing = { + create_public_ip = false + public_ip_name = "my_ip" + public_ip_resource_group = "my_rg_name" + in_rules = { + HTTP = { + port = 80 + protocol = "Tcp" + backend_port = 8080 + } + } + } +} + +# rules for a private LB, with a static private IP address and one HA PORTs rule +frontend_ips = { + internal = { + subnet_id = azurerm_subnet.this.id + private_ip_address = "192.168.0.10" + in_rules = { + HA_PORTS = { + port = 0 + protocol = "All" + } + } + } +} + +# rules for a public LB, session persistance with 2 tuple hash outbound rule defined +frontend_ips = { + rule_1 = { + create_public_ip = true + in_rules = { + HTTP = { + port = 80 + protocol = "Tcp" + session_persistence = "SourceIP" + } + } + } + out_rules = { + "outbound_tcp" = { + protocol = "Tcp" + allocated_outbound_ports = 2048 + enable_tcp_reset = true + idle_timeout_in_minutes = 10 + } + } +} +``` + + +Type: + +```hcl +map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + subnet_id = optional(string) + private_ip_address = optional(string) + gateway_load_balancer_frontend_ip_configuration_id = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number, 1024) + enable_tcp_reset = optional(bool, false) + idle_timeout_in_minutes = optional(number, 4) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + + + + + + +### Optional Inputs + + + + + +#### zones + +Controls zones for load balancer's Fronted IP configurations. + +For: + +- public IPs - these are zones in which the public IP resource is available +- private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs (all frontends are affected) + +Setting this variable to explicit `null` disables a zonal deployment. +This can be helpful in regions where Availability Zones are not available. + +For public Load Balancers, since this setting controls also Availability Zones for public IPs, you need to specify all zones available in a region (typically 3): `["1","2","3"]` + + +Type: list(string) + +Default value: `[1 2 3]` + +[back to list](#modules-optional-inputs) + +#### tags + +Azure tags to apply to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + + +#### backend_name + +The name of the backend pool to create. All frontends of the load balancer always use the same backend. + +Type: string + +Default value: `vmseries_backend` + +[back to list](#modules-optional-inputs) + +#### health_probe + +Backend's health probe definition. + +Following properties are available: + +- `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe +- `port` - (`number`, optional, defaults to `80`) port to run the probe against + + +Type: + +```hcl +object({ + name = optional(string, "vmseries_probe") + port = optional(number, 80) + }) +``` + + +Default value: `map[name:vmseries_probe port:80]` + +[back to list](#modules-optional-inputs) + +#### nsg_auto_rules_settings + +Controls automatic creation of NSG rules for all defined inbound rules. + +Following properties are supported: + +- `nsg_name` - (`string`, required) name of an existing Network Security Group +- `resource_group_name - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the NSG +- `source_ips` - (`list`, required) either `*` or a list of CIDRs/IP addresses from which access to the frontends will be allowed +- `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow + + +Type: + +```hcl +object({ + nsg_name = string + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number, 1000) + }) +``` + + +Default value: `&{}` + +[back to list](#modules-optional-inputs) + + + \ No newline at end of file diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index b5768ebc..568d66d9 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -1,12 +1,12 @@ locals { # Decide how the backend machines access internet. If outbound rules are defined use them instead of the default route. # This is an inbound rule setting, applicable to all inbound rules as you cannot mix SNAT with Outbound rules for a single backend. - disable_outbound_snat = anytrue([for _, v in var.frontend_ips : try(length(v.out_rules) > 0, false)]) + disable_outbound_snat = anytrue([for _, v in var.frontend_ips : length(v.out_rules) != 0]) # Calculate inbound rules in_flat_rules = flatten([ for fipkey, fip in var.frontend_ips : [ - for rulekey, rule in try(fip.in_rules, {}) : { + for rulekey, rule in fip.in_rules : { fipkey = fipkey fip = fip rulekey = rulekey @@ -19,7 +19,7 @@ locals { # Calculate outbound rules out_flat_rules = flatten([ for fipkey, fip in var.frontend_ips : [ - for rulekey, rule in try(fip.out_rules, {}) : { + for rulekey, rule in fip.out_rules : { fipkey = fipkey fip = fip rulekey = rulekey @@ -31,28 +31,25 @@ locals { } resource "azurerm_public_ip" "this" { - for_each = { for k, v in var.frontend_ips : k => v if try(v.create_public_ip, false) } + for_each = { for k, v in var.frontend_ips : k => v if v.create_public_ip } - name = "${var.name}-${each.key}-pip" + name = each.value.public_ip_name resource_group_name = var.resource_group_name location = var.location allocation_method = "Static" sku = "Standard" - zones = var.enable_zones ? var.avzones : null + zones = var.zones tags = var.tags } data "azurerm_public_ip" "this" { - for_each = { - for k, v in var.frontend_ips : k => v - if try(v.public_ip_name, null) != null && !try(v.create_public_ip, false) - } + for_each = { for k, v in var.frontend_ips : k => v if !v.create_public_ip && v.public_ip_name != null } - name = try(each.value.public_ip_name, "") - resource_group_name = try(each.value.public_ip_resource_group, var.resource_group_name, "") + name = each.value.public_ip_name + resource_group_name = coalesce(each.value.public_ip_resource_group, var.resource_group_name) } -resource "azurerm_lb" "lb" { +resource "azurerm_lb" "this" { name = var.name resource_group_name = var.resource_group_name location = var.location @@ -61,106 +58,101 @@ resource "azurerm_lb" "lb" { dynamic "frontend_ip_configuration" { for_each = var.frontend_ips - iterator = each + iterator = frontend_ip content { - name = each.key - public_ip_address_id = try(each.value.create_public_ip, false) ? azurerm_public_ip.this[each.key].id : try(data.azurerm_public_ip.this[each.key].id, null) - subnet_id = try(each.value.subnet_id, null) - private_ip_address_allocation = try(each.value.private_ip_address, null) != null ? "Static" : null - private_ip_address = try(each.value.private_ip_address, null) - zones = try(each.value.subnet_id, null) != null ? var.avzones : [] - - gateway_load_balancer_frontend_ip_configuration_id = try(each.value.gateway_load_balancer_frontend_ip_configuration_id, null) + name = frontend_ip.value.name + public_ip_address_id = frontend_ip.value.create_public_ip ? azurerm_public_ip.this[frontend_ip.key].id : try(data.azurerm_public_ip.this[frontend_ip.key].id, null) + subnet_id = frontend_ip.value.subnet_id + private_ip_address_allocation = frontend_ip.value.private_ip_address != null ? "Static" : null + private_ip_address = frontend_ip.value.private_ip_address + zones = frontend_ip.value.subnet_id != null ? var.zones : null + + gateway_load_balancer_frontend_ip_configuration_id = frontend_ip.value.gateway_load_balancer_frontend_ip_configuration_id } } } -resource "azurerm_lb_backend_address_pool" "lb_backend" { +resource "azurerm_lb_backend_address_pool" "this" { name = var.backend_name - loadbalancer_id = azurerm_lb.lb.id + loadbalancer_id = azurerm_lb.this.id } -resource "azurerm_lb_probe" "probe" { - name = var.probe_name - loadbalancer_id = azurerm_lb.lb.id - port = var.probe_port +resource "azurerm_lb_probe" "this" { + name = var.health_probe.name + loadbalancer_id = azurerm_lb.this.id + port = var.health_probe.port } -resource "azurerm_lb_rule" "in_rules" { +resource "azurerm_lb_rule" "this" { for_each = local.in_rules - name = each.key - loadbalancer_id = azurerm_lb.lb.id - probe_id = azurerm_lb_probe.probe.id - backend_address_pool_ids = [azurerm_lb_backend_address_pool.lb_backend.id] + name = each.value.rule.name + loadbalancer_id = azurerm_lb.this.id + probe_id = azurerm_lb_probe.this.id + backend_address_pool_ids = [azurerm_lb_backend_address_pool.this.id] protocol = each.value.rule.protocol - backend_port = coalesce(try(each.value.rule.backend_port, null), each.value.rule.port) - frontend_ip_configuration_name = each.value.fipkey + backend_port = coalesce(each.value.rule.backend_port, each.value.rule.port) + frontend_ip_configuration_name = each.value.fip.name frontend_port = each.value.rule.port - enable_floating_ip = try(each.value.rule.floating_ip, true) + enable_floating_ip = each.value.rule.floating_ip disable_outbound_snat = local.disable_outbound_snat - load_distribution = try(each.value.rule.session_persistence, null) + load_distribution = each.value.rule.session_persistence } -resource "azurerm_lb_outbound_rule" "out_rules" { +resource "azurerm_lb_outbound_rule" "this" { for_each = local.out_rules - name = each.key - loadbalancer_id = azurerm_lb.lb.id - backend_address_pool_id = azurerm_lb_backend_address_pool.lb_backend.id + name = each.value.rule.name + loadbalancer_id = azurerm_lb.this.id + backend_address_pool_id = azurerm_lb_backend_address_pool.this.id protocol = each.value.rule.protocol - enable_tcp_reset = each.value.rule.protocol != "Udp" ? try(each.value.rule.enable_tcp_reset, null) : null - allocated_outbound_ports = try(each.value.rule.allocated_outbound_ports, null) - idle_timeout_in_minutes = each.value.rule.protocol != "Udp" ? try(each.value.rule.idle_timeout_in_minutes, null) : null + enable_tcp_reset = each.value.rule.protocol != "Udp" ? each.value.rule.enable_tcp_reset : null + allocated_outbound_ports = each.value.rule.allocated_outbound_ports + idle_timeout_in_minutes = each.value.rule.protocol != "Udp" ? each.value.rule.idle_timeout_in_minutes : null frontend_ip_configuration { - name = each.value.fipkey + name = each.value.fip.name } + depends_on = [azurerm_lb_rule.this] } locals { # Map of all frontend IP addresses, public or private. frontend_addresses = { - for v in azurerm_lb.lb.frontend_ip_configuration : v.name => try(data.azurerm_public_ip.this[v.name].ip_address, azurerm_public_ip.this[v.name].ip_address, v.private_ip_address) + for k, v in var.frontend_ips : k => try(data.azurerm_public_ip.this[k].ip_address, azurerm_public_ip.this[k].ip_address, v.private_ip_address) } # A map of hashes calculated for each inbound rule. Used to calculate NSG inbound rules priority index if modules is also used to automatically manage NSG rules. rules_hash = { - for k, v in local.in_rules : k => substr( - sha256("${local.frontend_addresses[v.fipkey]}:${v.rule.port}"), - 0, - 4 - ) - if var.network_security_group_name != null && var.network_security_group_name != "" && length(var.network_security_allow_source_ips) > 0 + for k, v in local.in_rules : + k => substr(sha256("${local.frontend_addresses[v.fipkey]}:${v.rule.port}"), 0, 4) + if var.nsg_auto_rules_settings != null } } # Optional NSG rules. Each corresponds to one azurerm_lb_rule. -resource "azurerm_network_security_rule" "allow_inbound_ips" { - for_each = { - for k, v in local.in_rules : k => v - if var.network_security_group_name != null && var.network_security_group_name != "" && length(var.network_security_allow_source_ips) > 0 - } +resource "azurerm_network_security_rule" "this" { + for_each = { for k, v in local.in_rules : k => v if var.nsg_auto_rules_settings != null } name = "allow-inbound-ips-${each.key}" - network_security_group_name = var.network_security_group_name - resource_group_name = coalesce(var.network_security_resource_group_name, var.resource_group_name) - description = "Auto-generated for load balancer ${var.name} port ${each.value.rule.protocol}/${try(each.value.rule.backend_port, each.value.rule.port)}: allowed inbound IP ranges" + network_security_group_name = var.nsg_auto_rules_settings.nsg_name + resource_group_name = coalesce(var.nsg_auto_rules_settings.nsg_resource_group_name, var.resource_group_name) + description = "Auto-generated for load balancer ${var.name} port ${each.value.rule.protocol}/${coalesce(each.value.rule.backend_port, each.value.rule.port)}: allowed inbound IP ranges" direction = "Inbound" access = "Allow" protocol = title(replace(lower(each.value.rule.protocol), "all", "*")) source_port_range = "*" - destination_port_ranges = [each.value.rule.port == "0" ? "*" : try(each.value.rule.backend_port, each.value.rule.port)] - source_address_prefixes = var.network_security_allow_source_ips + destination_port_ranges = [each.value.rule.port == "0" ? "*" : coalesce(each.value.rule.backend_port, each.value.rule.port)] + source_address_prefixes = var.nsg_auto_rules_settings.source_ips destination_address_prefix = local.frontend_addresses[each.value.fipkey] # For the priority, we add this %10 so that the numbering would be a bit more sparse instead of sequential. # This helps tremendously when a mass of indexes shifts by +1 or -1 and prevents problems when we need to shift rules reusing already used priority index. - priority = try( + priority = coalesce( each.value.rule.nsg_priority, - index(keys(local.in_rules), each.key) * 10 + parseint(local.rules_hash[each.key], 16) % 10 + var.network_security_base_priority + index(keys(local.in_rules), each.key) * 10 + parseint(local.rules_hash[each.key], 16) % 10 + var.nsg_auto_rules_settings.base_priority ) } diff --git a/modules/loadbalancer/main_test.go b/modules/loadbalancer/main_test.go deleted file mode 100644 index ec23b950..00000000 --- a/modules/loadbalancer/main_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package loadbalancer - -import ( - "testing" - - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" -) - -func TestValidate(t *testing.T) { - testskeleton.ValidateCode(t, nil) -} diff --git a/modules/loadbalancer/outputs.tf b/modules/loadbalancer/outputs.tf index 7c2d4d19..09eadcf4 100644 --- a/modules/loadbalancer/outputs.tf +++ b/modules/loadbalancer/outputs.tf @@ -1,6 +1,6 @@ output "backend_pool_id" { description = "The identifier of the backend pool." - value = azurerm_lb_backend_address_pool.lb_backend.id + value = azurerm_lb_backend_address_pool.this.id } output "frontend_ip_configs" { @@ -11,5 +11,5 @@ output "frontend_ip_configs" { output "health_probe" { description = "The health probe object." - value = azurerm_lb_probe.probe + value = azurerm_lb_probe.this } diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 959d7bfc..a1f0c482 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -1,29 +1,93 @@ -variable "frontend_ips" { - description = <<-EOF - A map of objects describing LB Frontend IP configurations, inbound and outbound rules. Used for both public or private load balancers. - Keys of the map are names of LB Frontend IP configurations. +variable "name" { + description = "The name of the load balancer." + type = string +} - Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. A key in this map is the name of the rule, while value is the actual rule configuration. To understand this structure please see examples below. +variable "resource_group_name" { + description = "Name of a pre-existing Resource Group to place the resources in." + type = string +} - **Inbound rules.** +variable "location" { + description = "Region to deploy the resources." + type = string +} - Here is a list of properties supported by each `in_rule`: +variable "zones" { + description = <<-EOF + Controls zones for load balancer's Fronted IP configurations. - - `protocol` : required, communication protocol, either 'Tcp', 'Udp' or 'All'. - - `port` : required, communication port, this is both the front- and the backend port if `backend_port` is not given. - - `backend_port` : optional, this is the backend port to forward traffic to in the backend pool. - - `floating_ip` : optional, defaults to `true`, enables floating IP for this rule. - - `session_persistence` : optional, defaults to 5 tuple (Azure default), see `Session persistence/Load distribution` below for details. + For: - Public LB + - public IPs - these are zones in which the public IP resource is available + - private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs (all frontends are affected) - - `create_public_ip` : Optional. Set to `true` to create a public IP. - - `public_ip_name` : Ignored if `create_public_ip` is `true`. The existing public IP resource name to use. - - `public_ip_resource_group` : Ignored if `create_public_ip` is `true` or if `public_ip_name` is null. The name of the resource group which holds `public_ip_name`. + Setting this variable to explicit `null` disables a zonal deployment. + This can be helpful in regions where Availability Zones are not available. + + For public Load Balancers, since this setting controls also Availability Zones for public IPs, you need to specify all zones available in a region (typically 3): `["1","2","3"]` + EOF + default = ["1", "2", "3"] + type = list(string) +} - Example +variable "tags" { + description = "Azure tags to apply to the created resources." + default = {} + nullable = false + type = map(string) +} - ``` +variable "frontend_ips" { + description = <<-EOF + A map of objects describing LB Frontend IP configurations, inbound and outbound rules. + + Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. + + Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. To ease configuration they were grouped per Load Balancer type. + + Private Load Balancer: + + - `name` - (`string`, required) name of a frontend IP configuration + - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer + - `private_ip_address` - (`string`, optional, defaults to `null`) when assigned it will become the IP address of the Load Balancer, when skipped the IP will be assigned from DHCP. + - `gateway_load_balancer_frontend_ip_configuration_id` - ???? + - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + + Public Load Balancer: + + - `name` - (`string`, required) name of a frontend IP configuration + - `public_ip_name` - (`string`, required) name of a public IP resource + - `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be created, otherwise an existing resource will be used; in both cases the name of the resource is controled by `public_ip_name` + - `public_ip_resource_group` - (`string`, optional, defaults to `null`) name of a Resource Group hosting an existing public IP resource + - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below + + Below are the properties for the **inbound rules** map: + + - `name` - (`string`, required) a name of an inbound rule + - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. + - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports + - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool + - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. + - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: + - `Default` : this is the 5 tuple hash + - `SourceIP` : a 2 tuple hash is used + - `SourceIPProtocol` : a 3 tuple hash is used + - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) + + Below are the properties for outbound rules map. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: + + - `name` - (`string`, required) a name of an outbound rule + - `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted + - `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance + - `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` + - `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` + + Examples + + ```hcl + # rules for a public LB, reusing an existing public IP and doing port translation frontend_ips = { pip_existing = { create_public_ip = false @@ -33,41 +97,15 @@ variable "frontend_ips" { HTTP = { port = 80 protocol = "Tcp" - } - } - } - } - ``` - - Forward to a different port on backend pool - - ``` - frontend_ips = { - pip_existing = { - create_public_ip = false - public_ip_name = "my_ip" - public_ip_resource_group = "my_rg_name" - in_rules = { - HTTP = { - port = 80 backend_port = 8080 - protocol = "Tcp" } } } } - ``` - - Private LB - - `subnet_id` : Identifier of an existing subnet. This also trigger creation of an internal LB. - - `private_ip_address` : A static IP address of the Frontend IP configuration, has to be in limits of the subnet's (specified by `subnet_id`) address space. When not set, changes the address allocation from `Static` to `Dynamic`. - - Example - - ``` + # rules for a private LB, with a static private IP address and one HA PORTs rule frontend_ips = { - internal_fe = { + internal = { subnet_id = azurerm_subnet.this.id private_ip_address = "192.168.0.10" in_rules = { @@ -78,47 +116,8 @@ variable "frontend_ips" { } } } - ``` - - Session persistence/Load distribution - - By default the Load Balancer uses a 5 tuple hash to map traffic to available servers. This can be controlled using `session_persistence` property defined inside a rule. Available values are: - - - `Default` : this is the 5 tuple hash - this method is also used when no property is defined - - `SourceIP` : a 2 tuple hash is used - - `SourceIPProtocol` : a 3 tuple hash is used - - Example - - ``` - frontend_ips = { - rule_1 = { - create_public_ip = true - in_rules = { - HTTP = { - port = 80 - protocol = "Tcp" - session_persistence = "SourceIP" - } - } - } - } - ``` - **Outbound rules.** - - Each Frontend IP config can have outbound rules specified. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`. - - Following properties are available: - - - `protocol` : Protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted. - - `allocated_outbound_ports` : Number of ports allocated per instance. Defaults to `1024`. - - `enable_tcp_reset` : Ignored when `protocol` is set to `Udp`, defaults to `False` (Azure defaults). - - `idle_timeout_in_minutes` : Ignored when `protocol` is set to `Udp`. TCP connection timeout in case the connection is idle. Defaults to 4 minutes (Azure defaults). - - Example: - - ``` + # rules for a public LB, session persistance with 2 tuple hash outbound rule defined frontend_ips = { rule_1 = { create_public_ip = true @@ -129,114 +128,88 @@ variable "frontend_ips" { session_persistence = "SourceIP" } } - out_rules = { - "outbound_tcp" = { - protocol = "Tcp" - allocated_outbound_ports = 2048 - enable_tcp_reset = true - idle_timeout_in_minutes = 10 - } + } + out_rules = { + "outbound_tcp" = { + protocol = "Tcp" + allocated_outbound_ports = 2048 + enable_tcp_reset = true + idle_timeout_in_minutes = 10 } } } - + ``` EOF -} - -variable "resource_group_name" { - description = "Name of a pre-existing Resource Group to place the resources in." - type = string -} - -variable "location" { - description = "Region to deploy load balancer and dependencies." - type = string + type = map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + subnet_id = optional(string) + private_ip_address = optional(string) + gateway_load_balancer_frontend_ip_configuration_id = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number, 1024) + enable_tcp_reset = optional(bool, false) + idle_timeout_in_minutes = optional(number, 4) + })), {}) + })) } variable "backend_name" { - description = <<-EOF - The name of the backend pool to create. All the frontends of the load balancer always use the same single backend. - EOF + description = "The name of the backend pool to create. All frontends of the load balancer always use the same backend." default = "vmseries_backend" - type = string - nullable = false -} - -variable "name" { - description = "The name of the load balancer." - type = string -} - -variable "probe_name" { - description = "The name of the load balancer probe." - default = "vmseries_probe" - type = string nullable = false -} - -variable "probe_port" { - description = "Health check port number of the load balancer probe." - default = "80" type = string } -variable "network_security_allow_source_ips" { +variable "health_probe" { description = <<-EOF - List of IP CIDR ranges (such as `["192.168.0.0/16"]` or `["*"]`) from which the inbound traffic to all frontends should be allowed. - If it's empty, user is responsible for configuring a Network Security Group separately. - The list cannot include Azure tags like "Internet" or "Sql.EastUS". - EOF - default = [] - type = list(string) -} + Backend's health probe definition. -variable "network_security_resource_group_name" { - description = "Name of the Resource Group where the `network_security_group_name` resides. If empty, defaults to `resource_group_name`." - default = "" - type = string -} - -variable "network_security_group_name" { - description = <<-EOF - Name of the pre-existing Network Security Group (NSG) where to add auto-generated rules. Each NSG rule corresponds to a single `in_rule` on the load balancer. - User is responsible to associate the NSG with the load balancer's subnet, the module only supplies the rules. - If empty, user is responsible for configuring an NSG separately. - EOF - default = null - type = string -} + Following properties are available: -variable "network_security_base_priority" { - description = <<-EOF - The base number from which the auto-generated priorities of the NSG rules grow. - Ignored if `network_security_group_name` is empty or if `network_security_allow_source_ips` is empty. + - `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe + - `port` - (`number`, optional, defaults to `80`) port to run the probe against EOF - default = 1000 - type = number -} - -variable "enable_zones" { - description = "If `false`, all the subnet-associated frontends and also all created Public IP addresses default to not to use Availability Zones (the `No-Zone` setting). It is intended for the regions that do not yet support Availability Zones." - default = true - type = bool -} - -variable "tags" { - description = "Azure tags to apply to the created resources." - default = {} - type = map(string) + default = { + name = "vmseries_probe" + port = 80 + } + nullable = false + type = object({ + name = optional(string, "vmseries_probe") + port = optional(number, 80) + }) } -variable "avzones" { +variable "nsg_auto_rules_settings" { description = <<-EOF - Controls zones for load balancer's Fronted IP configurations. For: + Controls automatic creation of NSG rules for all defined inbound rules. - * public IPs - these are regions in which the IP resource is available - * private IPs - this represents Zones to which Azure will deploy paths leading to this Frontend IP. + Following properties are supported: - For public IPs, after provider version 3.x (Azure API upgrade) you need to specify all zones available in a region (typically 3), ie: for zone-redundant with 3 availability zone in current region value will be: - ```["1","2","3"]``` + - `nsg_name` - (`string`, required) name of an existing Network Security Group + - `resource_group_name - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the NSG + - `source_ips` - (`list`, required) either `*` or a list of CIDRs/IP addresses from which access to the frontends will be allowed + - `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow EOF - default = [] - type = list(string) + default = null + type = object({ + nsg_name = string + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number, 1000) + }) } diff --git a/modules/loadbalancer/versions.tf b/modules/loadbalancer/versions.tf index 501042ff..8dc5c1eb 100644 --- a/modules/loadbalancer/versions.tf +++ b/modules/loadbalancer/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.3, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" From 8ea497d2775c77c8c0851a29721ef162711d58b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Thu, 19 Oct 2023 12:33:48 +0200 Subject: [PATCH 02/11] check admonitions --- modules/loadbalancer/README.md | 5 ++++- modules/loadbalancer/variables.tf | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index e5937f3a..a6bc085c 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -137,7 +137,10 @@ Below are the properties for the **inbound rules** map: - `SourceIPProtocol` : a 3 tuple hash is used - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) -Below are the properties for outbound rules map. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: +Below are the properties for **outbound rules** map. + +> [!Warning] +> Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index a1f0c482..6e320268 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -76,7 +76,10 @@ variable "frontend_ips" { - `SourceIPProtocol` : a 3 tuple hash is used - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) - Below are the properties for outbound rules map. Setting at least one `out_rule` switches the outgoing traffic from SNAT to Outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and Outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: + Below are the properties for **outbound rules** map. + + > [!Warning] + > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted From ca8fcc5fb44783580e43b48ba16c195570a8ad68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Thu, 19 Oct 2023 14:46:16 +0200 Subject: [PATCH 03/11] in_rules and generic properties validation --- examples/lb/example.tfvars | 8 +++-- modules/loadbalancer/README.md | 2 +- modules/loadbalancer/variables.tf | 58 ++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars index 0693c02a..ff2a1db3 100644 --- a/examples/lb/example.tfvars +++ b/examples/lb/example.tfvars @@ -76,9 +76,11 @@ load_balancers = { private_ip_address = "10.0.0.21" in_rules = { HA_PORTS = { - name = "HA" - port = 0 - protocol = "All" + name = "HA" + port = 0 + protocol = "All" + session_persistence = "SourceIP" + nsg_priority = 2000 } } } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index a6bc085c..526354e3 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -143,7 +143,7 @@ Below are the properties for **outbound rules** map. > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule -- `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted +- `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance - `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` - `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 6e320268..56cb26e8 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -29,6 +29,10 @@ variable "zones" { EOF default = ["1", "2", "3"] type = list(string) + validation { + condition = length(var.zones) > 0 || var.zones == null + error_message = "The `var.zones` can either bea non empty list of Availability Zones or `null`." + } } variable "tags" { @@ -82,7 +86,7 @@ variable "frontend_ips" { > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule - - `protocol` - (`string`, required) protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted + - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance - `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` - `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` @@ -168,6 +172,58 @@ variable "frontend_ips" { idle_timeout_in_minutes = optional(number, 4) })), {}) })) + validation { + condition = alltrue([ + for _, fip in var.frontend_ips : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", fip.private_ip_address)) + if fip.private_ip_address != null + ]) + error_message = "The `private_ip_address` property should be in IPv4 format." + } + validation { + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : contains(["Tcp", "Udp", "All"], in_rule.protocol) + ] + ])) + error_message = "The `in_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\"." + } + validation { + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : (in_rule.port >= 0 && in_rule.port <= 65535) + ] + ])) + error_message = "The `in_rule.port` should be a valid TCP port number or `0` for all ports." + } + validation { + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : + (in_rule.backend_port > 0 && in_rule.backend_port <= 65535) + if in_rule.backend_port != null + ] + ])) + error_message = "The `in_rule.backend_port` should be a valid TCP port number." + } + validation { + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : contains(["Default", "SourceIP", "SourceIPProtocol"], in_rule.session_persistence) + ] + ])) + error_message = "The `in_rule.session_persistence` property should be one of: \"Default\", \"SourceIP\", \"SourceIPProtocol\"." + } + validation { + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : + in_rule.nsg_priority >= 100 && in_rule.nsg_priority <= 4000 + if in_rule.nsg_priority != null + ] + ])) + error_message = "The `in_rule.nsg_priority` property be a number between 100 and 4096." + } } variable "backend_name" { From f652af84bcaf35ec810c9ba5ca3931a06c4ee34f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Fri, 20 Oct 2023 16:30:07 +0200 Subject: [PATCH 04/11] validation for most of the variables in the module done --- examples/lb/README.md | 17 +++- examples/lb/example.tfvars | 92 ++++++++++++------- examples/lb/main.tf | 2 + examples/lb/variables.tf | 15 ++- modules/loadbalancer/.header.md | 10 ++ modules/loadbalancer/README.md | 42 ++++++--- modules/loadbalancer/main.tf | 24 ++++- modules/loadbalancer/variables.tf | 146 +++++++++++++++++++++++++----- 8 files changed, 271 insertions(+), 77 deletions(-) diff --git a/examples/lb/README.md b/examples/lb/README.md index fced2d91..b1bdf818 100644 --- a/examples/lb/README.md +++ b/examples/lb/README.md @@ -31,7 +31,7 @@ Name | Type | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.3, < 2.0 Providers used in this module: @@ -271,6 +271,14 @@ map(object({ vnet_key = optional(string) subnet_key = optional(string) zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) nsg_auto_rules_settings = optional(object({ nsg_name = optional(string) nsg_vnet_key = optional(string) @@ -293,6 +301,7 @@ map(object({ protocol = string port = number backend_port = optional(number) + health_probe_key = optional(string) floating_ip = optional(bool, true) session_persistence = optional(string, "Default") nsg_priority = optional(number) @@ -300,9 +309,9 @@ map(object({ out_rules = optional(map(object({ name = string protocol = string - allocated_outbound_ports = optional(number, 1024) - enable_tcp_reset = optional(bool, false) - idle_timeout_in_minutes = optional(number, 4) + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })), {}) })) diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars index ff2a1db3..85d07248 100644 --- a/examples/lb/example.tfvars +++ b/examples/lb/example.tfvars @@ -42,48 +42,78 @@ load_balancers = { # base_priority = 200 # } zones = ["1", "2", "3"] + health_probes = { + "http_default" = { + name = "http_default_probe" + protocol = "Http" + } + "https_default" = { + name = "https_default_probe" + protocol = "Https" + port = 8443 + request_path = "/hch" + probe_threshold = 10 + } + "ssh" = { + name = "ssh-probe" + protocol = "Tcp" + port = 22 + interval_in_seconds = 5 + } + } frontend_ips = { "default_front" = { - name = "default-public-frontend" - # public_ip_name = "frontend-pip" - # create_public_ip = true - public_ip_name = "fosix-sourced_frontend_zonal" - public_ip_resource_group = "fosix-lb-ips" + name = "default-public-frontend" + public_ip_name = "frontend-pip" + create_public_ip = true + # public_ip_name = "fosix-sourced_frontend_zonal" + # public_ip_resource_group = "fosix-lb-ips" in_rules = { "balanceHttp" = { - name = "HTTP" - protocol = "Tcp" - port = 80 + name = "HTTP" + protocol = "Tcp" + port = 80 + health_probe_key = "https_default" + } + "balanceHttps" = { + name = "HTTPS" + protocol = "Tcp" + port = 443 + backend_port = 8443 + health_probe_key = "https_default" } } out_rules = { default = { - name = "default-out" - protocol = "All" - } - } - } - } - } - "private" = { - name = "private-lb" - avzones = null - frontend_ips = { - "ha-ports" = { - name = "HA" - vnet_key = "transit" - subnet_key = "private" - private_ip_address = "10.0.0.21" - in_rules = { - HA_PORTS = { - name = "HA" - port = 0 - protocol = "All" - session_persistence = "SourceIP" - nsg_priority = 2000 + name = "default-out" + protocol = "Tcp" + allocated_outbound_ports = 20000 + enable_tcp_reset = true + idle_timeout_in_minutes = 120 } } } } } + # "private" = { + # name = "private-lb" + # avzones = null + # frontend_ips = { + # "ha-ports" = { + # name = "HA" + # vnet_key = "transit" + # subnet_key = "private" + # private_ip_address = "10.0.0.21" + # in_rules = { + # HA_PORTS = { + # name = "HA" + # port = 0 + # protocol = "All" + # session_persistence = "SourceIP" + # nsg_priority = 2000 + # } + # } + # } + # } + # } } \ No newline at end of file diff --git a/examples/lb/main.tf b/examples/lb/main.tf index 3a226004..0bad2cd2 100644 --- a/examples/lb/main.tf +++ b/examples/lb/main.tf @@ -52,6 +52,8 @@ module "load_balancer" { resource_group_name = local.resource_group.name zones = each.value.zones + health_probes = each.value.health_probes + nsg_auto_rules_settings = try( { nsg_name = try( diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf index ba5b23ea..755f194a 100644 --- a/examples/lb/variables.tf +++ b/examples/lb/variables.tf @@ -179,6 +179,14 @@ variable "load_balancers" { vnet_key = optional(string) subnet_key = optional(string) zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) nsg_auto_rules_settings = optional(object({ nsg_name = optional(string) nsg_vnet_key = optional(string) @@ -201,6 +209,7 @@ variable "load_balancers" { protocol = string port = number backend_port = optional(number) + health_probe_key = optional(string) floating_ip = optional(bool, true) session_persistence = optional(string, "Default") nsg_priority = optional(number) @@ -208,9 +217,9 @@ variable "load_balancers" { out_rules = optional(map(object({ name = string protocol = string - allocated_outbound_ports = optional(number, 1024) - enable_tcp_reset = optional(bool, false) - idle_timeout_in_minutes = optional(number, 4) + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })), {}) })) diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md index 148748c1..fd0fb3b3 100644 --- a/modules/loadbalancer/.header.md +++ b/modules/loadbalancer/.header.md @@ -9,3 +9,13 @@ In case of a public load balancer, you can define outbound rules and use the fro ## Usage For usage see any of the reference architecture examples. + +## TODO + +- [ ] nsg_auto_rules_settings validation +- [ ] update documentation +- [ ] do the .header usage documentation +- [ ] adjust type in examples + +- [ ] fix vnet validation, if in for loop instead of a complex condition +- [ ] vnet validation for name property diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 526354e3..95fbfbfe 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -28,7 +28,7 @@ Name | Type | Description [`zones`](#zones) | `list` | Controls zones for load balancer's Fronted IP configurations. [`tags`](#tags) | `map` | Azure tags to apply to the created resources. [`backend_name`](#backend_name) | `string` | The name of the backend pool to create. -[`health_probe`](#health_probe) | `object` | Backend's health probe definition. +[`health_probes`](#health_probes) | `map` | Backend's health probe definition. [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) | `object` | Controls automatic creation of NSG rules for all defined inbound rules. @@ -130,6 +130,7 @@ Below are the properties for the **inbound rules** map: - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool +- `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: - `Default` : this is the 5 tuple hash @@ -144,9 +145,9 @@ Below are the properties for **outbound rules** map. - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted -- `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance -- `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` -- `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` +- `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` +- `enable_tcp_reset` - (`bool`, optional, defaults to `null`) ignored when `protocol` is set to `Udp` +- `idle_timeout_in_minutes` - (`number`, optional, defaults to `null`) TCP connection timeout in minutes (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` Examples @@ -221,6 +222,7 @@ map(object({ protocol = string port = number backend_port = optional(number) + health_probe_key = optional(string, "default") floating_ip = optional(bool, true) session_persistence = optional(string, "Default") nsg_priority = optional(number) @@ -228,9 +230,9 @@ map(object({ out_rules = optional(map(object({ name = string protocol = string - allocated_outbound_ports = optional(number, 1024) - enable_tcp_reset = optional(bool, false) - idle_timeout_in_minutes = optional(number, 4) + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })) ``` @@ -291,27 +293,37 @@ Default value: `vmseries_backend` [back to list](#modules-optional-inputs) -#### health_probe +#### health_probes Backend's health probe definition. +When this property is not defined, or set to `null`, a default, TCP based probe will be created for port 80. + Following properties are available: -- `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe -- `port` - (`number`, optional, defaults to `80`) port to run the probe against +- `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe +- `protocol` - (`string`, optional, defaults to `"TCP"`) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" +- `port` - (`number`, optional, defaults to `80`) port to run the probe against +- `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint +- `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 +- `request_path` - (`string`, optional, defaults to Azure defaults) the URI used to check the endpoint status when `protocol` is set to `Http(s)` Type: ```hcl -object({ - name = optional(string, "vmseries_probe") - port = optional(number, 80) - }) +map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + })) ``` -Default value: `map[name:vmseries_probe port:80]` +Default value: `map[default:map[name:vmseries_probe port:80 protocol:Tcp]]` [back to list](#modules-optional-inputs) diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index 568d66d9..c3314cc5 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -77,10 +77,28 @@ resource "azurerm_lb_backend_address_pool" "this" { loadbalancer_id = azurerm_lb.this.id } +locals { + default_http_probe_port = { + "Http" = 80 + "Https" = "443" + } +} + resource "azurerm_lb_probe" "this" { - name = var.health_probe.name + for_each = var.health_probes + loadbalancer_id = azurerm_lb.this.id - port = var.health_probe.port + + name = each.value.name + protocol = each.value.protocol + port = contains(["Http", "Https"], each.value.protocol) && each.value.port == null ? local.default_http_probe_port[each.value.protocol] : each.value.port + probe_threshold = each.value.probe_threshold + interval_in_seconds = each.value.interval_in_seconds + request_path = each.value.protocol != "Tcp" ? each.value.request_path : null + + # this is to overcome the discrepancy between the provider and Azure defaults + # for more details see here -> https://learn.microsoft.com/en-gb/azure/load-balancer/whats-new#known-issues:~:text=SNAT%20port%20exhaustion-,numberOfProbes,-%2C%20%22Unhealthy%20threshold%22 + number_of_probes = 1 } resource "azurerm_lb_rule" "this" { @@ -88,7 +106,7 @@ resource "azurerm_lb_rule" "this" { name = each.value.rule.name loadbalancer_id = azurerm_lb.this.id - probe_id = azurerm_lb_probe.this.id + probe_id = azurerm_lb_probe.this[each.value.rule.health_probe_key].id backend_address_pool_ids = [azurerm_lb_backend_address_pool.this.id] protocol = each.value.rule.protocol diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 56cb26e8..89c623ee 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -73,6 +73,7 @@ variable "frontend_ips" { - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool + - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: - `Default` : this is the 5 tuple hash @@ -87,9 +88,9 @@ variable "frontend_ips" { - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - - `allocated_outbound_ports` - (`number`, optional, defaults to `1024`) number of ports allocated per instance - - `enable_tcp_reset` - (`bool`, optional, defaults to `false`) ignored when `protocol` is set to `Udp` - - `idle_timeout_in_minutes` - (`number`, optional, defaults to `4`) TCP connection timeout in minutes in case the connection is idle, ignored when `protocol` is set to `Udp` + - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` + - `enable_tcp_reset` - (`bool`, optional, defaults to `null`) ignored when `protocol` is set to `Udp` + - `idle_timeout_in_minutes` - (`number`, optional, defaults to `null`) TCP connection timeout in minutes (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` Examples @@ -160,6 +161,7 @@ variable "frontend_ips" { protocol = string port = number backend_port = optional(number) + health_probe_key = optional(string, "default") floating_ip = optional(bool, true) session_persistence = optional(string, "Default") nsg_priority = optional(number) @@ -167,12 +169,16 @@ variable "frontend_ips" { out_rules = optional(map(object({ name = string protocol = string - allocated_outbound_ports = optional(number, 1024) - enable_tcp_reset = optional(bool, false) - idle_timeout_in_minutes = optional(number, 4) + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })) - validation { + validation { # name + condition = length(flatten([for _, v in var.frontend_ips : v.name])) == length(distinct(flatten([for _, v in var.frontend_ips : v.name]))) + error_message = "The `name` property has to be unique among all frontend definitions." + } + validation { # private_ip_address condition = alltrue([ for _, fip in var.frontend_ips : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", fip.private_ip_address)) @@ -180,7 +186,17 @@ variable "frontend_ips" { ]) error_message = "The `private_ip_address` property should be in IPv4 format." } - validation { + validation { # in_rule.name + condition = length(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : in_rule.name + ]])) == length(distinct(flatten([ + for _, fip in var.frontend_ips : [ + for _, in_rule in fip.in_rules : in_rule.name + ]]))) + error_message = "The `in_rule.name` property has to be unique among all in rules definitions." + } + validation { # in_rule.protocol condition = alltrue(flatten([ for _, fip in var.frontend_ips : [ for _, in_rule in fip.in_rules : contains(["Tcp", "Udp", "All"], in_rule.protocol) @@ -188,7 +204,7 @@ variable "frontend_ips" { ])) error_message = "The `in_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\"." } - validation { + validation { # in_rule.port condition = alltrue(flatten([ for _, fip in var.frontend_ips : [ for _, in_rule in fip.in_rules : (in_rule.port >= 0 && in_rule.port <= 65535) @@ -196,7 +212,7 @@ variable "frontend_ips" { ])) error_message = "The `in_rule.port` should be a valid TCP port number or `0` for all ports." } - validation { + validation { # in_rule.backend_port condition = alltrue(flatten([ for _, fip in var.frontend_ips : [ for _, in_rule in fip.in_rules : @@ -206,7 +222,7 @@ variable "frontend_ips" { ])) error_message = "The `in_rule.backend_port` should be a valid TCP port number." } - validation { + validation { # in_rule.sessions_persistence condition = alltrue(flatten([ for _, fip in var.frontend_ips : [ for _, in_rule in fip.in_rules : contains(["Default", "SourceIP", "SourceIPProtocol"], in_rule.session_persistence) @@ -214,7 +230,7 @@ variable "frontend_ips" { ])) error_message = "The `in_rule.session_persistence` property should be one of: \"Default\", \"SourceIP\", \"SourceIPProtocol\"." } - validation { + validation { # in_rule.nsg_priority condition = alltrue(flatten([ for _, fip in var.frontend_ips : [ for _, in_rule in fip.in_rules : @@ -224,6 +240,44 @@ variable "frontend_ips" { ])) error_message = "The `in_rule.nsg_priority` property be a number between 100 and 4096." } + validation { # out_rule.name + condition = length(flatten([ + for _, fip in var.frontend_ips : [ + for _, out_rule in fip.out_rules : out_rule.name + ]])) == length(distinct(flatten([ + for _, fip in var.frontend_ips : [ + for _, out_rule in fip.out_rules : out_rule.name + ]]))) + error_message = "The `out_rule.name` property has to be unique among all in rules definitions." + } + validation { # out_rule.protocol + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, out_rule in fip.out_rules : contains(["Tcp", "Udp", "All"], out_rule.protocol) + ] + ])) + error_message = "The `out_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\"." + } + validation { # out_rule.allocated_outbound_ports + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, out_rule in fip.out_rules : + out_rule.allocated_outbound_ports >= 0 && out_rule.allocated_outbound_ports <= 64000 + if out_rule.allocated_outbound_ports != null + ] + ])) + error_message = "The `out_rule.allocated_outbound_ports` property should can be either `0` or a valid TCP port number with the maximum value of 64000." + } + validation { # out_rule.idle_timeout_in_minutes + condition = alltrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, out_rule in fip.out_rules : + out_rule.idle_timeout_in_minutes >= 4 && out_rule.idle_timeout_in_minutes <= 120 + if out_rule.idle_timeout_in_minutes != null + ] + ])) + error_message = "The `out_rule.idle_timeout_in_minutes` property should can take values between 4 and 120 (minutes)." + } } variable "backend_name" { @@ -233,24 +287,74 @@ variable "backend_name" { type = string } -variable "health_probe" { +variable "health_probes" { description = <<-EOF Backend's health probe definition. + When this property is not defined, or set to `null`, a default, TCP based probe will be created for port 80. + Following properties are available: - - `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe - - `port` - (`number`, optional, defaults to `80`) port to run the probe against + - `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe + - `protocol` - (`string`, optional, defaults to `"TCP"`) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" + - `port` - (`number`, optional, defaults to `80`) port to run the probe against + - `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint + - `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 + - `request_path` - (`string`, optional, defaults to Azure defaults) the URI used to check the endpoint status when `protocol` is set to `Http(s)` EOF default = { - name = "vmseries_probe" - port = 80 + default = { + name = "vmseries_probe" + protocol = "Tcp" + port = 80 + } } nullable = false - type = object({ - name = optional(string, "vmseries_probe") - port = optional(number, 80) - }) + type = map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + })) + validation { # name + condition = length(flatten([for _, v in var.health_probes : v.name])) == length(distinct(flatten([for _, v in var.health_probes : v.name]))) + error_message = "The `name` property has to be unique among all health probe definitions." + } + validation { # protocol + condition = alltrue([for k, v in var.health_probes : contains(["Tcp", "Http", "Https"], v.protocol)]) + error_message = "The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\"." + } + validation { # port + condition = alltrue([for k, v in var.health_probes : v.port != null if v.protocol == "Tcp"]) + error_message = "The `port` property is required when protocol is set to \"Tcp\"." + } + validation { # port + condition = alltrue([for k, v in var.health_probes : + v.port >= 1 && v.port <= 65535 + if v.port != null + ]) + error_message = "The `port` property has to be a valid TCP port." + } + validation { # interval_in_seconds + condition = alltrue([for k, v in var.health_probes : + v.interval_in_seconds >= 5 && v.interval_in_seconds <= 3600 + if v.interval_in_seconds != null + ]) + error_message = "The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour)." + } + validation { # probe_threshold + condition = alltrue([for k, v in var.health_probes : + v.probe_threshold >= 1 && v.probe_threshold <= 100 + if v.probe_threshold != null + ]) + error_message = "The `probe_threshold` property has to be between 1 and 100." + } + validation { # request + condition = alltrue([for k, v in var.health_probes : v.request_path != null if v.protocol != "Tcp"]) + error_message = "value" + } } variable "nsg_auto_rules_settings" { From b76a63a88542e071cc816f80e0abcedea4b2a2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Mon, 23 Oct 2023 13:29:53 +0200 Subject: [PATCH 05/11] update documentation --- examples/lb/example.tfvars | 108 ++++++++++--------- examples/lb/main.tf | 2 +- modules/loadbalancer/.header.md | 84 +++++++++++++-- modules/loadbalancer/README.md | 172 ++++++++++++++++++++++-------- modules/loadbalancer/main.tf | 33 +++++- modules/loadbalancer/variables.tf | 145 ++++++++++++++++--------- 6 files changed, 387 insertions(+), 157 deletions(-) diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars index 85d07248..ee51d5af 100644 --- a/examples/lb/example.tfvars +++ b/examples/lb/example.tfvars @@ -33,54 +33,32 @@ vnets = { load_balancers = { "public" = { name = "public-lb" - # nsg_auto_rules_settings = { - # nsg_name = "fosix-existing-nsg" - # nsg_resource_group_name = "fosix-lb-ips" - # # nsg_vnet_key = "transit" - # # nsg_key = "public" - # source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer - # base_priority = 200 - # } + nsg_auto_rules_settings = { + # nsg_name = "fosix-existing-nsg" + # nsg_resource_group_name = "fosix-lb-ips" + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["10.0.0.0/8"] # Put your own public IP address here <-- TODO to be adjusted by the customer + base_priority = 4000 + } zones = ["1", "2", "3"] health_probes = { "http_default" = { name = "http_default_probe" protocol = "Http" } - "https_default" = { - name = "https_default_probe" - protocol = "Https" - port = 8443 - request_path = "/hch" - probe_threshold = 10 - } - "ssh" = { - name = "ssh-probe" - protocol = "Tcp" - port = 22 - interval_in_seconds = 5 - } } frontend_ips = { "default_front" = { name = "default-public-frontend" public_ip_name = "frontend-pip" create_public_ip = true - # public_ip_name = "fosix-sourced_frontend_zonal" - # public_ip_resource_group = "fosix-lb-ips" in_rules = { "balanceHttp" = { name = "HTTP" protocol = "Tcp" port = 80 - health_probe_key = "https_default" - } - "balanceHttps" = { - name = "HTTPS" - protocol = "Tcp" - port = 443 - backend_port = 8443 - health_probe_key = "https_default" + health_probe_key = "http_default" } } out_rules = { @@ -93,27 +71,53 @@ load_balancers = { } } } + "sourced_pip" = { + name = "with-sourced-pip" + public_ip_name = "fosix-sourced_frontend" + public_ip_resource_group = "fosix-lb-ips" + zones = null + in_rules = { + "balanceHttp" = { + name = "HTTP-elevated" + protocol = "Tcp" + port = 80 + # health_probe_key = "http_default" + } + } + } + # "private" = { + # name = "private" + # vnet_key = "transit" + # subnet_key = "private" + # private_ip_address = "10.0.0.22" + # in_rules = { + # "balanceHttp" = { + # name = "HA" + # protocol = "Tcp" + # port = 80 + # health_probe_key = "http_default" + # } + # } + # } + } + } + "private" = { + name = "private-lb" + avzones = ["1"] + frontend_ips = { + "ha-ports" = { + name = "HA" + vnet_key = "transit" + subnet_key = "private" + private_ip_address = "10.0.0.21" + in_rules = { + HA_PORTS = { + name = "HA" + port = 0 + protocol = "All" + } + } + } } } - # "private" = { - # name = "private-lb" - # avzones = null - # frontend_ips = { - # "ha-ports" = { - # name = "HA" - # vnet_key = "transit" - # subnet_key = "private" - # private_ip_address = "10.0.0.21" - # in_rules = { - # HA_PORTS = { - # name = "HA" - # port = 0 - # protocol = "All" - # session_persistence = "SourceIP" - # nsg_priority = 2000 - # } - # } - # } - # } - # } } \ No newline at end of file diff --git a/examples/lb/main.tf b/examples/lb/main.tf index 0bad2cd2..f4912b2a 100644 --- a/examples/lb/main.tf +++ b/examples/lb/main.tf @@ -83,4 +83,4 @@ module "load_balancer" { tags = var.tags depends_on = [module.vnet] -} \ No newline at end of file +} diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md index fd0fb3b3..eac7e1a6 100644 --- a/modules/loadbalancer/.header.md +++ b/modules/loadbalancer/.header.md @@ -1,21 +1,83 @@ # Load Balancer Module for Azure -A Terraform module for deploying a Load Balancer for VM-Series firewalls. Supports both standalone and scale set deployments. Note, that due to that some properties are mutually exclusive. Please check the properties' description. +A Terraform module for deploying a Load Balancer for VM-Series firewalls. +Supports both standalone and scale set deployments. -The module creates a single load balancer and a single backend for it, but it allows multiple frontends. +> [!NOTE] +> The module can create a public or a private Load Balancer +> Due to that some properties are mutually exclusive. -In case of a public load balancer, you can define outbound rules and use the frontend's public IP address to access the internet. If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix SNAT with outbound rules for a single backend. +The module creates a single Load Balancer and a single backend for it, but it allows multiple frontends. + +> [!NOTE] +> In case of a public Load Balancer, you can define outbound rules and use the frontend's public IP address to access the internet. +> If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix +> SNAT with outbound rules for a single backend. ## Usage -For usage see any of the reference architecture examples. +There are two basic modes the module can work in: a public and a private Load Balancer. + +### Private Load Balancer + +To create a private Load Balancer one has to specify an ID of an existing Subnet and a private IP address +in each frontend IP configuration. + +Example of a private Load Balancer with HA ports rule: + +```hcl +module "lbi" { + source = "../../modules/loadbalancer" + + name = "private-lb" + location = "West Europe" + resource_group_name = "existing-rg" + + frontend_ips = { + ha = { + name = "HA" + subnet_id = "/subscription/xxxx/......." + private_ip_address = "10.0.0.1" + in_rules = { + ha = { + name = "HA" + port = 0 + protocol = "All" + } + } + } + } +} +``` + +### Public Load Balancer + +To create a public Load Balancer one has to specify a name of a public IP resource (existing or new) +in each frontend IP configuration. + +Example of a private Load Balancer with a single rule for port `80`: -## TODO +```hcl +module "lbe" { + source = "../../modules/loadbalancer" -- [ ] nsg_auto_rules_settings validation -- [ ] update documentation -- [ ] do the .header usage documentation -- [ ] adjust type in examples + name = "public-lb" + location = "West Europe" + resource_group_name = "existing-rg" -- [ ] fix vnet validation, if in for loop instead of a complex condition -- [ ] vnet validation for name property + frontend_ips = { + web = { + name = "web-traffic" + public_ip_name = "public-ip" + create_public_ip = true + in_rules = { + http = { + name = "http" + port = 80 + protocol = "TCP" + } + } + } + } +} +``` diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 95fbfbfe..89e17c0a 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -1,31 +1,103 @@ # Load Balancer Module for Azure -A Terraform module for deploying a Load Balancer for VM-Series firewalls. Supports both standalone and scale set deployments. Note, that due to that some properties are mutually exclusive. Please check the properties' description. +A Terraform module for deploying a Load Balancer for VM-Series firewalls. +Supports both standalone and scale set deployments. -The module creates a single load balancer and a single backend for it, but it allows multiple frontends. +> [!NOTE] +> The module can create a public or a private Load Balancer +> Due to that some properties are mutually exclusive. -In case of a public load balancer, you can define outbound rules and use the frontend's public IP address to access the internet. If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix SNAT with outbound rules for a single backend. +The module creates a single Load Balancer and a single backend for it, but it allows multiple frontends. + +> [!NOTE] +> In case of a public Load Balancer, you can define outbound rules and use the frontend's public IP address to access the internet. +> If this approach is chosen please note that all inbound rules will have the outbound SNAT disabled as you cannot mix +> SNAT with outbound rules for a single backend. ## Usage -For usage see any of the reference architecture examples. +There are two basic modes the module can work in: a public and a private Load Balancer. + +### Private Load Balancer + +To create a private Load Balancer one has to specify an ID of an existing Subnet and a private IP address +in each frontend IP configuration. + +Example of a private Load Balancer with HA ports rule: + +```hcl +module "lbi" { + source = "../../modules/loadbalancer" + + name = "private-lb" + location = "West Europe" + resource_group_name = "existing-rg" + + frontend_ips = { + ha = { + name = "HA" + subnet_id = "/subscription/xxxx/......." + private_ip_address = "10.0.0.1" + in_rules = { + ha = { + name = "HA" + port = 0 + protocol = "All" + } + } + } + } +} +``` + +### Public Load Balancer + +To create a public Load Balancer one has to specify a name of a public IP resource (existing or new) +in each frontend IP configuration. + +Example of a private Load Balancer with a single rule for port `80`: + +```hcl +module "lbe" { + source = "../../modules/loadbalancer" + + name = "public-lb" + location = "West Europe" + resource_group_name = "existing-rg" + + frontend_ips = { + web = { + name = "web-traffic" + public_ip_name = "public-ip" + create_public_ip = true + in_rules = { + http = { + name = "http" + port = 80 + protocol = "TCP" + } + } + } + } +} +``` ## Module's Required Inputs Name | Type | Description --- | --- | --- -[`name`](#name) | `string` | The name of the load balancer. +[`name`](#name) | `string` | The name of the Load Balancer. [`resource_group_name`](#resource_group_name) | `string` | Name of a pre-existing Resource Group to place the resources in. -[`location`](#location) | `string` | Region to deploy the resources. -[`frontend_ips`](#frontend_ips) | `map` | A map of objects describing LB Frontend IP configurations, inbound and outbound rules. +[`location`](#location) | `string` | Region to deploy the resources in. +[`frontend_ips`](#frontend_ips) | `map` | A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. ## Module's Optional Inputs Name | Type | Description --- | --- | --- -[`zones`](#zones) | `list` | Controls zones for load balancer's Fronted IP configurations. +[`zones`](#zones) | `list` | Controls zones for Load Balancer's Fronted IP configurations. [`tags`](#tags) | `map` | Azure tags to apply to the created resources. [`backend_name`](#backend_name) | `string` | The name of the backend pool to create. [`health_probes`](#health_probes) | `map` | Backend's health probe definition. @@ -75,7 +147,7 @@ Resources used in this module: #### name -The name of the load balancer. +The name of the Load Balancer. Type: string @@ -91,7 +163,7 @@ Type: string #### location -Region to deploy the resources. +Region to deploy the resources in. Type: string @@ -101,7 +173,7 @@ Type: string #### frontend_ips -A map of objects describing LB Frontend IP configurations, inbound and outbound rules. +A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. @@ -111,48 +183,61 @@ Private Load Balancer: - `name` - (`string`, required) name of a frontend IP configuration - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer -- `private_ip_address` - (`string`, optional, defaults to `null`) when assigned it will become the IP address of the Load Balancer, when skipped the IP will be assigned from DHCP. -- `gateway_load_balancer_frontend_ip_configuration_id` - ???? +- `private_ip_address` - (`string`, required) the IP address of the Load Balancer - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below +- `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration + of a Gateway Load Balancer Public Load Balancer: -- `name` - (`string`, required) name of a frontend IP configuration -- `public_ip_name` - (`string`, required) name of a public IP resource -- `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be created, otherwise an existing resource will be used; in both cases the name of the resource is controled by `public_ip_name` -- `public_ip_resource_group` - (`string`, optional, defaults to `null`) name of a Resource Group hosting an existing public IP resource -- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below -- `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below +- `name` - (`string`, required) name of a frontend IP configuration +- `public_ip_name` - (`string`, required) name of a public IP resource +- `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be + created, otherwise an existing resource will be used; + in both cases the name of the resource is controled by `public_ip_name` property +- `public_ip_resource_group` - (`string`, optional, defaults to the Load Balancer's RG) name of a Resource Group + hosting an existing public IP resource +- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below +- `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below -Below are the properties for the **inbound rules** map: +Below are the properties for the `in_rules` map: - `name` - (`string`, required) a name of an inbound rule - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool -- `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule +- `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining + a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. -- `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: +- `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, + three values are possible: - `Default` : this is the 5 tuple hash - `SourceIP` : a 2 tuple hash is used - `SourceIPProtocol` : a 3 tuple hash is used -- `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) +- `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, + when skipped the rule priority will be auto-calculated, + for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) -Below are the properties for **outbound rules** map. +Below are the properties for `out_rules` map. > [!Warning] -> Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: +> Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. +> Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, +> setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted -- `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` -- `enable_tcp_reset` - (`bool`, optional, defaults to `null`) ignored when `protocol` is set to `Udp` -- `idle_timeout_in_minutes` - (`number`, optional, defaults to `null`) TCP connection timeout in minutes (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` +- `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, + when skipped provider defaults will be used (`1024`), + when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` +- `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` +- `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes (between 4 and 120) + in case the connection is idle, ignored when `protocol` is set to `Udp` Examples ```hcl -# rules for a public LB, reusing an existing public IP and doing port translation +# rules for a public Load Balancer, reusing an existing public IP and doing port translation frontend_ips = { pip_existing = { create_public_ip = false @@ -168,7 +253,7 @@ frontend_ips = { } } -# rules for a private LB, with a static private IP address and one HA PORTs rule +# rules for a private Load Balancer, one HA PORTs rule frontend_ips = { internal = { subnet_id = azurerm_subnet.this.id @@ -182,7 +267,7 @@ frontend_ips = { } } -# rules for a public LB, session persistance with 2 tuple hash outbound rule defined +# rules for a public Load Balancer, session persistance with 2 tuple hash, outbound rule defined frontend_ips = { rule_1 = { create_public_ip = true @@ -253,7 +338,7 @@ map(object({ #### zones -Controls zones for load balancer's Fronted IP configurations. +Controls zones for Load Balancer's Fronted IP configurations. For: @@ -285,7 +370,7 @@ Default value: `map[]` #### backend_name -The name of the backend pool to create. All frontends of the load balancer always use the same backend. +The name of the backend pool to create. All frontends of the Load Balancer always use the same backend. Type: string @@ -297,16 +382,16 @@ Default value: `vmseries_backend` Backend's health probe definition. -When this property is not defined, or set to `null`, a default, TCP based probe will be created for port 80. +When this property is not defined, a default, TCP based probe will be created for port 80. Following properties are available: -- `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe -- `protocol` - (`string`, optional, defaults to `"TCP"`) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" -- `port` - (`number`, optional, defaults to `80`) port to run the probe against +- `name` - (`string`, required) name of the health check probe +- `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" +- `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run the probe against - `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint - `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 -- `request_path` - (`string`, optional, defaults to Azure defaults) the URI used to check the endpoint status when `protocol` is set to `Http(s)` +- `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, the URI used to check the endpoint status when `protocol` is set to `Http(s)` Type: @@ -323,7 +408,7 @@ map(object({ ``` -Default value: `map[default:map[name:vmseries_probe port:80 protocol:Tcp]]` +Default value: `&{}` [back to list](#modules-optional-inputs) @@ -331,12 +416,15 @@ Default value: `map[default:map[name:vmseries_probe port:80 protocol:Tcp]]` Controls automatic creation of NSG rules for all defined inbound rules. +When skipped or assigned an explicit `null`, disables rules creation. + Following properties are supported: - `nsg_name` - (`string`, required) name of an existing Network Security Group -- `resource_group_name - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the NSG -- `source_ips` - (`list`, required) either `*` or a list of CIDRs/IP addresses from which access to the frontends will be allowed -- `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow +- `resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG +- `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed +- `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow, + can take values between `100` and `4000` Type: diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index c3314cc5..58dd002e 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -70,6 +70,19 @@ resource "azurerm_lb" "this" { gateway_load_balancer_frontend_ip_configuration_id = frontend_ip.value.gateway_load_balancer_frontend_ip_configuration_id } } + + lifecycle { + precondition { + condition = !( + anytrue( + [for _, fip in var.frontend_ips : fip.public_ip_name != null] + ) && anytrue( + [for _, fip in var.frontend_ips : fip.subnet_id != null] + ) + ) + error_message = "All frontends have to be of the same type, either public or private. Please check module's documentation (Usage section) for details." + } + } } resource "azurerm_lb_backend_address_pool" "this" { @@ -82,10 +95,26 @@ locals { "Http" = 80 "Https" = "443" } + default_probe = ( + var.health_probes == null || anytrue(flatten([ + for _, fip in var.frontend_ips : [ + for _, rule in fip.in_rules : rule.health_probe_key == "default" + ] + ])) + ) ? { + default = { + name = "default_vmseries_probe" + protocol = "Tcp" + port = 80 + probe_threshold = null + interval_in_seconds = null + request_path = null + } + } : {} } resource "azurerm_lb_probe" "this" { - for_each = var.health_probes + for_each = merge(coalesce(var.health_probes, {}), local.default_probe) loadbalancer_id = azurerm_lb.this.id @@ -157,7 +186,7 @@ resource "azurerm_network_security_rule" "this" { name = "allow-inbound-ips-${each.key}" network_security_group_name = var.nsg_auto_rules_settings.nsg_name resource_group_name = coalesce(var.nsg_auto_rules_settings.nsg_resource_group_name, var.resource_group_name) - description = "Auto-generated for load balancer ${var.name} port ${each.value.rule.protocol}/${coalesce(each.value.rule.backend_port, each.value.rule.port)}: allowed inbound IP ranges" + description = "Auto-generated for load balancer ${var.name} port ${each.value.rule.protocol}/${coalesce(each.value.rule.backend_port, each.value.rule.port)}: allowed IPs: ${join(",", var.nsg_auto_rules_settings.source_ips)}" direction = "Inbound" access = "Allow" diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 89c623ee..18833f57 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -1,5 +1,5 @@ variable "name" { - description = "The name of the load balancer." + description = "The name of the Load Balancer." type = string } @@ -9,13 +9,13 @@ variable "resource_group_name" { } variable "location" { - description = "Region to deploy the resources." + description = "Region to deploy the resources in." type = string } variable "zones" { description = <<-EOF - Controls zones for load balancer's Fronted IP configurations. + Controls zones for Load Balancer's Fronted IP configurations. For: @@ -31,7 +31,7 @@ variable "zones" { type = list(string) validation { condition = length(var.zones) > 0 || var.zones == null - error_message = "The `var.zones` can either bea non empty list of Availability Zones or `null`." + error_message = "The `var.zones` can either bea non empty list of Availability Zones or explicit `null`." } } @@ -44,7 +44,7 @@ variable "tags" { variable "frontend_ips" { description = <<-EOF - A map of objects describing LB Frontend IP configurations, inbound and outbound rules. + A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. @@ -54,48 +54,61 @@ variable "frontend_ips" { - `name` - (`string`, required) name of a frontend IP configuration - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer - - `private_ip_address` - (`string`, optional, defaults to `null`) when assigned it will become the IP address of the Load Balancer, when skipped the IP will be assigned from DHCP. - - `gateway_load_balancer_frontend_ip_configuration_id` - ???? + - `private_ip_address` - (`string`, required) the IP address of the Load Balancer - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + - `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration + of a Gateway Load Balancer Public Load Balancer: - - `name` - (`string`, required) name of a frontend IP configuration - - `public_ip_name` - (`string`, required) name of a public IP resource - - `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be created, otherwise an existing resource will be used; in both cases the name of the resource is controled by `public_ip_name` - - `public_ip_resource_group` - (`string`, optional, defaults to `null`) name of a Resource Group hosting an existing public IP resource - - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below + - `name` - (`string`, required) name of a frontend IP configuration + - `public_ip_name` - (`string`, required) name of a public IP resource + - `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be + created, otherwise an existing resource will be used; + in both cases the name of the resource is controled by `public_ip_name` property + - `public_ip_resource_group` - (`string`, optional, defaults to the Load Balancer's RG) name of a Resource Group + hosting an existing public IP resource + - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below - Below are the properties for the **inbound rules** map: + Below are the properties for the `in_rules` map: - `name` - (`string`, required) a name of an inbound rule - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool - - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule + - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining + a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: + - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, + three values are possible: - `Default` : this is the 5 tuple hash - `SourceIP` : a 2 tuple hash is used - `SourceIPProtocol` : a 3 tuple hash is used - - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) + - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, + when skipped the rule priority will be auto-calculated, + for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) - Below are the properties for **outbound rules** map. + Below are the properties for `out_rules` map. > [!Warning] - > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: + > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. + > Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, + > setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` - - `enable_tcp_reset` - (`bool`, optional, defaults to `null`) ignored when `protocol` is set to `Udp` - - `idle_timeout_in_minutes` - (`number`, optional, defaults to `null`) TCP connection timeout in minutes (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` + - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, + when skipped provider defaults will be used (`1024`), + when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` + - `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` + - `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes (between 4 and 120) + in case the connection is idle, ignored when `protocol` is set to `Udp` Examples ```hcl - # rules for a public LB, reusing an existing public IP and doing port translation + # rules for a public Load Balancer, reusing an existing public IP and doing port translation frontend_ips = { pip_existing = { create_public_ip = false @@ -111,7 +124,7 @@ variable "frontend_ips" { } } - # rules for a private LB, with a static private IP address and one HA PORTs rule + # rules for a private Load Balancer, one HA PORTs rule frontend_ips = { internal = { subnet_id = azurerm_subnet.this.id @@ -125,7 +138,7 @@ variable "frontend_ips" { } } - # rules for a public LB, session persistance with 2 tuple hash outbound rule defined + # rules for a public Load Balancer, session persistance with 2 tuple hash, outbound rule defined frontend_ips = { rule_1 = { create_public_ip = true @@ -174,10 +187,26 @@ variable "frontend_ips" { idle_timeout_in_minutes = optional(number) })), {}) })) + validation { + condition = !( # unified LB type + anytrue( + [for _, fip in var.frontend_ips : fip.public_ip_name != null] + ) && anytrue( + [for _, fip in var.frontend_ips : fip.subnet_id != null] + ) + ) + error_message = "All frontends have to be of the same type, either public or private. Please check module's documentation (Usage section) for details." + } validation { # name condition = length(flatten([for _, v in var.frontend_ips : v.name])) == length(distinct(flatten([for _, v in var.frontend_ips : v.name]))) error_message = "The `name` property has to be unique among all frontend definitions." } + validation { # private_ip_address + condition = alltrue([ + for _, fip in var.frontend_ips : fip.private_ip_address != null if fip.subnet_id != null + ]) + error_message = "The `private_ip_address` id required for private Load Balancers." + } validation { # private_ip_address condition = alltrue([ for _, fip in var.frontend_ips : @@ -281,7 +310,7 @@ variable "frontend_ips" { } variable "backend_name" { - description = "The name of the backend pool to create. All frontends of the load balancer always use the same backend." + description = "The name of the backend pool to create. All frontends of the Load Balancer always use the same backend." default = "vmseries_backend" nullable = false type = string @@ -291,25 +320,18 @@ variable "health_probes" { description = <<-EOF Backend's health probe definition. - When this property is not defined, or set to `null`, a default, TCP based probe will be created for port 80. + When this property is not defined, a default, TCP based probe will be created for port 80. Following properties are available: - - `name` - (`string`, optional, defaults to `"vmseries_probe"`) name of the health check probe - - `protocol` - (`string`, optional, defaults to `"TCP"`) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" - - `port` - (`number`, optional, defaults to `80`) port to run the probe against + - `name` - (`string`, required) name of the health check probe + - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" + - `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run the probe against - `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint - `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 - - `request_path` - (`string`, optional, defaults to Azure defaults) the URI used to check the endpoint status when `protocol` is set to `Http(s)` + - `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, the URI used to check the endpoint status when `protocol` is set to `Http(s)` EOF - default = { - default = { - name = "vmseries_probe" - protocol = "Tcp" - port = 80 - } - } - nullable = false + default = null type = map(object({ name = string protocol = string @@ -318,41 +340,49 @@ variable "health_probes" { interval_in_seconds = optional(number) request_path = optional(string, "/") })) + validation { # keys + condition = var.health_probes == null ? true : !anytrue([for k, _ in var.health_probes : k == "default"]) + error_message = "The key describing a health probe cannot be \"default\"." + } validation { # name - condition = length(flatten([for _, v in var.health_probes : v.name])) == length(distinct(flatten([for _, v in var.health_probes : v.name]))) + condition = var.health_probes == null ? true : length([for _, v in var.health_probes : v.name]) == length(distinct([for _, v in var.health_probes : v.name])) error_message = "The `name` property has to be unique among all health probe definitions." } + validation { # name + condition = var.health_probes == null ? true : !anytrue([for _, v in var.health_probes : v.name == "default_vmseries_probe"]) + error_message = "The `name` property cannot be \"default_vmseries_probe\"." + } validation { # protocol - condition = alltrue([for k, v in var.health_probes : contains(["Tcp", "Http", "Https"], v.protocol)]) + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : contains(["Tcp", "Http", "Https"], v.protocol)]) error_message = "The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\"." } validation { # port - condition = alltrue([for k, v in var.health_probes : v.port != null if v.protocol == "Tcp"]) + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.port != null if v.protocol == "Tcp"]) error_message = "The `port` property is required when protocol is set to \"Tcp\"." } validation { # port - condition = alltrue([for k, v in var.health_probes : + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.port >= 1 && v.port <= 65535 if v.port != null ]) error_message = "The `port` property has to be a valid TCP port." } validation { # interval_in_seconds - condition = alltrue([for k, v in var.health_probes : + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.interval_in_seconds >= 5 && v.interval_in_seconds <= 3600 if v.interval_in_seconds != null ]) error_message = "The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour)." } validation { # probe_threshold - condition = alltrue([for k, v in var.health_probes : + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.probe_threshold >= 1 && v.probe_threshold <= 100 if v.probe_threshold != null ]) error_message = "The `probe_threshold` property has to be between 1 and 100." } validation { # request - condition = alltrue([for k, v in var.health_probes : v.request_path != null if v.protocol != "Tcp"]) + condition = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.request_path != null if v.protocol != "Tcp"]) error_message = "value" } } @@ -361,12 +391,15 @@ variable "nsg_auto_rules_settings" { description = <<-EOF Controls automatic creation of NSG rules for all defined inbound rules. + When skipped or assigned an explicit `null`, disables rules creation. + Following properties are supported: - `nsg_name` - (`string`, required) name of an existing Network Security Group - - `resource_group_name - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the NSG - - `source_ips` - (`list`, required) either `*` or a list of CIDRs/IP addresses from which access to the frontends will be allowed - - `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow + - `resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG + - `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed + - `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow, + can take values between `100` and `4000` EOF default = null type = object({ @@ -375,4 +408,18 @@ variable "nsg_auto_rules_settings" { source_ips = list(string) base_priority = optional(number, 1000) }) + validation { # source_ips + condition = var.nsg_auto_rules_settings != null ? alltrue([ + for ip in var.nsg_auto_rules_settings.source_ips : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/0|\\/[12]?[0-9]|\\/3[0-2])?$", ip)) + ]) : true + error_message = "The `source_ips` property can an IPv4 address or address space in CIDR notation." + } + validation { # base_priority + condition = try( + var.nsg_auto_rules_settings.base_priority >= 100 && var.nsg_auto_rules_settings.base_priority <= 4000, + true + ) + error_message = "The `base_priority` property can take only values between `100` and `4000`." + } } From 4ec98da9c7a0376d38463290df7d5d62db1a0121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Mon, 23 Oct 2023 15:36:43 +0200 Subject: [PATCH 06/11] update example's documentation --- examples/lb/.header.md | 6 +- examples/lb/README.md | 114 ++++++++++-------------------- examples/lb/example.tfvars | 4 +- examples/lb/variables.tf | 106 ++++++++++----------------- modules/loadbalancer/README.md | 70 +++++++++++------- modules/loadbalancer/variables.tf | 70 +++++++++++------- 6 files changed, 167 insertions(+), 203 deletions(-) diff --git a/examples/lb/.header.md b/examples/lb/.header.md index 24dfd8ae..46e6e81d 100644 --- a/examples/lb/.header.md +++ b/examples/lb/.header.md @@ -1,5 +1,3 @@ -# VNET module sample +# LB -A sample of using a VNET module with the new variables layout and usage of `optional` keyword. - -The `README` is also in new, document-style format. +Sample LB documentation. diff --git a/examples/lb/README.md b/examples/lb/README.md index b1bdf818..a595c522 100644 --- a/examples/lb/README.md +++ b/examples/lb/README.md @@ -1,9 +1,7 @@ -# VNET module sample +# LB -A sample of using a VNET module with the new variables layout and usage of `optional` keyword. - -The `README` is also in new, document-style format. +Sample LB documentation. ## Module's Required Inputs @@ -21,7 +19,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`name_prefix`](#name_prefix) | `string` | A prefix that will be added to all created resources. [`create_resource_group`](#create_resource_group) | `bool` | When set to `true` it will cause a Resource Group creation. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. @@ -164,7 +162,8 @@ Default value: `map[]` #### name_prefix A prefix that will be added to all created resources. -There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. Example: ```hcl @@ -196,81 +195,46 @@ Default value: `true` #### load_balancers -A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - -Following properties are available: - -- `name`: name of the Load Balancer resource. -- `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. -- `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. -- `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). -- `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. -- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. -- `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). -- `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - -Example of a public Load Balancer: - -``` -"public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } -} -``` +A map containing configuration for all (private and public) Load Balancers. -Example of a private Load Balancer with HA PORTS rule: +This is a brief description of available properties. For a detailed one please refer to +[module documentation](../../modules/loadbalancer/README.md). -``` -"private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } -} -``` +Following properties are available: +- `name` - (`string`, required) a name of the Load Balancer +- `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties +- `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` +- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`; please refer to + [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available + properties; please note that in this example the `subnet_id` is not available directly, two other + properties were introduced instead: + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` Type: ```hcl map(object({ - name = string - vnet_key = optional(string) - subnet_key = optional(string) - zones = optional(list(string), ["1", "2", "3"]) + name = string + zones = optional(list(string), ["1", "2", "3"]) health_probes = optional(map(object({ name = string protocol = string @@ -290,7 +254,7 @@ map(object({ frontend_ips = optional(map(object({ name = string public_ip_name = optional(string) - create_public_ip = optional(bool, false) + create_public_ip = optional(bool) public_ip_resource_group = optional(string) vnet_key = optional(string) subnet_key = optional(string) @@ -302,8 +266,8 @@ map(object({ port = number backend_port = optional(number) health_probe_key = optional(string) - floating_ip = optional(bool, true) - session_persistence = optional(string, "Default") + floating_ip = optional(bool) + session_persistence = optional(string) nsg_priority = optional(number) })), {}) out_rules = optional(map(object({ diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars index ee51d5af..555a2bde 100644 --- a/examples/lb/example.tfvars +++ b/examples/lb/example.tfvars @@ -102,8 +102,8 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1"] + name = "private-lb" + zones = ["1"] frontend_ips = { "ha-ports" = { name = "HA" diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf index 755f194a..98730df1 100644 --- a/examples/lb/variables.tf +++ b/examples/lb/variables.tf @@ -13,7 +13,8 @@ variable "location" { variable "name_prefix" { description = <<-EOF A prefix that will be added to all created resources. - There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix. + There is no default delimiter applied between the prefix and the resource name. + Please include the delimiter in the actual prefix. Example: ```hcl @@ -106,79 +107,44 @@ variable "vnets" { variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. + A map containing configuration for all (private and public) Load Balancers. - Following properties are available: - - - `name`: name of the Load Balancer resource. - - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. - - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. - - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: - - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` - - Example of a private Load Balancer with HA PORTS rule: + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` + Following properties are available: + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`; please refer to + [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available + properties; please note that in this example the `subnet_id` is not available directly, two other + properties were introduced instead: + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} nullable = false type = map(object({ - name = string - vnet_key = optional(string) - subnet_key = optional(string) - zones = optional(list(string), ["1", "2", "3"]) + name = string + zones = optional(list(string), ["1", "2", "3"]) health_probes = optional(map(object({ name = string protocol = string @@ -198,7 +164,7 @@ variable "load_balancers" { frontend_ips = optional(map(object({ name = string public_ip_name = optional(string) - create_public_ip = optional(bool, false) + create_public_ip = optional(bool) public_ip_resource_group = optional(string) vnet_key = optional(string) subnet_key = optional(string) @@ -210,8 +176,8 @@ variable "load_balancers" { port = number backend_port = optional(number) health_probe_key = optional(string) - floating_ip = optional(bool, true) - session_persistence = optional(string, "Default") + floating_ip = optional(bool) + session_persistence = optional(string) nsg_priority = optional(number) })), {}) out_rules = optional(map(object({ diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 89e17c0a..82f9f0f7 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -175,9 +175,11 @@ Type: string A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. -Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. +Each Frontend IP configuration can have multiple rules assigned. +They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. -Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. To ease configuration they were grouped per Load Balancer type. +Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. +To ease configuration they were grouped per Load Balancer type. Private Load Balancer: @@ -185,8 +187,8 @@ Private Load Balancer: - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer - `private_ip_address` - (`string`, required) the IP address of the Load Balancer - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below -- `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration - of a Gateway Load Balancer +- `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of + a frontend IP configuration of a Gateway Load Balancer Public Load Balancer: @@ -199,21 +201,23 @@ Public Load Balancer: hosting an existing public IP resource - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below - + Below are the properties for the `in_rules` map: - `name` - (`string`, required) a name of an inbound rule - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. -- `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports -- `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool +- `port` - (`number`, required) communication port, this is both the front- and the backend port + if `backend_port` is not set; value of `0` means all ports +- `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic + to in the backend pool - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: - - `Default` : this is the 5 tuple hash - - `SourceIP` : a 2 tuple hash is used - - `SourceIPProtocol` : a 3 tuple hash is used + - `Default` - this is the 5 tuple hash + - `SourceIP` - a 2 tuple hash is used + - `SourceIPProtocol` - a 3 tuple hash is used - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) @@ -222,16 +226,19 @@ Below are the properties for `out_rules` map. > [!Warning] > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. -> Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, -> setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: +> Keep in mind that since we use a single backend, +> and you cannot mix SNAT and outbound rules traffic in rules using the same backend, +> setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`. - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), - when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` + when set to `0` port allocation will be set to default number (Azure defaults); + maximum value is `64000` - `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` -- `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes (between 4 and 120) +- `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes + (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` Examples @@ -343,12 +350,14 @@ Controls zones for Load Balancer's Fronted IP configurations. For: - public IPs - these are zones in which the public IP resource is available -- private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs (all frontends are affected) +- private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs + (all frontends are affected) Setting this variable to explicit `null` disables a zonal deployment. This can be helpful in regions where Availability Zones are not available. -For public Load Balancers, since this setting controls also Availability Zones for public IPs, you need to specify all zones available in a region (typically 3): `["1","2","3"]` +For public Load Balancers, since this setting controls also Availability Zones for public IPs, +you need to specify all zones available in a region (typically 3): `["1","2","3"]`. Type: list(string) @@ -382,16 +391,25 @@ Default value: `vmseries_backend` Backend's health probe definition. -When this property is not defined, a default, TCP based probe will be created for port 80. +When this property is either: + +- not defined at all, or +- at least one `in_rule` has no health probe specified + +a default, TCP based probe will be created for port 80. Following properties are available: - `name` - (`string`, required) name of the health check probe - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" -- `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run the probe against -- `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint -- `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 -- `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, the URI used to check the endpoint status when `protocol` is set to `Http(s)` +- `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run + the probe against +- `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide + on forwarding traffic to an endpoint +- `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, + with a minimal value of 5 +- `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, + the URI used to check the endpoint status when `protocol` is set to `Http(s)` Type: @@ -420,11 +438,11 @@ When skipped or assigned an explicit `null`, disables rules creation. Following properties are supported: -- `nsg_name` - (`string`, required) name of an existing Network Security Group -- `resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG -- `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed -- `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow, - can take values between `100` and `4000` +- `nsg_name` - (`string`, required) name of an existing Network Security Group +- `nsg_resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG +- `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed +- `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all + auto-generated rules grow, can take values between `100` and `4000` Type: diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 18833f57..29cd3bde 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -20,12 +20,14 @@ variable "zones" { For: - public IPs - these are zones in which the public IP resource is available - - private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs (all frontends are affected) + - private IPs - this represents Zones to which Azure will deploy paths leading to Load Balancer frontend IPs + (all frontends are affected) Setting this variable to explicit `null` disables a zonal deployment. This can be helpful in regions where Availability Zones are not available. - For public Load Balancers, since this setting controls also Availability Zones for public IPs, you need to specify all zones available in a region (typically 3): `["1","2","3"]` + For public Load Balancers, since this setting controls also Availability Zones for public IPs, + you need to specify all zones available in a region (typically 3): `["1","2","3"]`. EOF default = ["1", "2", "3"] type = list(string) @@ -46,9 +48,11 @@ variable "frontend_ips" { description = <<-EOF A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. - Each Frontend IP configuration can have multiple rules assigned. They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. + Each Frontend IP configuration can have multiple rules assigned. + They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. - Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. To ease configuration they were grouped per Load Balancer type. + Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. + To ease configuration they were grouped per Load Balancer type. Private Load Balancer: @@ -56,8 +60,8 @@ variable "frontend_ips" { - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer - `private_ip_address` - (`string`, required) the IP address of the Load Balancer - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - - `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration - of a Gateway Load Balancer + - `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of + a frontend IP configuration of a Gateway Load Balancer Public Load Balancer: @@ -70,21 +74,23 @@ variable "frontend_ips" { hosting an existing public IP resource - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below - + Below are the properties for the `in_rules` map: - `name` - (`string`, required) a name of an inbound rule - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - - `port` - (`number`, required) communication port, this is both the front- and the backend port if `backend_port` is not set; value of `0` means all ports - - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic to in the backend pool + - `port` - (`number`, required) communication port, this is both the front- and the backend port + if `backend_port` is not set; value of `0` means all ports + - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic + to in the backend pool - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining a health probe to use with this rule - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, three values are possible: - - `Default` : this is the 5 tuple hash - - `SourceIP` : a 2 tuple hash is used - - `SourceIPProtocol` : a 3 tuple hash is used + - `Default` - this is the 5 tuple hash + - `SourceIP` - a 2 tuple hash is used + - `SourceIPProtocol` - a 3 tuple hash is used - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, when skipped the rule priority will be auto-calculated, for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) @@ -93,16 +99,19 @@ variable "frontend_ips" { > [!Warning] > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. - > Keep in mind that since we use a single backend, and you cannot mix SNAT and outbound rules traffic in rules using the same backend, - > setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`: + > Keep in mind that since we use a single backend, + > and you cannot mix SNAT and outbound rules traffic in rules using the same backend, + > setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`. - `name` - (`string`, required) a name of an outbound rule - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, when skipped provider defaults will be used (`1024`), - when set to `0` port allocation will be set to default number (Azure defaults); maximum value is `64000` + when set to `0` port allocation will be set to default number (Azure defaults); + maximum value is `64000` - `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` - - `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes (between 4 and 120) + - `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes + (between 4 and 120) in case the connection is idle, ignored when `protocol` is set to `Udp` Examples @@ -320,16 +329,25 @@ variable "health_probes" { description = <<-EOF Backend's health probe definition. - When this property is not defined, a default, TCP based probe will be created for port 80. + When this property is either: + + - not defined at all, or + - at least one `in_rule` has no health probe specified + + a default, TCP based probe will be created for port 80. Following properties are available: - `name` - (`string`, required) name of the health check probe - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https" - - `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run the probe against - - `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide on forwarding traffic to an endpoint - - `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, with a minimal value of 5 - - `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, the URI used to check the endpoint status when `protocol` is set to `Http(s)` + - `port` - (`number`, required for `Tcp`, defaults to protocol port for `Http(s)` probes) port to run + the probe against + - `probe_threshold` - (`number`, optional, defaults to Azure defaults) number of consecutive probes that decide + on forwarding traffic to an endpoint + - `interval_in_seconds` - (`number, optional, defaults to Azure defaults) interval in seconds between probes, + with a minimal value of 5 + - `request_path` - (`string`, optional, defaults to `/`) used only for non `Tcp` probes, + the URI used to check the endpoint status when `protocol` is set to `Http(s)` EOF default = null type = map(object({ @@ -395,11 +413,11 @@ variable "nsg_auto_rules_settings" { Following properties are supported: - - `nsg_name` - (`string`, required) name of an existing Network Security Group - - `resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG - - `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed - - `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all auto-generated rules grow, - can take values between `100` and `4000` + - `nsg_name` - (`string`, required) name of an existing Network Security Group + - `nsg_resource_group_name - (`string`, optional, defaults to Load Balancer's RG) name of a Resource Group hosting the NSG + - `source_ips` - (`list`, required) list of CIDRs/IP addresses from which access to the frontends will be allowed + - `base_priority` - (`nubmer`, optional, defaults to `1000`) minimum rule priority from which all + auto-generated rules grow, can take values between `100` and `4000` EOF default = null type = object({ From 5d412e3b3aecd54e9e19f4b682b6cb272fe04435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Wed, 25 Oct 2023 09:12:00 +0200 Subject: [PATCH 07/11] adjust examples --- examples/common_vmseries/example.tfvars | 11 +- examples/common_vmseries/main.tf | 46 +++--- examples/common_vmseries/variables.tf | 148 ++++++++++-------- .../common_vmseries_and_autoscale/main.tf | 47 +++--- .../variables.tf | 142 +++++++++-------- examples/dedicated_vmseries/main.tf | 45 +++--- examples/dedicated_vmseries/variables.tf | 148 ++++++++++-------- .../dedicated_vmseries_and_autoscale/main.tf | 47 +++--- .../variables.tf | 142 +++++++++-------- examples/gwlb_with_vmseries/main.tf | 52 +++--- examples/gwlb_with_vmseries/variables.tf | 148 +++++++++++------- examples/lb/README.md | 16 +- examples/lb/variables.tf | 16 +- examples/standalone_vmseries/main.tf | 45 +++--- examples/standalone_vmseries/variables.tf | 143 +++++++++-------- modules/loadbalancer/README.md | 26 +-- modules/loadbalancer/main.tf | 2 +- modules/loadbalancer/variables.tf | 26 +-- 18 files changed, 700 insertions(+), 550 deletions(-) diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 3e439335..c22871a8 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -122,12 +122,14 @@ load_balancers = { nsg_vnet_key = "transit" nsg_key = "public" network_security_allow_source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer - avzones = ["1", "2", "3"] frontend_ips = { - "palo-lb-app1" = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" create_public_ip = true in_rules = { "balanceHttp" = { + name = "HTTP" protocol = "Tcp" port = 80 } @@ -136,15 +138,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] + name = "private-lb" frontend_ips = { "ha-ports" = { + name = "private-vmseries" vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 77df6a95..8e7b3a1f 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -103,32 +103,35 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones - network_security_group_name = try( - "${var.name_prefix}${var.vnets[each.value.nsg_vnet_key].network_security_groups[each.value.nsg_key].name}", - each.value.network_security_group_name, - null - ) - # network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try( - var.vnets[each.value.nsg_vnet_key].resource_group_name, - each.value.network_security_group_rg_name, + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, null ) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags @@ -137,6 +140,7 @@ module "load_balancer" { + # create the actual VMSeries VMs and resources module "ai" { source = "../../modules/application_insights" diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 3ab1d4f6..a9ebd724 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -154,73 +154,91 @@ variable "natgws" { ### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - - Following properties are available (for details refer to module's documentation): - - - `name`: name of the Load Balancer resource. - - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. - - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. - - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: - - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` - - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` - + A map containing configuration for all (private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index f196086a..86c5d15b 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -97,32 +97,35 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) - - network_security_group_name = try( - "${var.name_prefix}${var.vnets[each.value.nsg_vnet_key].network_security_groups[each.value.nsg_key].name}", - each.value.network_security_group_name, - null - ) - # network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try( - var.vnets[each.value.nsg_vnet_key].resource_group_name, - each.value.network_security_group_rg_name, + zones = each.value.zones + + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, null ) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 64821d3b..5f05a8d2 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -154,73 +154,91 @@ variable "natgws" { ### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - - Following properties are available (for details refer to module's documentation): - - - `name`: name of the Load Balancer resource. - - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. - - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. - - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: + A map containing configuration for all (private and public) Load Balancers. - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` + Following properties are available: + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 295e249d..3cc433c9 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -102,32 +102,35 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones - network_security_group_name = try( - "${var.name_prefix}${var.vnets[each.value.nsg_vnet_key].network_security_groups[each.value.nsg_key].name}", - each.value.network_security_group_name, - null - ) - # network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try( - var.vnets[each.value.nsg_vnet_key].resource_group_name, - each.value.network_security_group_rg_name, + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, null ) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 5f2929ee..afbd2e78 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -154,73 +154,91 @@ variable "natgws" { ### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - - Following properties are available (for details refer to module's documentation): - - - `name`: name of the Load Balancer resource. - - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. - - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. - - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: - - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` - - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` - + A map containing configuration for all (private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index f196086a..86c5d15b 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -97,32 +97,35 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) - - network_security_group_name = try( - "${var.name_prefix}${var.vnets[each.value.nsg_vnet_key].network_security_groups[each.value.nsg_key].name}", - each.value.network_security_group_name, - null - ) - # network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try( - var.vnets[each.value.nsg_vnet_key].resource_group_name, - each.value.network_security_group_rg_name, + zones = each.value.zones + + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, null ) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 64821d3b..5f05a8d2 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -154,73 +154,91 @@ variable "natgws" { ### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - - Following properties are available (for details refer to module's documentation): - - - `name`: name of the Load Balancer resource. - - `nsg_vnet_key`: (public LB) defaults to `null`, a key describing a vnet (as defined in `vnet` variable) that hold an NSG we will update with an ingress rule for each listener. - - `nsg_key`: (public LB) defaults to `null`, a key describing an NSG (as defined in `vnet` variable, under `nsg_vnet_key`) we will update with an ingress rule for each listener. - - `network_security_group_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) defaults to `null`, in case of a brownfield deployment (no possibility to depend on `vnet` variable), a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: + A map containing configuration for all (private and public) Load Balancers. - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` + Following properties are available: + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 8cf983c8..44a64e9b 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -249,39 +249,51 @@ module "vmseries" { # Sample application VMs and Load Balancers module "load_balancer" { + source = "../../modules/loadbalancer" + for_each = var.load_balancers - source = "../../modules/loadbalancer" name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones - network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try(each.value.network_security_group_rg_name, null) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, + null + ) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - zones = var.enable_zones ? try(v.zones, null) : null # For the regions without AZ support. - - gateway_load_balancer_frontend_ip_configuration_id = try(v.gwlb_key, null) != null ? module.gwlb[v.gwlb_key].frontend_ip_config_id : null - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + gwlb_fip_id = try(module.gwlb[v.gwlb_key].frontend_ip_config_id, null) + } + ) } - tags = var.tags - + tags = var.tags depends_on = [module.vnet] } + + resource "random_password" "appvms" { count = try(var.appvms_common.password, null) == null ? 1 : 0 diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index bf006d10..f8fd7e89 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -234,67 +234,97 @@ variable "availability_sets" { # Application variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - Following properties are available (for details refer to module's documentation): - - `name` - (required|string) Name of the Load Balancer resource. - - `network_security_group_name` - (optional|string) Public LB only - name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name` - (optional|string) Public LB only - name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips` - (optional|string) Public LB only - list of IP addresses that will be allowed in the ingress rules. - - `avzones` - (optional|list) For regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips` - (optional|map) Map configuring both a listener and load balancing/outbound rules, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), values are objects with the following properties: - - `create_public_ip` - (optional|bool) Public LB only - defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name` - (optional|string) Public LB only - defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group` - (optional|string) Public LB only - defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address` - (optional|string) Private LB only - defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key` - (optional|string) Private LB only - defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key` - (optional|string) Private LB only - defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `in_rules`/`out_rules` - (optional|map) Configuration of load balancing/outbound rules, please refer to [load_balancer module documentation](../../modules/loadbalancer/README.md#inputs) for details. - - Example of a public Load Balancer: - - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` - - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "internal_app_lb" - frontend_ips = { - "ha-ports" = { - vnet_key = "internal_app_vnet" - subnet_key = "internal_app_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` - + A map containing configuration for all (private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` + + > [!NOTE] + > The `gwlb_fip_id` property is not available directly as well, it was replaced by `gwlb_key`. + + - `gwlb_key` - (`string`, optional, defaults to `null`) a key pointing to a GWLB definition in the + `var.gateway_load_balancers`map. EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } variable "appvms_common" { diff --git a/examples/lb/README.md b/examples/lb/README.md index a595c522..5ad6de64 100644 --- a/examples/lb/README.md +++ b/examples/lb/README.md @@ -252,14 +252,14 @@ map(object({ base_priority = optional(number) })) frontend_ips = optional(map(object({ - name = string - public_ip_name = optional(string) - create_public_ip = optional(bool) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gateway_load_balancer_frontend_ip_configuration_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) in_rules = optional(map(object({ name = string protocol = string diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf index 98730df1..8a422944 100644 --- a/examples/lb/variables.tf +++ b/examples/lb/variables.tf @@ -162,14 +162,14 @@ variable "load_balancers" { base_priority = optional(number) })) frontend_ips = optional(map(object({ - name = string - public_ip_name = optional(string) - create_public_ip = optional(bool) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gateway_load_balancer_frontend_ip_configuration_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) in_rules = optional(map(object({ name = string protocol = string diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index a01f8cdc..af27cd17 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -102,32 +102,35 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones - network_security_group_name = try( - "${var.name_prefix}${var.vnets[each.value.nsg_vnet_key].network_security_groups[each.value.nsg_key].name}", - each.value.network_security_group_name, - null - ) - # network_security_group_name = try(each.value.network_security_group_name, null) - network_security_resource_group_name = try( - var.vnets[each.value.nsg_vnet_key].resource_group_name, - each.value.network_security_group_rg_name, + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, null ) - network_security_allow_source_ips = try(each.value.network_security_allow_source_ips, []) frontend_ips = { - for k, v in each.value.frontend_ips : k => { - create_public_ip = try(v.create_public_ip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - private_ip_address = try(v.private_ip_address, null) - subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) - in_rules = try(v.in_rules, {}) - out_rules = try(v.out_rules, {}) - } + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index a60942fa..cb249ab5 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -154,71 +154,88 @@ variable "natgws" { ### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancer that will be created in this deployment. - - Following properties are available (for details refer to module's documentation): - - - `name`: name of the Load Balancer resource. - - `network_security_group_name`: (public LB) a name of a security group, an ingress rule will be created in that NSG for each listener. **NOTE** this is the FULL NAME of the NSG (including prefixes). - - `network_security_group_rg_name`: (public LB) a name of a resource group for the security group, to be used when the NSG is hosted in a different RG than the one described in `var.resource_group_name`. - - `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules. - - `avzones`: (both) for regional Load Balancers, a list of supported zones (this has different meaning for public and private LBs - please refer to module's documentation for details). - - `frontend_ips`: (both) a map configuring both a listener and a load balancing rule, key is the name that will be used as an application name inside LB config as well as to create a rule in NSG (for public LBs), value is an object with the following properties: - - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener - - `public_ip_name`: (public LB) defaults to `null`, when `create_public_ip` is set to `false` this property is used to reference an existing Public IP object in Azure - - `public_ip_resource_group`: (public LB) defaults to `null`, when using an existing Public IP created in a different Resource Group than the currently used use this property is to provide the name of that RG - - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener - - `vnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a vnet's key (as defined in `vnet` variable). This will be the VNET hosting this Load Balancer - - `subnet_key`: (private LB) defaults to `null`, when `private_ip_address` is set specifies a subnet's key (as defined in `vnet` variable) to which the LB will be attached, in case of VMSeries this should be a internal/trust subnet - - `rules` - a map configuring the actual rules load balancing rules, a key is a rule name, a value is an object with the following properties: - - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule - - `port`: port used by the rule, for HA PORTS rule set this to `0` - - Example of a public Load Balancer: - - ``` - "public_lb" = { - name = "https_app_lb" - network_security_group_name = "untrust_nsg" - network_security_allow_source_ips = ["1.2.3.4"] - avzones = ["1", "2", "3"] - frontend_ips = { - "https_app_1" = { - create_public_ip = true - rules = { - "balanceHttps" = { - protocol = "Tcp" - port = 443 - } - } - } - } - } - ``` - - Example of a private Load Balancer with HA PORTS rule: - - ``` - "private_lb" = { - name = "ha_ports_internal_lb - frontend_ips = { - "ha-ports" = { - vnet_key = "trust_vnet" - subnet_key = "trust_snet" - private_ip_address = "10.0.0.1" - rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - } - ``` - + A map containing configuration for all (private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`; please refer to + [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available + properties; please note that in this example the `subnet_id` is not available directly, two other + properties were introduced instead: + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` EOF default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 82f9f0f7..5376232c 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -183,12 +183,12 @@ To ease configuration they were grouped per Load Balancer type. Private Load Balancer: -- `name` - (`string`, required) name of a frontend IP configuration -- `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer -- `private_ip_address` - (`string`, required) the IP address of the Load Balancer -- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below -- `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of - a frontend IP configuration of a Gateway Load Balancer +- `name` - (`string`, required) name of a frontend IP configuration +- `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer +- `private_ip_address` - (`string`, required) the IP address of the Load Balancer +- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below +- `gwlb_fip_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration + of a Gateway Load Balancer Public Load Balancer: @@ -302,13 +302,13 @@ Type: ```hcl map(object({ - name = string - public_ip_name = optional(string) - create_public_ip = optional(bool, false) - public_ip_resource_group = optional(string) - subnet_id = optional(string) - private_ip_address = optional(string) - gateway_load_balancer_frontend_ip_configuration_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + subnet_id = optional(string) + private_ip_address = optional(string) + gwlb_fip_id = optional(string) in_rules = optional(map(object({ name = string protocol = string diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index 58dd002e..044617ee 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -67,7 +67,7 @@ resource "azurerm_lb" "this" { private_ip_address = frontend_ip.value.private_ip_address zones = frontend_ip.value.subnet_id != null ? var.zones : null - gateway_load_balancer_frontend_ip_configuration_id = frontend_ip.value.gateway_load_balancer_frontend_ip_configuration_id + gateway_load_balancer_frontend_ip_configuration_id = frontend_ip.value.gwlb_fip_id } } diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 29cd3bde..59458547 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -56,12 +56,12 @@ variable "frontend_ips" { Private Load Balancer: - - `name` - (`string`, required) name of a frontend IP configuration - - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer - - `private_ip_address` - (`string`, required) the IP address of the Load Balancer - - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - - `gateway_load_balancer_frontend_ip_configuration_id` - (`string`, optional, defaults to `null`) an ID of - a frontend IP configuration of a Gateway Load Balancer + - `name` - (`string`, required) name of a frontend IP configuration + - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer + - `private_ip_address` - (`string`, required) the IP address of the Load Balancer + - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below + - `gwlb_fip_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration + of a Gateway Load Balancer Public Load Balancer: @@ -171,13 +171,13 @@ variable "frontend_ips" { ``` EOF type = map(object({ - name = string - public_ip_name = optional(string) - create_public_ip = optional(bool, false) - public_ip_resource_group = optional(string) - subnet_id = optional(string) - private_ip_address = optional(string) - gateway_load_balancer_frontend_ip_configuration_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group = optional(string) + subnet_id = optional(string) + private_ip_address = optional(string) + gwlb_fip_id = optional(string) in_rules = optional(map(object({ name = string protocol = string From aad5e5b61a31b7870a87142c3d0cbc681f9a6c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Wed, 25 Oct 2023 10:10:32 +0200 Subject: [PATCH 08/11] fixing broken examples --- .../common_vmseries_and_autoscale/example.tfvars | 14 ++++++++------ examples/dedicated_vmseries/example.tfvars | 11 +++++++---- .../example.tfvars | 11 +++++++++-- examples/gwlb_with_vmseries/example.tfvars | 6 +++++- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index a69c6b80..81f09d74 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -6,7 +6,7 @@ tags = { "CreatedBy" = "Palo Alto Networks" "CreatedWith" = "Terraform" } -enable_zones = true + # --- VNET PART --- # vnets = { @@ -132,12 +132,14 @@ load_balancers = { nsg_vnet_key = "transit" nsg_key = "public" network_security_allow_source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer - avzones = ["1", "2", "3"] frontend_ips = { - "palo-lb-app1" = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" create_public_ip = true in_rules = { "balanceHttp" = { + name = "HTTP" protocol = "Tcp" port = 80 } @@ -146,16 +148,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] - + name = "private-lb" frontend_ips = { "ha-ports" = { + name = "private-vmseries" vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 2ac20c25..aef3a15e 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -118,12 +118,14 @@ load_balancers = { nsg_vnet_key = "transit" nsg_key = "public" network_security_allow_source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer - avzones = ["1", "2", "3"] frontend_ips = { - "palo-lb-app1" = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" create_public_ip = true in_rules = { "balanceHttp" = { + name = "HTTP" protocol = "Tcp" port = 80 } @@ -132,15 +134,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] + name = "private-lb" frontend_ips = { "ha-ports" = { + name = "private-vmseries" vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 5579d8a8..64eb0878 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -144,11 +144,15 @@ load_balancers = { nsg_vnet_key = "transit" nsg_key = "public" network_security_allow_source_ips = ["0.0.0.0/0"] # Put your own public IP address here <-- TODO to be adjusted by the customer + zones = null frontend_ips = { - "palo-lb-app1" = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" create_public_ip = true in_rules = { "balanceHttp" = { + name = "HTTP" protocol = "Tcp" port = 80 } @@ -157,14 +161,17 @@ load_balancers = { } } "private" = { - name = "private-lb" + name = "private-lb" + zones = null frontend_ips = { "ha-ports" = { + name = "private-vmseries" vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 45278cce..4f1c507f 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -210,18 +210,21 @@ vmseries_common = { load_balancers = { app1 = { name = "app1-web" - frontend_ips = { app1 = { + name = "app1" create_public_ip = true + public_ip_name = "lb-app1-pip" gwlb_key = "gwlb" in_rules = { http = { + name = "HTTP" floating_ip = false port = 80 protocol = "Tcp" } https = { + name = "HTTPs" floating_ip = false port = 443 protocol = "Tcp" @@ -229,6 +232,7 @@ load_balancers = { } out_rules = { outbound = { + name = "tcp-outbound" protocol = "Tcp" } } From 1f5adbc347ec322f2ed089400d4150bec687e17a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Wed, 25 Oct 2023 13:23:21 +0200 Subject: [PATCH 09/11] re-add main_test.go for the lb module --- modules/loadbalancer/.header.md | 44 +++++++++++++++++++++++++++++++ modules/loadbalancer/README.md | 43 ++++++++++++++++++++++++++++++ modules/loadbalancer/main_test.go | 11 ++++++++ 3 files changed, 98 insertions(+) create mode 100644 modules/loadbalancer/main_test.go diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md index eac7e1a6..152cd7e4 100644 --- a/modules/loadbalancer/.header.md +++ b/modules/loadbalancer/.header.md @@ -1,3 +1,47 @@ +# Admonition options + +## full badge + +> should work with tf registry + +![Static Badge](https://img.shields.io/badge/NOTE-The%20module%20can%20create%20a%20public%20or%20a%20private%20Load%20Balancer%3B%20due%20to%20that%20some%20properties%20are%20mutually%20exclusive.-blue?style=flat&labelColor=gray) + +## half badge + +> the text is not centered, so it looks like an error in rendering. Should work with TFR + +![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat) The module can create a public or a private Load Balancer.\ +Due to that some properties are mutually exclusive. + +## horizontal table + +> should also work with TFR, but we cannot drop the header, so it renders empty + +| | | +--- | --- +![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat) | The module can create a public or a private Load Balancer.
Due to that some properties are mutually exclusive. + +## vertical table + +> this should also work with TFR, but also looks kinda strange + +|![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat)| +|:--- | +The module can create a public or a private Load Balancer.
Due to that some properties are mutually exclusive. + +## GH admonition + +> most probably will not render on TFR + +> [!NOTE] +> The module can create a public or a private Load Balancer +> Due to that some properties are mutually exclusive. + + +--- +--- +--- + # Load Balancer Module for Azure A Terraform module for deploying a Load Balancer for VM-Series firewalls. diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 5376232c..9daa406f 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -1,4 +1,47 @@ +# Admonition options + +## full badge + +> should work with tf registry + +![Static Badge](https://img.shields.io/badge/NOTE-The%20module%20can%20create%20a%20public%20or%20a%20private%20Load%20Balancer%3B%20due%20to%20that%20some%20properties%20are%20mutually%20exclusive.-blue?style=flat&labelColor=gray) + +## half badge + +> the text is not centered, so it looks like an error in rendering. Should work with TFR + +![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat) The module can create a public or a private Load Balancer.\ +Due to that some properties are mutually exclusive. + +## horizontal table + +> should also work with TFR, but we cannot drop the header, so it renders empty + +| | | +--- | --- +![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat) | The module can create a public or a private Load Balancer.
Due to that some properties are mutually exclusive. + +## vertical table + +> this should also work with TFR, but also looks kinda strange + +|![Static Badge](https://img.shields.io/badge/NOTE-blue?style=flat)| +|:--- | +The module can create a public or a private Load Balancer.
Due to that some properties are mutually exclusive. + +## GH admonition + +> most probably will not render on TFR + +> [!NOTE] +> The module can create a public or a private Load Balancer +> Due to that some properties are mutually exclusive. + +--- +--- +--- + # Load Balancer Module for Azure A Terraform module for deploying a Load Balancer for VM-Series firewalls. diff --git a/modules/loadbalancer/main_test.go b/modules/loadbalancer/main_test.go new file mode 100644 index 00000000..ec23b950 --- /dev/null +++ b/modules/loadbalancer/main_test.go @@ -0,0 +1,11 @@ +package loadbalancer + +import ( + "testing" + + "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" +) + +func TestValidate(t *testing.T) { + testskeleton.ValidateCode(t, nil) +} From 37c31a27063bd57be9908ab4e9352798bea98a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Thu, 26 Oct 2023 15:03:17 +0200 Subject: [PATCH 10/11] fix redundand regex --- modules/loadbalancer/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 59458547..20268585 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -429,7 +429,7 @@ variable "nsg_auto_rules_settings" { validation { # source_ips condition = var.nsg_auto_rules_settings != null ? alltrue([ for ip in var.nsg_auto_rules_settings.source_ips : - can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/0|\\/[12]?[0-9]|\\/3[0-2])?$", ip)) + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", ip)) ]) : true error_message = "The `source_ips` property can an IPv4 address or address space in CIDR notation." } From f8cc1ca464757aa5338e6a04b8e89129f7ef5e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= Date: Thu, 26 Oct 2023 15:55:22 +0200 Subject: [PATCH 11/11] separate fip and rules --- examples/lb/README.md | 36 ++-- examples/lb/example.tfvars | 105 +++++------ examples/lb/main.tf | 2 + examples/lb/variables.tf | 36 ++-- modules/loadbalancer/README.md | 196 ++++++-------------- modules/loadbalancer/main.tf | 108 ++++++----- modules/loadbalancer/variables.tf | 296 ++++-------------------------- 7 files changed, 237 insertions(+), 542 deletions(-) diff --git a/examples/lb/README.md b/examples/lb/README.md index 5ad6de64..c0f7bc2d 100644 --- a/examples/lb/README.md +++ b/examples/lb/README.md @@ -260,23 +260,25 @@ map(object({ subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) - in_rules = optional(map(object({ - name = string - protocol = string - port = number - backend_port = optional(number) - health_probe_key = optional(string) - floating_ip = optional(bool) - session_persistence = optional(string) - nsg_priority = optional(number) - })), {}) - out_rules = optional(map(object({ - name = string - protocol = string - allocated_outbound_ports = optional(number) - enable_tcp_reset = optional(bool) - idle_timeout_in_minutes = optional(number) - })), {}) + })), {}) + inbound_rules = optional(map(object({ + name = string + frontend_ip_key = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + outbound_rules = optional(map(object({ + name = string + frontend_ip_key = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })) ``` diff --git a/examples/lb/example.tfvars b/examples/lb/example.tfvars index 555a2bde..a77263b0 100644 --- a/examples/lb/example.tfvars +++ b/examples/lb/example.tfvars @@ -34,11 +34,9 @@ load_balancers = { "public" = { name = "public-lb" nsg_auto_rules_settings = { - # nsg_name = "fosix-existing-nsg" - # nsg_resource_group_name = "fosix-lb-ips" nsg_vnet_key = "transit" nsg_key = "public" - source_ips = ["10.0.0.0/8"] # Put your own public IP address here <-- TODO to be adjusted by the customer + source_ips = ["0.0.0.0/0"] base_priority = 4000 } zones = ["1", "2", "3"] @@ -49,57 +47,59 @@ load_balancers = { } } frontend_ips = { - "default_front" = { + default_front = { name = "default-public-frontend" public_ip_name = "frontend-pip" create_public_ip = true - in_rules = { - "balanceHttp" = { - name = "HTTP" - protocol = "Tcp" - port = 80 - health_probe_key = "http_default" - } - } - out_rules = { - default = { - name = "default-out" - protocol = "Tcp" - allocated_outbound_ports = 20000 - enable_tcp_reset = true - idle_timeout_in_minutes = 120 - } - } } - "sourced_pip" = { + sourced_pip = { name = "with-sourced-pip" public_ip_name = "fosix-sourced_frontend" public_ip_resource_group = "fosix-lb-ips" - zones = null - in_rules = { - "balanceHttp" = { - name = "HTTP-elevated" - protocol = "Tcp" - port = 80 - # health_probe_key = "http_default" - } - } } - # "private" = { - # name = "private" - # vnet_key = "transit" - # subnet_key = "private" - # private_ip_address = "10.0.0.22" - # in_rules = { - # "balanceHttp" = { - # name = "HA" - # protocol = "Tcp" - # port = 80 - # health_probe_key = "http_default" - # } - # } - # } } + inbound_rules = { + default_balanceHttp = { + name = "HTTP" + frontend_ip_key = "default_front" + protocol = "Tcp" + port = 80 + health_probe_key = "http_default" + } + sourced_balanceHttp = { + name = "HTTP_sourced" + frontend_ip_key = "sourced_pip" + protocol = "Tcp" + port = 80 + + } + } + outbound_rules = { + default = { + name = "default-out" + frontend_ip_key = "default_front" + protocol = "Tcp" + allocated_outbound_ports = 20000 + enable_tcp_reset = true + idle_timeout_in_minutes = 120 + } + } + + # "private" = { + # name = "private" + # vnet_key = "transit" + # subnet_key = "private" + # private_ip_address = "10.0.0.22" + # in_rules = { + # "balanceHttp" = { + # name = "HA" + # protocol = "Tcp" + # port = 80 + # health_probe_key = "http_default" + # } + # } + # } + } "private" = { name = "private-lb" @@ -110,13 +110,14 @@ load_balancers = { vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.21" - in_rules = { - HA_PORTS = { - name = "HA" - port = 0 - protocol = "All" - } - } + } + } + inbound_rules = { + HA_PORTS = { + name = "HA" + frontend_ip_key = "ha-ports" + port = 0 + protocol = "All" } } } diff --git a/examples/lb/main.tf b/examples/lb/main.tf index f4912b2a..4710db99 100644 --- a/examples/lb/main.tf +++ b/examples/lb/main.tf @@ -80,6 +80,8 @@ module "load_balancer" { } ) } + inbound_rules = each.value.inbound_rules + outbound_rules = each.value.outbound_rules tags = var.tags depends_on = [module.vnet] diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf index 8a422944..b83a57fc 100644 --- a/examples/lb/variables.tf +++ b/examples/lb/variables.tf @@ -170,23 +170,25 @@ variable "load_balancers" { subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) - in_rules = optional(map(object({ - name = string - protocol = string - port = number - backend_port = optional(number) - health_probe_key = optional(string) - floating_ip = optional(bool) - session_persistence = optional(string) - nsg_priority = optional(number) - })), {}) - out_rules = optional(map(object({ - name = string - protocol = string - allocated_outbound_ports = optional(number) - enable_tcp_reset = optional(bool) - idle_timeout_in_minutes = optional(number) - })), {}) + })), {}) + inbound_rules = optional(map(object({ + name = string + frontend_ip_key = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + outbound_rules = optional(map(object({ + name = string + frontend_ip_key = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) })), {}) })) } \ No newline at end of file diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 9daa406f..4aaf6ba4 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -133,7 +133,7 @@ Name | Type | Description [`name`](#name) | `string` | The name of the Load Balancer. [`resource_group_name`](#resource_group_name) | `string` | Name of a pre-existing Resource Group to place the resources in. [`location`](#location) | `string` | Region to deploy the resources in. -[`frontend_ips`](#frontend_ips) | `map` | A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. +[`frontend_ips`](#frontend_ips) | `map` | Frontend IP configuration. ## Module's Optional Inputs @@ -142,6 +142,8 @@ Name | Type | Description --- | --- | --- [`zones`](#zones) | `list` | Controls zones for Load Balancer's Fronted IP configurations. [`tags`](#tags) | `map` | Azure tags to apply to the created resources. +[`inbound_rules`](#inbound_rules) | `map` | Inbound rules. +[`outbound_rules`](#outbound_rules) | `map` | Outbound rules. [`backend_name`](#backend_name) | `string` | The name of the backend pool to create. [`health_probes`](#health_probes) | `map` | Backend's health probe definition. [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) | `object` | Controls automatic creation of NSG rules for all defined inbound rules. @@ -216,129 +218,7 @@ Type: string #### frontend_ips -A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. - -Each Frontend IP configuration can have multiple rules assigned. -They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. - -Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. -To ease configuration they were grouped per Load Balancer type. - -Private Load Balancer: - -- `name` - (`string`, required) name of a frontend IP configuration -- `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer -- `private_ip_address` - (`string`, required) the IP address of the Load Balancer -- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below -- `gwlb_fip_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration - of a Gateway Load Balancer - -Public Load Balancer: - -- `name` - (`string`, required) name of a frontend IP configuration -- `public_ip_name` - (`string`, required) name of a public IP resource -- `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be - created, otherwise an existing resource will be used; - in both cases the name of the resource is controled by `public_ip_name` property -- `public_ip_resource_group` - (`string`, optional, defaults to the Load Balancer's RG) name of a Resource Group - hosting an existing public IP resource -- `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below -- `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below - -Below are the properties for the `in_rules` map: - -- `name` - (`string`, required) a name of an inbound rule -- `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. -- `port` - (`number`, required) communication port, this is both the front- and the backend port - if `backend_port` is not set; value of `0` means all ports -- `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic - to in the backend pool -- `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining - a health probe to use with this rule -- `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. -- `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, - three values are possible: - - `Default` - this is the 5 tuple hash - - `SourceIP` - a 2 tuple hash is used - - `SourceIPProtocol` - a 3 tuple hash is used -- `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, - when skipped the rule priority will be auto-calculated, - for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) - -Below are the properties for `out_rules` map. - -> [!Warning] -> Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. -> Keep in mind that since we use a single backend, -> and you cannot mix SNAT and outbound rules traffic in rules using the same backend, -> setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`. - -- `name` - (`string`, required) a name of an outbound rule -- `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted -- `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, - when skipped provider defaults will be used (`1024`), - when set to `0` port allocation will be set to default number (Azure defaults); - maximum value is `64000` -- `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` -- `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes - (between 4 and 120) - in case the connection is idle, ignored when `protocol` is set to `Udp` - -Examples - -```hcl -# rules for a public Load Balancer, reusing an existing public IP and doing port translation -frontend_ips = { - pip_existing = { - create_public_ip = false - public_ip_name = "my_ip" - public_ip_resource_group = "my_rg_name" - in_rules = { - HTTP = { - port = 80 - protocol = "Tcp" - backend_port = 8080 - } - } - } -} - -# rules for a private Load Balancer, one HA PORTs rule -frontend_ips = { - internal = { - subnet_id = azurerm_subnet.this.id - private_ip_address = "192.168.0.10" - in_rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } -} - -# rules for a public Load Balancer, session persistance with 2 tuple hash, outbound rule defined -frontend_ips = { - rule_1 = { - create_public_ip = true - in_rules = { - HTTP = { - port = 80 - protocol = "Tcp" - session_persistence = "SourceIP" - } - } - } - out_rules = { - "outbound_tcp" = { - protocol = "Tcp" - allocated_outbound_ports = 2048 - enable_tcp_reset = true - idle_timeout_in_minutes = 10 - } - } -} -``` +Frontend IP configuration. Type: @@ -352,23 +232,6 @@ map(object({ subnet_id = optional(string) private_ip_address = optional(string) gwlb_fip_id = optional(string) - in_rules = optional(map(object({ - name = string - protocol = string - port = number - backend_port = optional(number) - health_probe_key = optional(string, "default") - floating_ip = optional(bool, true) - session_persistence = optional(string, "Default") - nsg_priority = optional(number) - })), {}) - out_rules = optional(map(object({ - name = string - protocol = string - allocated_outbound_ports = optional(number) - enable_tcp_reset = optional(bool) - idle_timeout_in_minutes = optional(number) - })), {}) })) ``` @@ -380,6 +243,8 @@ map(object({ + + ### Optional Inputs @@ -420,6 +285,55 @@ Default value: `map[]` [back to list](#modules-optional-inputs) +#### inbound_rules + +Inbound rules. + + +Type: + +```hcl +map(object({ + name = string + frontend_ip_key = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string, "default") + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### outbound_rules + +Outbound rules. + + +Type: + +```hcl +map(object({ + name = string + frontend_ip_key = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + #### backend_name The name of the backend pool to create. All frontends of the Load Balancer always use the same backend. diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index 044617ee..ce4cb92a 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -1,33 +1,33 @@ locals { # Decide how the backend machines access internet. If outbound rules are defined use them instead of the default route. # This is an inbound rule setting, applicable to all inbound rules as you cannot mix SNAT with Outbound rules for a single backend. - disable_outbound_snat = anytrue([for _, v in var.frontend_ips : length(v.out_rules) != 0]) - - # Calculate inbound rules - in_flat_rules = flatten([ - for fipkey, fip in var.frontend_ips : [ - for rulekey, rule in fip.in_rules : { - fipkey = fipkey - fip = fip - rulekey = rulekey - rule = rule - } - ] - ]) - in_rules = { for v in local.in_flat_rules : "${v.fipkey}-${v.rulekey}" => v } - - # Calculate outbound rules - out_flat_rules = flatten([ - for fipkey, fip in var.frontend_ips : [ - for rulekey, rule in fip.out_rules : { - fipkey = fipkey - fip = fip - rulekey = rulekey - rule = rule - } - ] - ]) - out_rules = { for v in local.out_flat_rules : "${v.fipkey}-${v.rulekey}" => v } + disable_outbound_snat = length(var.outbound_rules) > 0 + + # # Calculate inbound rules + # in_flat_rules = flatten([ + # for fipkey, fip in var.frontend_ips : [ + # for rulekey, rule in fip.in_rules : { + # fipkey = fipkey + # fip = fip + # rulekey = rulekey + # rule = rule + # } + # ] + # ]) + # in_rules = { for v in local.in_flat_rules : "${v.fipkey}-${v.rulekey}" => v } + + # # Calculate outbound rules + # out_flat_rules = flatten([ + # for fipkey, fip in var.frontend_ips : [ + # for rulekey, rule in fip.out_rules : { + # fipkey = fipkey + # fip = fip + # rulekey = rulekey + # rule = rule + # } + # ] + # ]) + # out_rules = { for v in local.out_flat_rules : "${v.fipkey}-${v.rulekey}" => v } } resource "azurerm_public_ip" "this" { @@ -97,9 +97,7 @@ locals { } default_probe = ( var.health_probes == null || anytrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, rule in fip.in_rules : rule.health_probe_key == "default" - ] + for _, rule in var.inbound_rules : rule.health_probe_key == "default" ])) ) ? { default = { @@ -131,36 +129,36 @@ resource "azurerm_lb_probe" "this" { } resource "azurerm_lb_rule" "this" { - for_each = local.in_rules + for_each = var.inbound_rules - name = each.value.rule.name + name = each.value.name loadbalancer_id = azurerm_lb.this.id - probe_id = azurerm_lb_probe.this[each.value.rule.health_probe_key].id + probe_id = azurerm_lb_probe.this[each.value.health_probe_key].id backend_address_pool_ids = [azurerm_lb_backend_address_pool.this.id] - protocol = each.value.rule.protocol - backend_port = coalesce(each.value.rule.backend_port, each.value.rule.port) - frontend_ip_configuration_name = each.value.fip.name - frontend_port = each.value.rule.port - enable_floating_ip = each.value.rule.floating_ip + protocol = each.value.protocol + backend_port = coalesce(each.value.backend_port, each.value.port) + frontend_ip_configuration_name = var.frontend_ips[each.value.frontend_ip_key].name + frontend_port = each.value.port + enable_floating_ip = each.value.floating_ip disable_outbound_snat = local.disable_outbound_snat - load_distribution = each.value.rule.session_persistence + load_distribution = each.value.session_persistence } resource "azurerm_lb_outbound_rule" "this" { - for_each = local.out_rules + for_each = var.outbound_rules - name = each.value.rule.name + name = each.value.name loadbalancer_id = azurerm_lb.this.id backend_address_pool_id = azurerm_lb_backend_address_pool.this.id - protocol = each.value.rule.protocol - enable_tcp_reset = each.value.rule.protocol != "Udp" ? each.value.rule.enable_tcp_reset : null - allocated_outbound_ports = each.value.rule.allocated_outbound_ports - idle_timeout_in_minutes = each.value.rule.protocol != "Udp" ? each.value.rule.idle_timeout_in_minutes : null + protocol = each.value.protocol + enable_tcp_reset = each.value.protocol != "Udp" ? each.value.enable_tcp_reset : null + allocated_outbound_ports = each.value.allocated_outbound_ports + idle_timeout_in_minutes = each.value.protocol != "Udp" ? each.value.idle_timeout_in_minutes : null frontend_ip_configuration { - name = each.value.fip.name + name = var.frontend_ips[each.value.frontend_ip_key].name } depends_on = [azurerm_lb_rule.this] } @@ -173,33 +171,33 @@ locals { # A map of hashes calculated for each inbound rule. Used to calculate NSG inbound rules priority index if modules is also used to automatically manage NSG rules. rules_hash = { - for k, v in local.in_rules : - k => substr(sha256("${local.frontend_addresses[v.fipkey]}:${v.rule.port}"), 0, 4) + for k, v in var.inbound_rules : + k => substr(sha256("${local.frontend_addresses[v.frontend_ip_key]}:${v.port}"), 0, 4) if var.nsg_auto_rules_settings != null } } # Optional NSG rules. Each corresponds to one azurerm_lb_rule. resource "azurerm_network_security_rule" "this" { - for_each = { for k, v in local.in_rules : k => v if var.nsg_auto_rules_settings != null } + for_each = var.nsg_auto_rules_settings != null ? var.inbound_rules : {} name = "allow-inbound-ips-${each.key}" network_security_group_name = var.nsg_auto_rules_settings.nsg_name resource_group_name = coalesce(var.nsg_auto_rules_settings.nsg_resource_group_name, var.resource_group_name) - description = "Auto-generated for load balancer ${var.name} port ${each.value.rule.protocol}/${coalesce(each.value.rule.backend_port, each.value.rule.port)}: allowed IPs: ${join(",", var.nsg_auto_rules_settings.source_ips)}" + description = "Auto-generated for load balancer ${var.name} port ${each.value.protocol}/${coalesce(each.value.backend_port, each.value.port)}: allowed IPs: ${join(",", var.nsg_auto_rules_settings.source_ips)}" direction = "Inbound" access = "Allow" - protocol = title(replace(lower(each.value.rule.protocol), "all", "*")) + protocol = title(replace(lower(each.value.protocol), "all", "*")) source_port_range = "*" - destination_port_ranges = [each.value.rule.port == "0" ? "*" : coalesce(each.value.rule.backend_port, each.value.rule.port)] + destination_port_ranges = [each.value.port == "0" ? "*" : coalesce(each.value.backend_port, each.value.port)] source_address_prefixes = var.nsg_auto_rules_settings.source_ips - destination_address_prefix = local.frontend_addresses[each.value.fipkey] + destination_address_prefix = local.frontend_addresses[each.value.frontend_ip_key] # For the priority, we add this %10 so that the numbering would be a bit more sparse instead of sequential. # This helps tremendously when a mass of indexes shifts by +1 or -1 and prevents problems when we need to shift rules reusing already used priority index. priority = coalesce( - each.value.rule.nsg_priority, - index(keys(local.in_rules), each.key) * 10 + parseint(local.rules_hash[each.key], 16) % 10 + var.nsg_auto_rules_settings.base_priority + each.value.nsg_priority, + index(keys(var.inbound_rules), each.key) * 10 + parseint(local.rules_hash[each.key], 16) % 10 + var.nsg_auto_rules_settings.base_priority ) } diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 20268585..3f752e2a 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -46,129 +46,7 @@ variable "tags" { variable "frontend_ips" { description = <<-EOF - A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. - - Each Frontend IP configuration can have multiple rules assigned. - They are defined in a maps called `in_rules` and `out_rules` for inbound and outbound rules respectively. - - Since this module can be used to create either a private or a public Load Balancer some properties can be mutually exclusive. - To ease configuration they were grouped per Load Balancer type. - - Private Load Balancer: - - - `name` - (`string`, required) name of a frontend IP configuration - - `subnet_id` - (`string`, required) an ID of an existing subnet that will host the private Load Balancer - - `private_ip_address` - (`string`, required) the IP address of the Load Balancer - - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - - `gwlb_fip_id` - (`string`, optional, defaults to `null`) an ID of a frontend IP configuration - of a Gateway Load Balancer - - Public Load Balancer: - - - `name` - (`string`, required) name of a frontend IP configuration - - `public_ip_name` - (`string`, required) name of a public IP resource - - `create_public_ip` - (`bool`, optional, defaults to `false`) when set to `true` a new public IP will be - created, otherwise an existing resource will be used; - in both cases the name of the resource is controled by `public_ip_name` property - - `public_ip_resource_group` - (`string`, optional, defaults to the Load Balancer's RG) name of a Resource Group - hosting an existing public IP resource - - `in_rules` - (`map`, optional, defaults to `{}`) a map defining inbound rules, see details below - - `out_rules` - (`map`, optional, defaults to `{}`) a map defining outbound rules, see details below - - Below are the properties for the `in_rules` map: - - - `name` - (`string`, required) a name of an inbound rule - - `protocol` - (`string`, required) communication protocol, either 'Tcp', 'Udp' or 'All'. - - `port` - (`number`, required) communication port, this is both the front- and the backend port - if `backend_port` is not set; value of `0` means all ports - - `backend_port` - (`number`, optional, defaults to `null`) this is the backend port to forward traffic - to in the backend pool - - `health_probe_key` - (`string`, optional, defaults to `default`) a key from the `var.health_probes` map defining - a health probe to use with this rule - - `floating_ip` - (`bool`, optional, defaults to `true`) enables floating IP for this rule. - - `session_persistence` - (`string`, optional, defaults to `Default`) controls session persistance/load distribution, - three values are possible: - - `Default` - this is the 5 tuple hash - - `SourceIP` - a 2 tuple hash is used - - `SourceIPProtocol` - a 3 tuple hash is used - - `nsg_priority` - (number, optional, defaults to `null`) this becomes a priority of an auto-generated NSG rule, - when skipped the rule priority will be auto-calculated, - for more details on auto-generated NSG rules see [`nsg_auto_rules_settings`](#nsg_auto_rules_settings) - - Below are the properties for `out_rules` map. - - > [!Warning] - > Setting at least one `out_rule` switches the outgoing traffic from SNAT to outbound rules. - > Keep in mind that since we use a single backend, - > and you cannot mix SNAT and outbound rules traffic in rules using the same backend, - > setting one `out_rule` switches the outgoing traffic route for **ALL** `in_rules`. - - - `name` - (`string`, required) a name of an outbound rule - - `protocol` - (`string`, required) protocol used by the rule. One of `All`, `Tcp` or `Udp` is accepted - - `allocated_outbound_ports` - (`number`, optional, defaults to `null`) number of ports allocated per instance, - when skipped provider defaults will be used (`1024`), - when set to `0` port allocation will be set to default number (Azure defaults); - maximum value is `64000` - - `enable_tcp_reset` - (`bool`, optional, defaults to Azure defaults) ignored when `protocol` is set to `Udp` - - `idle_timeout_in_minutes` - (`number`, optional, defaults to Azure defaults) TCP connection timeout in minutes - (between 4 and 120) - in case the connection is idle, ignored when `protocol` is set to `Udp` - - Examples - - ```hcl - # rules for a public Load Balancer, reusing an existing public IP and doing port translation - frontend_ips = { - pip_existing = { - create_public_ip = false - public_ip_name = "my_ip" - public_ip_resource_group = "my_rg_name" - in_rules = { - HTTP = { - port = 80 - protocol = "Tcp" - backend_port = 8080 - } - } - } - } - - # rules for a private Load Balancer, one HA PORTs rule - frontend_ips = { - internal = { - subnet_id = azurerm_subnet.this.id - private_ip_address = "192.168.0.10" - in_rules = { - HA_PORTS = { - port = 0 - protocol = "All" - } - } - } - } - - # rules for a public Load Balancer, session persistance with 2 tuple hash, outbound rule defined - frontend_ips = { - rule_1 = { - create_public_ip = true - in_rules = { - HTTP = { - port = 80 - protocol = "Tcp" - session_persistence = "SourceIP" - } - } - } - out_rules = { - "outbound_tcp" = { - protocol = "Tcp" - allocated_outbound_ports = 2048 - enable_tcp_reset = true - idle_timeout_in_minutes = 10 - } - } - } - ``` + Frontend IP configuration. EOF type = map(object({ name = string @@ -178,144 +56,42 @@ variable "frontend_ips" { subnet_id = optional(string) private_ip_address = optional(string) gwlb_fip_id = optional(string) - in_rules = optional(map(object({ - name = string - protocol = string - port = number - backend_port = optional(number) - health_probe_key = optional(string, "default") - floating_ip = optional(bool, true) - session_persistence = optional(string, "Default") - nsg_priority = optional(number) - })), {}) - out_rules = optional(map(object({ - name = string - protocol = string - allocated_outbound_ports = optional(number) - enable_tcp_reset = optional(bool) - idle_timeout_in_minutes = optional(number) - })), {}) })) - validation { - condition = !( # unified LB type - anytrue( - [for _, fip in var.frontend_ips : fip.public_ip_name != null] - ) && anytrue( - [for _, fip in var.frontend_ips : fip.subnet_id != null] - ) - ) - error_message = "All frontends have to be of the same type, either public or private. Please check module's documentation (Usage section) for details." - } - validation { # name - condition = length(flatten([for _, v in var.frontend_ips : v.name])) == length(distinct(flatten([for _, v in var.frontend_ips : v.name]))) - error_message = "The `name` property has to be unique among all frontend definitions." - } - validation { # private_ip_address - condition = alltrue([ - for _, fip in var.frontend_ips : fip.private_ip_address != null if fip.subnet_id != null - ]) - error_message = "The `private_ip_address` id required for private Load Balancers." - } - validation { # private_ip_address - condition = alltrue([ - for _, fip in var.frontend_ips : - can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", fip.private_ip_address)) - if fip.private_ip_address != null - ]) - error_message = "The `private_ip_address` property should be in IPv4 format." - } - validation { # in_rule.name - condition = length(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : in_rule.name - ]])) == length(distinct(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : in_rule.name - ]]))) - error_message = "The `in_rule.name` property has to be unique among all in rules definitions." - } - validation { # in_rule.protocol - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : contains(["Tcp", "Udp", "All"], in_rule.protocol) - ] - ])) - error_message = "The `in_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\"." - } - validation { # in_rule.port - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : (in_rule.port >= 0 && in_rule.port <= 65535) - ] - ])) - error_message = "The `in_rule.port` should be a valid TCP port number or `0` for all ports." - } - validation { # in_rule.backend_port - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : - (in_rule.backend_port > 0 && in_rule.backend_port <= 65535) - if in_rule.backend_port != null - ] - ])) - error_message = "The `in_rule.backend_port` should be a valid TCP port number." - } - validation { # in_rule.sessions_persistence - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : contains(["Default", "SourceIP", "SourceIPProtocol"], in_rule.session_persistence) - ] - ])) - error_message = "The `in_rule.session_persistence` property should be one of: \"Default\", \"SourceIP\", \"SourceIPProtocol\"." - } - validation { # in_rule.nsg_priority - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, in_rule in fip.in_rules : - in_rule.nsg_priority >= 100 && in_rule.nsg_priority <= 4000 - if in_rule.nsg_priority != null - ] - ])) - error_message = "The `in_rule.nsg_priority` property be a number between 100 and 4096." - } - validation { # out_rule.name - condition = length(flatten([ - for _, fip in var.frontend_ips : [ - for _, out_rule in fip.out_rules : out_rule.name - ]])) == length(distinct(flatten([ - for _, fip in var.frontend_ips : [ - for _, out_rule in fip.out_rules : out_rule.name - ]]))) - error_message = "The `out_rule.name` property has to be unique among all in rules definitions." - } - validation { # out_rule.protocol - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, out_rule in fip.out_rules : contains(["Tcp", "Udp", "All"], out_rule.protocol) - ] - ])) - error_message = "The `out_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\"." - } - validation { # out_rule.allocated_outbound_ports - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, out_rule in fip.out_rules : - out_rule.allocated_outbound_ports >= 0 && out_rule.allocated_outbound_ports <= 64000 - if out_rule.allocated_outbound_ports != null - ] - ])) - error_message = "The `out_rule.allocated_outbound_ports` property should can be either `0` or a valid TCP port number with the maximum value of 64000." - } - validation { # out_rule.idle_timeout_in_minutes - condition = alltrue(flatten([ - for _, fip in var.frontend_ips : [ - for _, out_rule in fip.out_rules : - out_rule.idle_timeout_in_minutes >= 4 && out_rule.idle_timeout_in_minutes <= 120 - if out_rule.idle_timeout_in_minutes != null - ] - ])) - error_message = "The `out_rule.idle_timeout_in_minutes` property should can take values between 4 and 120 (minutes)." - } +} + +variable "inbound_rules" { + description = <<-EOF + Inbound rules. + EOF + default = {} + nullable = false + type = map(object({ + name = string + frontend_ip_key = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string, "default") + floating_ip = optional(bool, true) + session_persistence = optional(string, "Default") + nsg_priority = optional(number) + })) +} + +variable "outbound_rules" { + description = <<-EOF + Outbound rules. + EOF + default = {} + nullable = false + type = map(object({ + name = string + frontend_ip_key = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })) } variable "backend_name" {