From 574e56ff26b62e766787b97638ef5ab2bac10769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <lpawlega@paloaltonetworks.com> Date: Tue, 26 Sep 2023 16:41:03 +0200 Subject: [PATCH 01/49] name property --- .pre-commit-config.yaml | 4 +- .terraform-docs.yml | 111 +++++++++++++++++++++++++++++++++ examples/vnet/example.tfvars | 115 +++++++++++++++++++++++++++++++++++ examples/vnet/main.tf | 45 ++++++++++++++ examples/vnet/main_test.go | 62 +++++++++++++++++++ examples/vnet/outputs.tf | 39 ++++++++++++ examples/vnet/todo.md | 9 +++ examples/vnet/variables.tf | 62 +++++++++++++++++++ examples/vnet/versions.tf | 22 +++++++ modules/vnet/.header.md | 7 +++ modules/vnet/README.md | 12 +--- modules/vnet/main.tf | 32 +++++----- modules/vnet/variables.tf | 71 +++++++++++++-------- modules/vnet/versions.tf | 2 +- 14 files changed, 537 insertions(+), 56 deletions(-) create mode 100644 .terraform-docs.yml create mode 100644 examples/vnet/example.tfvars create mode 100644 examples/vnet/main.tf create mode 100644 examples/vnet/main_test.go create mode 100644 examples/vnet/outputs.tf create mode 100644 examples/vnet/todo.md create mode 100644 examples/vnet/variables.tf create mode 100644 examples/vnet/versions.tf create mode 100644 modules/vnet/.header.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c88ed742..ca409184 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,9 +2,7 @@ repos: - hooks: - id: terraform_fmt - args: - - --args=--sort=false - - --args=--lockfile=false - - --args=--indent=3 + - --args=--config=.terraform-docs.yml id: terraform_docs - args: - --args=--only=terraform_deprecated_interpolation diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 00000000..1aa148a3 --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,111 @@ +formatter: "markdown document" # this is required +version: "" +header-from: ".header.md" + +output: + file: README.md + mode: replace + +sort: + enabled: false + +settings: + indent: 3 + lockfile: false + +content: |- + {{ .Header }} + + ## Module's Required Inputs + + Name | Type | Description + --- | --- | --- + {{- range .Module.Inputs }} + {{- if .Required }} + [`{{ .Name }}`](#{{ .Name }}) | `{{ .Type }}` | {{ (split "." .Description.Raw)._0 }}. + {{- end -}} + {{ end }} + + ## Module's Optional Inputs + + Name | Type | Description + --- | --- | --- + {{- range .Module.Inputs }} + {{- if not .Required }} + [`{{ .Name }}`](#{{ .Name }}) | `{{ .Type }}` | {{ (split "." .Description.Raw)._0 }}. + {{- end -}} + {{ end }} + + ## Module's Outputs + + Name | Description + --- | --- + {{- range .Module.Outputs }} + [`{{ .Name }}`](#{{ .Name }}) | {{ (split "." .Description.Raw)._0 }} + {{- end }} + + ## Module's Nameplate + + Requirements needed by this module: + {{ range .Module.Requirements }} + - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} + {{- end }} + + Providers used in this module: + {{ range .Module.Providers }} + - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} + {{- end }} + + Modules used in this module: + Name | Version | Source | Description + --- | --- | --- | --- + {{- range .Module.ModuleCalls }} + `{{ .Name }}` | {{ if .Version }}{{ .Version }}{{ else }}-{{ end }} | {{ .Source }} | {{ .Description }} + {{- end }} + + Resources used in this module: + {{ range .Module.Resources }} + - `{{ .Type }}` ({{ .Mode }}) + {{- end }} + + ## Inputs/Outpus details + + ### Required Inputs + + {{ range .Module.Inputs }} + {{ if .Required -}} + #### {{ .Name }} + + {{ .Description }} + + Type: `{{ .Type }}` + + <sup>[back to list](#modules-required-inputs)</sup> + {{ end }} + {{- end }} + + ### Optional Inputs + + {{ range .Module.Inputs }} + {{ if not .Required -}} + #### {{ .Name }} + + {{ .Description }} + + Type: `{{ .Type }}` + + Default value: `{{ .Default }}` + + <sup>[back to list](#modules-optional-inputs)</sup> + {{ end }} + {{- end }} + + ### Outputs + + {{ range .Module.Outputs }} + #### `{{ .Name }}` + + {{ .Description }} + + <sup>[back to list](#modules-outputs)</sup> + {{- end }} \ No newline at end of file diff --git a/examples/vnet/example.tfvars b/examples/vnet/example.tfvars new file mode 100644 index 00000000..35fbe122 --- /dev/null +++ b/examples/vnet/example.tfvars @@ -0,0 +1,115 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "transit-vnet-common" +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 = { + "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.tf b/examples/vnet/main.tf new file mode 100644 index 00000000..686ecc08 --- /dev/null +++ b/examples/vnet/main.tf @@ -0,0 +1,45 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = try(each.value.create_virtual_network, true) + resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + + create_subnets = try(each.value.create_subnets, true) + subnets = try(each.value.create_subnets, true) ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets + + network_security_groups = { for k, v in try(each.value.network_security_groups, {}) : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in try(each.value.route_tables, {}) : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} diff --git a/examples/vnet/main_test.go b/examples/vnet/main_test.go new file mode 100644 index 00000000..ba72cbc9 --- /dev/null +++ b/examples/vnet/main_test.go @@ -0,0 +1,62 @@ +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/outputs.tf b/examples/vnet/outputs.tf new file mode 100644 index 00000000..892ab61b --- /dev/null +++ b/examples/vnet/outputs.tf @@ -0,0 +1,39 @@ +# output "username" { +# description = "Initial administrative username to use for VM-Series." +# value = var.vmseries_username +# } + +# output "password" { +# description = "Initial administrative password to use for VM-Series." +# value = local.vmseries_password +# sensitive = true +# } + +# output "natgw_public_ips" { +# description = "Nat Gateways Public IP resources." +# value = length(var.natgws) > 0 ? { for k, v in module.natgw : k => { +# pip = v.natgw_pip +# pip_prefix = v.natgw_pip_prefix +# } } : null +# } + +# output "metrics_instrumentation_keys" { +# description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." +# value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null +# sensitive = true +# } + +# output "lb_frontend_ips" { +# description = "IP Addresses of the load balancers." +# value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null +# } + +# output "vmseries_mgmt_ips" { +# description = "IP addresses for the VMSeries management interface." +# value = { for k, v in module.vmseries : k => v.mgmt_ip_address } +# } + +# output "bootstrap_storage_urls" { +# value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null +# sensitive = true +# } diff --git a/examples/vnet/todo.md b/examples/vnet/todo.md new file mode 100644 index 00000000..5c3e63e4 --- /dev/null +++ b/examples/vnet/todo.md @@ -0,0 +1,9 @@ +todo: + +- [x] add name property to all elements +- [x] add possibility to specify name elements in glue code +- [x] use name prefix for name in glue code, name templater in the future +- [x] pop up minimum terraform to 1.3 +- [ ] add optional + full maps specification +- [ ] add documentation in new format +- [ ] add new format of the README with header, etc \ No newline at end of file diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf new file mode 100644 index 00000000..867232bd --- /dev/null +++ b/examples/vnet/variables.tf @@ -0,0 +1,62 @@ +### 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: + ``` + 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) + + - `name` : A name of a VNET. + - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` + - `address_space` : a list of CIDRs for VNET + - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + + - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets + - `subnets` : map of Subnets to create + + - `network_security_groups` : map of Network Security Groups to create + - `route_tables` : map of Route Tables to create. + EOF +} diff --git a/examples/vnet/versions.tf b/examples/vnet/versions.tf new file mode 100644 index 00000000..95b07f02 --- /dev/null +++ b/examples/vnet/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/modules/vnet/.header.md b/modules/vnet/.header.md new file mode 100644 index 00000000..ca5a5749 --- /dev/null +++ b/modules/vnet/.header.md @@ -0,0 +1,7 @@ +# Palo Alto Networks VNet Module for Azure + +A terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. + +## Usage + +For usage refer to any example module. diff --git a/modules/vnet/README.md b/modules/vnet/README.md index d57b478c..e17f72bd 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -1,10 +1,3 @@ -# Palo Alto Networks VNet Module for Azure - -A terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. - -## Usage - -For usage refer to any example module. ## Reference <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> @@ -12,7 +5,7 @@ For usage refer to any example module. | Name | Version | |------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | +| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | | <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | ### Providers @@ -44,7 +37,6 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix added to all resource names created by this module: VNET, NSGs, RTs. Subnet, as a sub-resource is not prefixed. | `string` | `""` | no | | <a name="input_name"></a> [name](#input\_name) | The name of the Azure Virtual Network. | `string` | n/a | yes | | <a name="input_create_virtual_network"></a> [create\_virtual\_network](#input\_create\_virtual\_network) | If true, create the Virtual Network, otherwise just use a pre-existing network. | `bool` | `true` | no | | <a name="input_create_subnets"></a> [create\_subnets](#input\_create\_subnets) | If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. | `bool` | `true` | no | @@ -52,7 +44,7 @@ No modules. | <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to all of the created resources. | `map(any)` | `{}` | no | | <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to use. | `string` | n/a | yes | | <a name="input_address_space"></a> [address\_space](#input\_address\_space) | The address space used by the virtual network. You can supply more than one address space. | `list(string)` | n/a | yes | -| <a name="input_network_security_groups"></a> [network\_security\_groups](#input\_network\_security\_groups) | Map of Network Security Groups to create.<br>List of available attributes of each Network Security Group entry:<br>- `name` : Name of the Network Security Group.<br>- `location` : (Optional) Specifies the Azure location where to deploy the resource.<br>- `rules`: (Optional) A list of objects representing a Network Security Rule. The key of each entry acts as the name of the rule and<br> needs to be unique across all rules in the Network Security Group.<br> List of attributes available to define a Network Security Rule.<br> Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`:<br> - `priority` : Numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection.<br> The lower the priority number, the higher the priority of the rule.<br> - `direction` : The direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`.<br> - `access` : Specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`.<br> - `protocol` : Network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol)<br> - `source_port_range` : A source port or a range of ports. This can also be an `*` to match all.<br> - `source_port_ranges` : A list of source ports or ranges of ports. This can be specified only if `source_port_range` was not used.<br> - `destination_port_range` : A destination port or a range of ports. This can also be an `*` to match all.<br> - `destination_port_ranges` : A list of destination ports or a ranges of ports. This can be specified only if `destination_port_range` was not used.<br> - `source_address_prefix` : Source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`.<br> - `source_address_prefixes` : A list of source address prefixes. Tags are not allowed. Can be specified only if `source_address_prefix` was not used.<br> - `destination_address_prefix` : Destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details.<br> - `destination_address_prefixes` : A list of destination address prefixes. Tags are not allowed. Can be specified only if `destination_address_prefix` was not used.<br><br>Example:<pre>{<br> "nsg_1" = {<br> name = "network_security_group_1"<br> location = "Australia Central"<br> rules = {<br> "AllOutbound" = {<br> priority = 100<br> direction = "Outbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "*"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowSSH" = {<br> priority = 200<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "22"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowWebBrowsing" = {<br> priority = 300<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_ranges = ["80","443"]<br> source_address_prefix = "*"<br> destination_address_prefix = "VirtualNetwork"<br> }<br> }<br> },<br> "network_security_group_2" = {<br> rules = {}<br> }<br>}</pre> | `any` | n/a | yes | +| <a name="input_network_security_groups"></a> [network\_security\_groups](#input\_network\_security\_groups) | Map of Network Security Groups to create.<br><br>List of either required or important properties:<br><br>- `name` - (`string`, required) name of the Network Security Group.<br>- `rules` - (`map`, optional) A list of objects representing Network Security Rules.<br><br> Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available:<br><br> - `name` - (`string`, required) name of the rule<br> - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule.<br> - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`.<br> - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`.<br> - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol)<br> - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all.<br> - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports.<br> - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all.<br> - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports.<br> - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`.<br> - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed.<br> - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details.<br> - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed.<br><br>List of optional properties:<br><br>- `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource.<br><br>Example:<pre>{<br> "nsg_1" = {<br> name = "network_security_group_1"<br> location = "Australia Central"<br> rules = {<br> "AllOutbound" = {<br> priority = 100<br> direction = "Outbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "*"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowSSH" = {<br> priority = 200<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "22"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowWebBrowsing" = {<br> priority = 300<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_ranges = ["80","443"]<br> source_address_prefix = "*"<br> destination_address_prefix = "VirtualNetwork"<br> }<br> }<br> },<br> "network_security_group_2" = {<br> rules = {}<br> }<br>}</pre> | <pre>map(object({<br> name = string<br> location = optional(string)<br> rules = optional(map(object({<br> name = string<br> priority = number<br> direction = string<br> access = string<br> protocol = string<br> source_port_range = optional(string)<br> source_port_ranges = optional(list(string))<br> destination_port_range = optional(string)<br> destination_port_ranges = optional(list(string))<br> source_address_prefix = optional(string)<br> source_address_prefixes = optional(list(string))<br> destination_address_prefix = optional(string)<br> destination_address_prefixes = optional(list(string))<br> })), {})<br> }))</pre> | `{}` | no | | <a name="input_route_tables"></a> [route\_tables](#input\_route\_tables) | Map of objects describing a Route Table.<br>List of available attributes of each Route Table entry:<br>- `name`: Name of a Route Table.<br>- `location` : (Optional) Specifies the Azure location where to deploy the resource.<br>- `routes` : (Optional) Map of routes within the Route Table.<br> List of available attributes of each route entry:<br> - `address_prefix` : The destination CIDR to which the route applies, such as `10.1.0.0/16`.<br> - `next_hop_type` : The type of Azure hop the packet should be sent to.<br> Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`.<br> - `next_hop_in_ip_address` : Contains the IP address packets should be forwarded to. <br> Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`.<br><br>Example:<pre>{<br> "rt_1" = {<br> name = "route_table_1"<br> routes = {<br> "route_1" = {<br> address_prefix = "10.1.0.0/16"<br> next_hop_type = "vnetlocal"<br> },<br> "route_2" = {<br> address_prefix = "10.2.0.0/16"<br> next_hop_type = "vnetlocal"<br> },<br> }<br> },<br> "rt_2" = {<br> name = "route_table_2"<br> routes = {<br> "route_3" = {<br> address_prefix = "0.0.0.0/0"<br> next_hop_type = "VirtualAppliance"<br> next_hop_in_ip_address = "10.112.0.100"<br> }<br> },<br> },<br>}</pre> | `map` | `{}` | no | | <a name="input_subnets"></a> [subnets](#input\_subnets) | Map of subnet objects to create within a virtual network. If `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them.<br><br>List of available attributes of each subnet entry:<br>- `name` - Name of a subnet.<br>- `address_prefixes` : The address prefix to use for the subnet. Only required when a subnet will be created.<br>- `network_security_group` : The Network Security Group identifier to associate with the subnet.<br>- `route_table_id` : The Route Table identifier to associate with the subnet.<br>- `enable_storage_service_endpoint` : Flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Defaults to `false`.<br>Example:<pre>{<br> "management" = {<br> name = "management-snet"<br> address_prefixes = ["10.100.0.0/24"]<br> network_security_group = "network_security_group_1"<br> route_table = "route_table_1"<br> enable_storage_service_endpoint = true<br> },<br> "private" = {<br> name = "private-snet"<br> address_prefixes = ["10.100.1.0/24"]<br> network_security_group = "network_security_group_2"<br> route_table = "route_table_2"<br> },<br> "public" = {<br> name = "public-snet"<br> address_prefixes = ["10.100.2.0/24"]<br> network_security_group = "network_security_group_3"<br> route_table = "route_table_3"<br> },<br>}</pre> | `any` | n/a | yes | diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index 5135053b..60165fcf 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -1,7 +1,7 @@ resource "azurerm_virtual_network" "this" { count = var.create_virtual_network ? 1 : 0 - name = "${var.name_prefix}${var.name}" + name = var.name location = var.location resource_group_name = var.resource_group_name address_space = var.address_space @@ -44,8 +44,8 @@ locals { resource "azurerm_network_security_group" "this" { for_each = var.network_security_groups - name = "${var.name_prefix}${each.value.name}" - location = try(each.value.location, var.location) + name = each.value.name + location = coalesce(each.value.location, var.location) resource_group_name = var.resource_group_name tags = var.tags } @@ -53,10 +53,10 @@ resource "azurerm_network_security_group" "this" { locals { nsg_rules = flatten([ for nsg_key, nsg in var.network_security_groups : [ - for rule_name, rule in lookup(nsg, "rules", {}) : { + for rule_key, rule in try(nsg.rules, {}) : { nsg_key = nsg_key nsg_name = nsg.name - rule_name = rule_name + rule_name = rule.name rule = rule } ] @@ -75,14 +75,14 @@ resource "azurerm_network_security_rule" "this" { direction = each.value.rule.direction access = each.value.rule.access protocol = each.value.rule.protocol - source_port_range = try(each.value.rule.source_port_range, null) - source_port_ranges = try(each.value.rule.source_port_ranges, null) - destination_port_range = try(each.value.rule.destination_port_range, null) - destination_port_ranges = try(each.value.rule.destination_port_ranges, null) - source_address_prefix = try(each.value.rule.source_address_prefix, null) - source_address_prefixes = try(each.value.rule.source_address_prefixes, null) - destination_address_prefix = try(each.value.rule.destination_address_prefix, null) - destination_address_prefixes = try(each.value.rule.destination_address_prefixes, null) + source_port_range = each.value.rule.source_port_range + source_port_ranges = each.value.rule.source_port_ranges + destination_port_range = each.value.rule.destination_port_range + destination_port_ranges = each.value.rule.destination_port_ranges + source_address_prefix = each.value.rule.source_address_prefix + source_address_prefixes = each.value.rule.source_address_prefixes + destination_address_prefix = each.value.rule.destination_address_prefix + destination_address_prefixes = each.value.rule.destination_address_prefixes depends_on = [azurerm_network_security_group.this] } @@ -90,7 +90,7 @@ resource "azurerm_network_security_rule" "this" { resource "azurerm_route_table" "this" { for_each = var.route_tables - name = "${var.name_prefix}${each.value.name}" + name = each.value.name location = try(each.value.location, var.location) resource_group_name = var.resource_group_name tags = var.tags @@ -99,10 +99,10 @@ resource "azurerm_route_table" "this" { locals { route = flatten([ for route_table_key, route_table in var.route_tables : [ - for route_name, route in route_table.routes : { + for route_key, route in route_table.routes : { route_table_name = route_table.name route_table_key = route_table_key - route_name = route_name + route_name = route.name route = route } ] diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index c5829725..492e8554 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -1,9 +1,3 @@ -variable "name_prefix" { - description = "A prefix added to all resource names created by this module: VNET, NSGs, RTs. Subnet, as a sub-resource is not prefixed." - default = "" - type = string -} - variable "name" { description = "The name of the Azure Virtual Network." type = string @@ -45,26 +39,31 @@ variable "address_space" { variable "network_security_groups" { description = <<-EOF Map of Network Security Groups to create. - List of available attributes of each Network Security Group entry: - - `name` : Name of the Network Security Group. - - `location` : (Optional) Specifies the Azure location where to deploy the resource. - - `rules`: (Optional) A list of objects representing a Network Security Rule. The key of each entry acts as the name of the rule and - needs to be unique across all rules in the Network Security Group. - List of attributes available to define a Network Security Rule. - Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`: - - `priority` : Numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. - The lower the priority number, the higher the priority of the rule. - - `direction` : The direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. - - `access` : Specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. - - `protocol` : Network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol) - - `source_port_range` : A source port or a range of ports. This can also be an `*` to match all. - - `source_port_ranges` : A list of source ports or ranges of ports. This can be specified only if `source_port_range` was not used. - - `destination_port_range` : A destination port or a range of ports. This can also be an `*` to match all. - - `destination_port_ranges` : A list of destination ports or a ranges of ports. This can be specified only if `destination_port_range` was not used. - - `source_address_prefix` : Source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` : A list of source address prefixes. Tags are not allowed. Can be specified only if `source_address_prefix` was not used. - - `destination_address_prefix` : Destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - - `destination_address_prefixes` : A list of destination address prefixes. Tags are not allowed. Can be specified only if `destination_address_prefix` was not used. + + List of either required or important properties: + + - `name` - (`string`, required) name of the Network Security Group. + - `rules` - (`map`, optional) A list of objects representing Network Security Rules. + + Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: + + - `name` - (`string`, required) name of the rule + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol) + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. + + List of optional properties: + + - `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource. Example: ``` @@ -111,6 +110,26 @@ variable "network_security_groups" { } ``` EOF + type = 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)) + })), {}) + })) + default = {} } variable "route_tables" { diff --git a/modules/vnet/versions.tf b/modules/vnet/versions.tf index 501042ff..8dc5c1eb 100644 --- a/modules/vnet/versions.tf +++ b/modules/vnet/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 c3ac63fdd525d95fb75d74b217669bc64632bf9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <lpawlega@paloaltonetworks.com> Date: Wed, 27 Sep 2023 16:04:08 +0200 Subject: [PATCH 02/49] working vnet example --- .terraform-docs.yml | 47 ++- examples/vnet/.header.md | 5 + examples/vnet/brownfield.tfvars | 32 ++ examples/vnet/example.tfvars | 221 ++++++++---- examples/vnet/main.tf | 6 +- examples/vnet/variables.tf | 45 ++- modules/application_insights/outputs.tf | 6 +- modules/vnet/README.md | 445 ++++++++++++++++++++---- modules/vnet/main.tf | 14 +- modules/vnet/variables.tf | 87 +++-- 10 files changed, 706 insertions(+), 202 deletions(-) create mode 100644 examples/vnet/.header.md create mode 100644 examples/vnet/brownfield.tfvars diff --git a/.terraform-docs.yml b/.terraform-docs.yml index 1aa148a3..28fac438 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -22,51 +22,66 @@ content: |- --- | --- | --- {{- range .Module.Inputs }} {{- if .Required }} - [`{{ .Name }}`](#{{ .Name }}) | `{{ .Type }}` | {{ (split "." .Description.Raw)._0 }}. + [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. {{- end -}} {{ end }} + {{- $optional := false }} + {{- range .Module.Inputs }}{{ if not .Required }}{{ $optional = true }}{{ end }}{{ end }} + + {{ if $optional }} ## Module's Optional Inputs Name | Type | Description --- | --- | --- {{- range .Module.Inputs }} {{- if not .Required }} - [`{{ .Name }}`](#{{ .Name }}) | `{{ .Type }}` | {{ (split "." .Description.Raw)._0 }}. + [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. {{- end -}} {{ end }} + {{ end }} + {{ if ne (len .Module.Outputs) 0 }} ## Module's Outputs Name | Description --- | --- {{- range .Module.Outputs }} - [`{{ .Name }}`](#{{ .Name }}) | {{ (split "." .Description.Raw)._0 }} + `{{ .Name }}` | {{ .Description.Raw }} + {{- end }} {{- end }} ## Module's Nameplate + {{ if ne (len .Module.Requirements) 0 }} Requirements needed by this module: {{ range .Module.Requirements }} - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} {{- end }} + {{- end }} + {{ if ne (len .Module.Providers) 0 }} Providers used in this module: {{ range .Module.Providers }} - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} {{- end }} + {{- end }} + {{ if ne (len .Module.ModuleCalls) 0 }} Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- {{- range .Module.ModuleCalls }} `{{ .Name }}` | {{ if .Version }}{{ .Version }}{{ else }}-{{ end }} | {{ .Source }} | {{ .Description }} {{- end }} + {{- end }} + {{ if ne (len .Module.Resources) 0 }} Resources used in this module: {{ range .Module.Resources }} - `{{ .Type }}` ({{ .Mode }}) {{- end }} + {{- end }} ## Inputs/Outpus details @@ -78,12 +93,18 @@ content: |- {{ .Description }} - Type: `{{ .Type }}` + Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} + + ``` + {{ .Type }} + ``` + {{ end }} <sup>[back to list](#modules-required-inputs)</sup> {{ end }} {{- end }} + {{ if $optional }} ### Optional Inputs {{ range .Module.Inputs }} @@ -92,20 +113,16 @@ content: |- {{ .Description }} - Type: `{{ .Type }}` + Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} + + ``` + {{ .Type }} + ``` + {{ end }} Default value: `{{ .Default }}` <sup>[back to list](#modules-optional-inputs)</sup> {{ end }} {{- end }} - - ### Outputs - - {{ range .Module.Outputs }} - #### `{{ .Name }}` - - {{ .Description }} - - <sup>[back to list](#modules-outputs)</sup> - {{- end }} \ No newline at end of file + {{ end }} diff --git a/examples/vnet/.header.md b/examples/vnet/.header.md new file mode 100644 index 00000000..24dfd8ae --- /dev/null +++ b/examples/vnet/.header.md @@ -0,0 +1,5 @@ +# 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. diff --git a/examples/vnet/brownfield.tfvars b/examples/vnet/brownfield.tfvars new file mode 100644 index 00000000..5f99eb82 --- /dev/null +++ b/examples/vnet/brownfield.tfvars @@ -0,0 +1,32 @@ +# --- 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 index 35fbe122..f1c2a951 100644 --- a/examples/vnet/example.tfvars +++ b/examples/vnet/example.tfvars @@ -10,15 +10,40 @@ tags = { # --- VNET PART --- # vnets = { - "transit" = { - name = "transit" - address_space = ["10.0.0.0/25"] + 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 = { - "management" = { - name = "mgmt-nsg" + "nsg" = { + name = "nsg" rules = { - inbound = { - name = "mgmt_allow_inbound" + "a_rule" = { + name = "a_rule_name" priority = 100 direction = "Inbound" access = "Allow" @@ -30,86 +55,128 @@ vnets = { } } } - "public" = { - name = "public-nsg" - } } route_tables = { - "management" = { - name = "mgmt-rt" + "rt" = { + name = "a_udr" 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" + "udr" = { + name = "udr" + address_prefix = "10.0.0.0/8" 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"] + "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.tf b/examples/vnet/main.tf index 686ecc08..6373bf4e 100644 --- a/examples/vnet/main.tf +++ b/examples/vnet/main.tf @@ -24,14 +24,14 @@ module "vnet" { name = "${var.name_prefix}${each.value.name}" create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.create_virtual_network ? each.value.address_space : [] create_subnets = try(each.value.create_subnets, true) subnets = try(each.value.create_subnets, true) ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + for k, v in try(each.value.subnets, {}) : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } : each.value.subnets network_security_groups = { for k, v in try(each.value.network_security_groups, {}) : diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf index 867232bd..56fc3cbd 100644 --- a/examples/vnet/variables.tf +++ b/examples/vnet/variables.tf @@ -51,7 +51,7 @@ variable "vnets" { - `name` : A name of a VNET. - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `resource_group_name` : (default: current RG) a name of an existing Resource Group in which the VNET will reside - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - `subnets` : map of Subnets to create @@ -59,4 +59,47 @@ variable "vnets" { - `network_security_groups` : map of Network Security Groups to create - `route_tables` : map of Route Tables to create. 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/application_insights/outputs.tf b/modules/application_insights/outputs.tf index 6d148f1a..9f0a00e2 100644 --- a/modules/application_insights/outputs.tf +++ b/modules/application_insights/outputs.tf @@ -1,9 +1,5 @@ output "metrics_instrumentation_key" { - description = <<-EOF - The Instrumentation Key of the created instance of Azure Application Insights. - - The instance is unused by default, but is ready to receive custom PAN-OS metrics from the firewalls. To use it, paste this Instrumentation Key into PAN-OS -> Device -> VM-Series -> Azure. - EOF + description = "The Instrumentation Key of the created instance of Azure Application Insights." value = azurerm_application_insights.this.instrumentation_key sensitive = true } diff --git a/modules/vnet/README.md b/modules/vnet/README.md index e17f72bd..fb22280c 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -1,61 +1,386 @@ +<!-- BEGIN_TF_DOCS --> +# Palo Alto Networks VNet Module for Azure -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.3, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_network_security_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource | -| [azurerm_network_security_rule.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule) | resource | -| [azurerm_route.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route) | resource | -| [azurerm_route_table.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table) | resource | -| [azurerm_subnet.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource | -| [azurerm_subnet_network_security_group_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association) | resource | -| [azurerm_subnet_route_table_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_route_table_association) | resource | -| [azurerm_virtual_network.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource | -| [azurerm_subnet.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet) | data source | -| [azurerm_virtual_network.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name"></a> [name](#input\_name) | The name of the Azure Virtual Network. | `string` | n/a | yes | -| <a name="input_create_virtual_network"></a> [create\_virtual\_network](#input\_create\_virtual\_network) | If true, create the Virtual Network, otherwise just use a pre-existing network. | `bool` | `true` | no | -| <a name="input_create_subnets"></a> [create\_subnets](#input\_create\_subnets) | If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. | `bool` | `true` | no | -| <a name="input_location"></a> [location](#input\_location) | Location of the resources that will be deployed. | `string` | n/a | yes | -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to all of the created resources. | `map(any)` | `{}` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to use. | `string` | n/a | yes | -| <a name="input_address_space"></a> [address\_space](#input\_address\_space) | The address space used by the virtual network. You can supply more than one address space. | `list(string)` | n/a | yes | -| <a name="input_network_security_groups"></a> [network\_security\_groups](#input\_network\_security\_groups) | Map of Network Security Groups to create.<br><br>List of either required or important properties:<br><br>- `name` - (`string`, required) name of the Network Security Group.<br>- `rules` - (`map`, optional) A list of objects representing Network Security Rules.<br><br> Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available:<br><br> - `name` - (`string`, required) name of the rule<br> - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule.<br> - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`.<br> - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`.<br> - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol)<br> - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all.<br> - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports.<br> - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all.<br> - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports.<br> - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`.<br> - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed.<br> - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details.<br> - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed.<br><br>List of optional properties:<br><br>- `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource.<br><br>Example:<pre>{<br> "nsg_1" = {<br> name = "network_security_group_1"<br> location = "Australia Central"<br> rules = {<br> "AllOutbound" = {<br> priority = 100<br> direction = "Outbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "*"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowSSH" = {<br> priority = 200<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_range = "22"<br> source_address_prefix = "*"<br> destination_address_prefix = "*"<br> },<br> "AllowWebBrowsing" = {<br> priority = 300<br> direction = "Inbound"<br> access = "Allow"<br> protocol = "Tcp"<br> source_port_range = "*"<br> destination_port_ranges = ["80","443"]<br> source_address_prefix = "*"<br> destination_address_prefix = "VirtualNetwork"<br> }<br> }<br> },<br> "network_security_group_2" = {<br> rules = {}<br> }<br>}</pre> | <pre>map(object({<br> name = string<br> location = optional(string)<br> rules = optional(map(object({<br> name = string<br> priority = number<br> direction = string<br> access = string<br> protocol = string<br> source_port_range = optional(string)<br> source_port_ranges = optional(list(string))<br> destination_port_range = optional(string)<br> destination_port_ranges = optional(list(string))<br> source_address_prefix = optional(string)<br> source_address_prefixes = optional(list(string))<br> destination_address_prefix = optional(string)<br> destination_address_prefixes = optional(list(string))<br> })), {})<br> }))</pre> | `{}` | no | -| <a name="input_route_tables"></a> [route\_tables](#input\_route\_tables) | Map of objects describing a Route Table.<br>List of available attributes of each Route Table entry:<br>- `name`: Name of a Route Table.<br>- `location` : (Optional) Specifies the Azure location where to deploy the resource.<br>- `routes` : (Optional) Map of routes within the Route Table.<br> List of available attributes of each route entry:<br> - `address_prefix` : The destination CIDR to which the route applies, such as `10.1.0.0/16`.<br> - `next_hop_type` : The type of Azure hop the packet should be sent to.<br> Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`.<br> - `next_hop_in_ip_address` : Contains the IP address packets should be forwarded to. <br> Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`.<br><br>Example:<pre>{<br> "rt_1" = {<br> name = "route_table_1"<br> routes = {<br> "route_1" = {<br> address_prefix = "10.1.0.0/16"<br> next_hop_type = "vnetlocal"<br> },<br> "route_2" = {<br> address_prefix = "10.2.0.0/16"<br> next_hop_type = "vnetlocal"<br> },<br> }<br> },<br> "rt_2" = {<br> name = "route_table_2"<br> routes = {<br> "route_3" = {<br> address_prefix = "0.0.0.0/0"<br> next_hop_type = "VirtualAppliance"<br> next_hop_in_ip_address = "10.112.0.100"<br> }<br> },<br> },<br>}</pre> | `map` | `{}` | no | -| <a name="input_subnets"></a> [subnets](#input\_subnets) | Map of subnet objects to create within a virtual network. If `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them.<br><br>List of available attributes of each subnet entry:<br>- `name` - Name of a subnet.<br>- `address_prefixes` : The address prefix to use for the subnet. Only required when a subnet will be created.<br>- `network_security_group` : The Network Security Group identifier to associate with the subnet.<br>- `route_table_id` : The Route Table identifier to associate with the subnet.<br>- `enable_storage_service_endpoint` : Flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Defaults to `false`.<br>Example:<pre>{<br> "management" = {<br> name = "management-snet"<br> address_prefixes = ["10.100.0.0/24"]<br> network_security_group = "network_security_group_1"<br> route_table = "route_table_1"<br> enable_storage_service_endpoint = true<br> },<br> "private" = {<br> name = "private-snet"<br> address_prefixes = ["10.100.1.0/24"]<br> network_security_group = "network_security_group_2"<br> route_table = "route_table_2"<br> },<br> "public" = {<br> name = "public-snet"<br> address_prefixes = ["10.100.2.0/24"]<br> network_security_group = "network_security_group_3"<br> route_table = "route_table_3"<br> },<br>}</pre> | `any` | n/a | yes | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_virtual_network_id"></a> [virtual\_network\_id](#output\_virtual\_network\_id) | The identifier of the created or sourced Virtual Network. | -| <a name="output_vnet_cidr"></a> [vnet\_cidr](#output\_vnet\_cidr) | VNET address space. | -| <a name="output_subnet_ids"></a> [subnet\_ids](#output\_subnet\_ids) | The identifiers of the created or sourced Subnets. | -| <a name="output_subnet_cidrs"></a> [subnet\_cidrs](#output\_subnet\_cidrs) | Subnet CIDRs (sourced or created). | -| <a name="output_network_security_group_ids"></a> [network\_security\_group\_ids](#output\_network\_security\_group\_ids) | The identifiers of the created Network Security Groups. | -| <a name="output_route_table_ids"></a> [route\_table\_ids](#output\_route\_table\_ids) | The identifiers of the created Route Tables. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +A terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. + +## Usage + +For usage refer to any example module. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Virtual Network. +[`location`](#location) | `string` | Location of the resources that will be deployed. +[`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group to use. +[`address_space`](#address_space) | `list` | The address space used by the virtual network. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`create_virtual_network`](#create_virtual_network) | `bool` | If true, create the Virtual Network, otherwise just use a pre-existing network. +[`tags`](#tags) | `map` | Map of tags to assign to all of the created resources. +[`network_security_groups`](#network_security_groups) | `map` | Map of objects describing Network Security Groups. +[`route_tables`](#route_tables) | `map` | Map of objects describing a Route Tables. +[`create_subnets`](#create_subnets) | `bool` | If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. +[`subnets`](#subnets) | `map` | Map of objects describing subnets to create within a virtual network. + + + +## Module's Outputs + +Name | Description +--- | --- +`virtual_network_id` | The identifier of the created or sourced Virtual Network. +`vnet_cidr` | VNET address space. +`subnet_ids` | The identifiers of the created or sourced Subnets. +`subnet_cidrs` | Subnet CIDRs (sourced or created). +`network_security_group_ids` | The identifiers of the created Network Security Groups. +`route_table_ids` | The identifiers of the created Route Tables. + +## 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: + +- `network_security_group` (managed) +- `network_security_rule` (managed) +- `route` (managed) +- `route_table` (managed) +- `subnet` (managed) +- `subnet_network_security_group_association` (managed) +- `subnet_route_table_association` (managed) +- `virtual_network` (managed) +- `subnet` (data) +- `virtual_network` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Virtual Network. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### location + +Location of the resources that will be deployed. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### resource_group_name + +Name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### address_space + +The address space used by the virtual network. You can supply more than one address space. + +Type: list(map(string)) + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + +### Optional Inputs + + + +#### create_virtual_network + +If true, create the Virtual Network, otherwise just use a pre-existing network. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### tags + +Map of tags to assign to all of the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### network_security_groups + +Map of objects describing Network Security Groups. + +List of either required or important properties: + +- `name` - (`string`, required) name of the Network Security Group. +- `rules` - (`map`, optional) A list of objects representing Network Security Rules. + + Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: + + - `name` - (`string`, required) name of the rule + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol) + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. + +List of optional properties: + +- `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource. + +Example: +```hcl +{ + "nsg_1" = { + name = "network_security_group_1" + location = "Australia Central" + rules = { + "AllOutbound" = { + priority = 100 + direction = "Outbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "*" + source_address_prefix = "*" + destination_address_prefix = "*" + }, + "AllowSSH" = { + priority = 200 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = "*" + destination_address_prefix = "*" + }, + "AllowWebBrowsing" = { + priority = 300 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_ranges = ["80","443"] + source_address_prefix = "*" + destination_address_prefix = "VirtualNetwork" + } + } + }, + "network_security_group_2" = { + rules = {} + } +} +``` + + +Type: + +``` +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)) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### route_tables + +Map of objects describing a Route Tables. + +List of either required or important properties: + +- `name` - (`string`, required) name of a Route Table. +- `routes` - (`map`, required) a map of Route Table entries (UDRs): + - `name` - (`string`, required) a name of a UDR. + - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. + - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. + Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. + - `next_hop_in_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. + Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`. + +List of optional properties: + +- `location` - (`string`, optional, defaults to VNET's location) Specifies the Azure location where to deploy the resource. + +Example: +```hcl +{ + "rt_1" = { + name = "route_table_1" + routes = { + "route_1" = { + address_prefix = "10.1.0.0/16" + next_hop_type = "vnetlocal" + }, + "route_2" = { + address_prefix = "10.2.0.0/16" + next_hop_type = "vnetlocal" + }, + } + }, + "rt_2" = { + name = "route_table_2" + routes = { + "route_3" = { + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "10.112.0.100" + } + }, + }, +} +``` + + +Type: + +``` +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) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### create_subnets + +If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### subnets + +Map of objects describing subnets to create within a virtual network. + +By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. + +List of available attributes of each subnet entry: + +- `name` - (`string`, required) name of a subnet. +- `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. +- `network_security_group` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. +- `route_table_id` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. +- `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. + +Example: +```hcl +{ + "management" = { + name = "management-snet" + address_prefixes = ["10.100.0.0/24"] + network_security_group = "network_security_group_1" + route_table = "route_table_1" + enable_storage_service_endpoint = true + }, + "private" = { + name = "private-snet" + address_prefixes = ["10.100.1.0/24"] + network_security_group = "network_security_group_2" + route_table = "route_table_2" + }, + "public" = { + name = "public-snet" + address_prefixes = ["10.100.2.0/24"] + network_security_group = "network_security_group_3" + route_table = "route_table_3" + }, +} +``` + + +Type: + +``` +map(object({ + name = string + address_prefixes = optional(list(string), []) + network_security_group = optional(string) + route_table = optional(string) + enable_storage_service_endpoint = optional(bool, false) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index 60165fcf..bf212691 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -45,7 +45,7 @@ resource "azurerm_network_security_group" "this" { for_each = var.network_security_groups name = each.value.name - location = coalesce(each.value.location, var.location) + location = var.location resource_group_name = var.resource_group_name tags = var.tags } @@ -91,7 +91,7 @@ resource "azurerm_route_table" "this" { for_each = var.route_tables name = each.value.name - location = try(each.value.location, var.location) + location = var.location resource_group_name = var.resource_group_name tags = var.tags } @@ -119,19 +119,19 @@ resource "azurerm_route" "this" { route_table_name = azurerm_route_table.this[each.value.route_table_key].name address_prefix = each.value.route.address_prefix next_hop_type = each.value.route.next_hop_type - next_hop_in_ip_address = try(each.value.route.next_hop_in_ip_address, null) + next_hop_in_ip_address = each.value.route.next_hop_in_ip_address } resource "azurerm_subnet_network_security_group_association" "this" { - for_each = { for k, v in var.subnets : k => v if can(v.network_security_group) } + for_each = { for k, v in var.subnets : k => v if v.network_security_group_key != null } subnet_id = local.subnets[each.key].id - network_security_group_id = azurerm_network_security_group.this[each.value.network_security_group].id + network_security_group_id = azurerm_network_security_group.this[each.value.network_security_group_key].id } resource "azurerm_subnet_route_table_association" "this" { - for_each = { for k, v in var.subnets : k => v if can(v.route_table) } + for_each = { for k, v in var.subnets : k => v if v.route_table_key != null } subnet_id = local.subnets[each.key].id - route_table_id = azurerm_route_table.this[each.value.route_table].id + route_table_id = azurerm_route_table.this[each.value.route_table_key].id } diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index 492e8554..64d96e85 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -9,12 +9,6 @@ variable "create_virtual_network" { type = bool } -variable "create_subnets" { - description = "If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets." - default = true - type = bool -} - variable "location" { description = "Location of the resources that will be deployed." type = string @@ -22,8 +16,8 @@ variable "location" { variable "tags" { description = "Map of tags to assign to all of the created resources." - type = map(any) default = {} + type = map(string) } variable "resource_group_name" { @@ -38,7 +32,7 @@ variable "address_space" { variable "network_security_groups" { description = <<-EOF - Map of Network Security Groups to create. + Map of objects describing Network Security Groups. List of either required or important properties: @@ -61,16 +55,11 @@ variable "network_security_groups" { - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. - List of optional properties: - - - `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource. - Example: - ``` + ```hcl { "nsg_1" = { name = "network_security_group_1" - location = "Australia Central" rules = { "AllOutbound" = { priority = 100 @@ -110,9 +99,10 @@ variable "network_security_groups" { } ``` EOF + default = {} + nullable = false type = map(object({ - name = string - location = optional(string) + name = string rules = optional(map(object({ name = string priority = number @@ -129,25 +119,25 @@ variable "network_security_groups" { destination_address_prefixes = optional(list(string)) })), {}) })) - default = {} } variable "route_tables" { description = <<-EOF - Map of objects describing a Route Table. - List of available attributes of each Route Table entry: - - `name`: Name of a Route Table. - - `location` : (Optional) Specifies the Azure location where to deploy the resource. - - `routes` : (Optional) Map of routes within the Route Table. - List of available attributes of each route entry: - - `address_prefix` : The destination CIDR to which the route applies, such as `10.1.0.0/16`. - - `next_hop_type` : The type of Azure hop the packet should be sent to. + Map of objects describing a Route Tables. + + List of either required or important properties: + + - `name` - (`string`, required) name of a Route Table. + - `routes` - (`map`, required) a map of Route Table entries (UDRs): + - `name` - (`string`, required) a name of a UDR. + - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. + - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. - - `next_hop_in_ip_address` : Contains the IP address packets should be forwarded to. + - `next_hop_in_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`. Example: - ``` + ```hcl { "rt_1" = { name = "route_table_1" @@ -176,20 +166,40 @@ variable "route_tables" { ``` EOF default = {} + nullable = false + type = map(object({ + name = string + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_in_ip_address = optional(string) + })) + })) +} + +variable "create_subnets" { + description = "If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets." + default = true + type = bool } variable "subnets" { description = <<-EOF - Map of subnet objects to create within a virtual network. If `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. + Map of objects describing subnets to create within a virtual network. + + By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. List of available attributes of each subnet entry: - - `name` - Name of a subnet. - - `address_prefixes` : The address prefix to use for the subnet. Only required when a subnet will be created. - - `network_security_group` : The Network Security Group identifier to associate with the subnet. - - `route_table_id` : The Route Table identifier to associate with the subnet. - - `enable_storage_service_endpoint` : Flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Defaults to `false`. + + - `name` - (`string`, required) name of a subnet. + - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. + - `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. + - `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. + - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. + Example: - ``` + ```hcl { "management" = { name = "management-snet" @@ -213,4 +223,13 @@ variable "subnets" { } ``` EOF + default = {} + nullable = false + type = 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) + })) } From bb242f59ede9e4bc54d03ecb0342bccc01d8682d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <lpawlega@paloaltonetworks.com> Date: Wed, 27 Sep 2023 16:16:17 +0200 Subject: [PATCH 03/49] update documentation --- .terraform-docs.yml | 4 +- examples/vnet/README.md | 195 +++++++++++++++++++++++++++++++++++++ examples/vnet/variables.tf | 19 ++-- modules/vnet/README.md | 31 ++---- 4 files changed, 217 insertions(+), 32 deletions(-) create mode 100644 examples/vnet/README.md diff --git a/.terraform-docs.yml b/.terraform-docs.yml index 28fac438..fcbe8715 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -95,7 +95,7 @@ content: |- Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} - ``` + ```hcl {{ .Type }} ``` {{ end }} @@ -115,7 +115,7 @@ content: |- Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} - ``` + ```hcl {{ .Type }} ``` {{ end }} diff --git a/examples/vnet/README.md b/examples/vnet/README.md new file mode 100644 index 00000000..ae4ce653 --- /dev/null +++ b/examples/vnet/README.md @@ -0,0 +1,195 @@ +<!-- BEGIN_TF_DOCS --> +# 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 + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf index 56fc3cbd..a0cea61d 100644 --- a/examples/vnet/variables.tf +++ b/examples/vnet/variables.tf @@ -16,7 +16,7 @@ variable "name_prefix" { 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-" ``` @@ -48,17 +48,18 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of an existing Resource Group in which the VNET will reside + - `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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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) diff --git a/modules/vnet/README.md b/modules/vnet/README.md index fb22280c..8e2bade8 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -105,7 +105,7 @@ Type: string The address space used by the virtual network. You can supply more than one address space. -Type: list(map(string)) +Type: list(string) <sup>[back to list](#modules-required-inputs)</sup> @@ -167,16 +167,11 @@ List of either required or important properties: - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. -List of optional properties: - -- `location` : (`string`, optional, defaults to VNET's location) specifies the Azure location where to deploy the resource. - Example: ```hcl { "nsg_1" = { name = "network_security_group_1" - location = "Australia Central" rules = { "AllOutbound" = { priority = 100 @@ -219,10 +214,9 @@ Example: Type: -``` +```hcl map(object({ - name = string - location = optional(string) + name = string rules = optional(map(object({ name = string priority = number @@ -261,10 +255,6 @@ List of either required or important properties: - `next_hop_in_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`. -List of optional properties: - -- `location` - (`string`, optional, defaults to VNET's location) Specifies the Azure location where to deploy the resource. - Example: ```hcl { @@ -297,10 +287,9 @@ Example: Type: -``` +```hcl map(object({ - name = string - location = optional(string) + name = string routes = map(object({ name = string address_prefix = string @@ -335,8 +324,8 @@ List of available attributes of each subnet entry: - `name` - (`string`, required) name of a subnet. - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. -- `network_security_group` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. -- `route_table_id` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. +- `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. +- `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Example: @@ -367,12 +356,12 @@ Example: Type: -``` +```hcl map(object({ name = string address_prefixes = optional(list(string), []) - network_security_group = optional(string) - route_table = optional(string) + network_security_group_key = optional(string) + route_table_key = optional(string) enable_storage_service_endpoint = optional(bool, false) })) ``` From 2b2c82f1848f64a8ee84a2e3fba4aa3d17c1125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <lpawlega@paloaltonetworks.com> Date: Fri, 29 Sep 2023 09:35:45 +0200 Subject: [PATCH 04/49] 1st module tests --- examples/vnet/main.tf | 16 +++--- examples/vnet/todo.md | 9 ---- modules/vnet/main.tf | 4 +- modules/vnet/main_test.go | 63 ++++++++++++++++++++++++ tests/modules/vnet/main.tf | 33 +++++++++++++ tests/modules/vnet/outputs.tf | 29 +++++++++++ tests/modules/vnet/plan.tfvars | 71 +++++++++++++++++++++++++++ tests/modules/vnet/standard_plan.json | 32 ++++++++++++ tests/modules/vnet/variables.tf | 51 +++++++++++++++++++ tests/modules/vnet/versions.tf | 16 ++++++ 10 files changed, 304 insertions(+), 20 deletions(-) delete mode 100644 examples/vnet/todo.md create mode 100644 tests/modules/vnet/main.tf create mode 100644 tests/modules/vnet/outputs.tf create mode 100644 tests/modules/vnet/plan.tfvars create mode 100644 tests/modules/vnet/standard_plan.json create mode 100644 tests/modules/vnet/variables.tf create mode 100644 tests/modules/vnet/versions.tf diff --git a/examples/vnet/main.tf b/examples/vnet/main.tf index 6373bf4e..46c34da1 100644 --- a/examples/vnet/main.tf +++ b/examples/vnet/main.tf @@ -23,22 +23,20 @@ module "vnet" { for_each = var.vnets name = "${var.name_prefix}${each.value.name}" - create_virtual_network = try(each.value.create_virtual_network, true) + create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = each.value.create_virtual_network ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = try(each.value.create_subnets, true) ? { - for k, v in try(each.value.subnets, {}) : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } : each.value.subnets - network_security_groups = { for k, v in try(each.value.network_security_groups, {}) : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in try(each.value.route_tables, {}) : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags diff --git a/examples/vnet/todo.md b/examples/vnet/todo.md deleted file mode 100644 index 5c3e63e4..00000000 --- a/examples/vnet/todo.md +++ /dev/null @@ -1,9 +0,0 @@ -todo: - -- [x] add name property to all elements -- [x] add possibility to specify name elements in glue code -- [x] use name prefix for name in glue code, name templater in the future -- [x] pop up minimum terraform to 1.3 -- [ ] add optional + full maps specification -- [ ] add documentation in new format -- [ ] add new format of the README with header, etc \ No newline at end of file diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index bf212691..7d3842a0 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -26,7 +26,7 @@ resource "azurerm_subnet" "this" { resource_group_name = var.resource_group_name virtual_network_name = local.virtual_network.name address_prefixes = each.value.address_prefixes - service_endpoints = try(each.value.enable_storage_service_endpoint, false) ? ["Microsoft.Storage"] : null + service_endpoints = each.value.enable_storage_service_endpoint ? ["Microsoft.Storage"] : null } data "azurerm_subnet" "this" { @@ -53,7 +53,7 @@ resource "azurerm_network_security_group" "this" { locals { nsg_rules = flatten([ for nsg_key, nsg in var.network_security_groups : [ - for rule_key, rule in try(nsg.rules, {}) : { + for rule_key, rule in nsg.rules : { nsg_key = nsg_key nsg_name = nsg.name rule_name = rule.name diff --git a/modules/vnet/main_test.go b/modules/vnet/main_test.go index b7db98e9..785ebe6c 100644 --- a/modules/vnet/main_test.go +++ b/modules/vnet/main_test.go @@ -1,11 +1,74 @@ package vnet import ( + "encoding/json" + "os" "testing" "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/terraform" ) +const testModulePath = "../../tests/modules/vnet" + func TestValidate(t *testing.T) { testskeleton.ValidateCode(t, nil) } + +func CreateTerraformOptions(t *testing.T) *terraform.Options { + terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: testModulePath, + VarFiles: []string{"plan.tfvars"}, + Logger: logger.Default, + Lock: true, + Upgrade: true, + SetVarsAfterVarFiles: true, + PlanFilePath: "plan.tfplan", + }) + + return terraformOptions +} + +func TestSetup(t *testing.T) { + terraformOptions := CreateTerraformOptions(t) + + tfplan := terraform.InitAndPlanAndShowWithStruct(t, terraformOptions) + + standardPlan := make(map[string][]string) + standardPlan["plannedValues"] = make([]string, len(tfplan.ResourcePlannedValuesMap)) + standardPlan["changedValues"] = make([]string, len(tfplan.ResourcePlannedValuesMap)) + + i := 0 + for k := range tfplan.ResourcePlannedValuesMap { + standardPlan["plannedValues"][i] = k + i++ + } + i = 0 + for k := range tfplan.ResourceChangesMap { + standardPlan["changedValues"][i] = k + i++ + } + standardPlanJSON, _ := json.MarshalIndent(standardPlan, "", " ") + println() + println(string(standardPlanJSON)) + println() +} + +func TestPlan(t *testing.T) { + terraformOptions := CreateTerraformOptions(t) + + tfplan := terraform.InitAndPlanAndShowWithStruct(t, terraformOptions) + + standardPlanJSON, _ := os.ReadFile(testModulePath + "/standard_plan.json") + var standardPlan map[string][]string + json.Unmarshal(standardPlanJSON, &standardPlan) + + for _, planned := range standardPlan["plannedValues"] { + terraform.AssertPlannedValuesMapKeyExists(t, tfplan, planned) + } + for _, changed := range standardPlan["changedValues"] { + terraform.AssertResourceChangesMapKeyExists(t, tfplan, changed) + } + +} diff --git a/tests/modules/vnet/main.tf b/tests/modules/vnet/main.tf new file mode 100644 index 00000000..241e0d9e --- /dev/null +++ b/tests/modules/vnet/main.tf @@ -0,0 +1,33 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../../modules/vnet" + + for_each = var.vnets + + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, azurerm_resource_group.this.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} diff --git a/tests/modules/vnet/outputs.tf b/tests/modules/vnet/outputs.tf new file mode 100644 index 00000000..e6bb0b88 --- /dev/null +++ b/tests/modules/vnet/outputs.tf @@ -0,0 +1,29 @@ +# output "virtual_network_id" { +# description = "The identifier of the created or sourced Virtual Network." +# value = local.virtual_network.id +# } + +# output "vnet_cidr" { +# description = "VNET address space." +# value = local.virtual_network.address_space +# } + +# output "subnet_ids" { +# description = "The identifiers of the created or sourced Subnets." +# value = { for k, v in local.subnets : k => v.id } +# } + +# output "subnet_cidrs" { +# description = "Subnet CIDRs (sourced or created)." +# value = { for k, v in local.subnets : k => v.address_prefixes[0] } +# } + +# output "network_security_group_ids" { +# description = "The identifiers of the created Network Security Groups." +# value = { for k, v in azurerm_network_security_group.this : k => v.id } +# } + +# output "route_table_ids" { +# description = "The identifiers of the created Route Tables." +# value = { for k, v in azurerm_route_table.this : k => v.id } +# } diff --git a/tests/modules/vnet/plan.tfvars b/tests/modules/vnet/plan.tfvars new file mode 100644 index 00000000..78d6dc6b --- /dev/null +++ b/tests/modules/vnet/plan.tfvars @@ -0,0 +1,71 @@ +location = "North Europe" +resource_group_name = "vnet-rg" +name_prefix = "terratest-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terratest" +} + +vnets = { + empty = { + name = "empty" + address_space = ["10.0.1.0/25"] + } + subnetted = { + name = "subnetted-vnet" + address_space = ["10.0.2.0/25"] + subnets = { + subnet_a = { + name = "fosix-subnet_a" + address_prefixes = ["10.0.2.0/26"] + } + subnet_b = { + name = "fosix-subnet_b" + address_prefixes = ["10.0.2.64/26"] + } + } + } + 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"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/25" + 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" + enable_storage_service_endpoint = true + } + } + } +} diff --git a/tests/modules/vnet/standard_plan.json b/tests/modules/vnet/standard_plan.json new file mode 100644 index 00000000..76de85e5 --- /dev/null +++ b/tests/modules/vnet/standard_plan.json @@ -0,0 +1,32 @@ +{ + "changedValues": [ + "azurerm_resource_group.this", + "module.vnet[\"non-empty\"].azurerm_route.this[\"rt-udr\"]", + "module.vnet[\"non-empty\"].azurerm_subnet.this[\"some_subnet\"]", + "module.vnet[\"non-empty\"].azurerm_subnet_route_table_association.this[\"some_subnet\"]", + "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_a\"]", + "module.vnet[\"non-empty\"].azurerm_network_security_group.this[\"nsg\"]", + "module.vnet[\"non-empty\"].azurerm_network_security_rule.this[\"nsg-a_rule_name\"]", + "module.vnet[\"non-empty\"].azurerm_subnet_network_security_group_association.this[\"some_subnet\"]", + "module.vnet[\"subnetted\"].azurerm_virtual_network.this[0]", + "module.vnet[\"non-empty\"].azurerm_virtual_network.this[0]", + "module.vnet[\"empty\"].azurerm_virtual_network.this[0]", + "module.vnet[\"non-empty\"].azurerm_route_table.this[\"rt\"]", + "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_b\"]" + ], + "plannedValues": [ + "module.vnet[\"non-empty\"].azurerm_virtual_network.this[0]", + "module.vnet[\"non-empty\"].azurerm_subnet_network_security_group_association.this[\"some_subnet\"]", + "azurerm_resource_group.this", + "module.vnet[\"non-empty\"].azurerm_route.this[\"rt-udr\"]", + "module.vnet[\"non-empty\"].azurerm_route_table.this[\"rt\"]", + "module.vnet[\"non-empty\"].azurerm_subnet.this[\"some_subnet\"]", + "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_b\"]", + "module.vnet[\"non-empty\"].azurerm_subnet_route_table_association.this[\"some_subnet\"]", + "module.vnet[\"empty\"].azurerm_virtual_network.this[0]", + "module.vnet[\"non-empty\"].azurerm_network_security_group.this[\"nsg\"]", + "module.vnet[\"non-empty\"].azurerm_network_security_rule.this[\"nsg-a_rule_name\"]", + "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_a\"]", + "module.vnet[\"subnetted\"].azurerm_virtual_network.this[0]" + ] +} \ No newline at end of file diff --git a/tests/modules/vnet/variables.tf b/tests/modules/vnet/variables.tf new file mode 100644 index 00000000..b80059fd --- /dev/null +++ b/tests/modules/vnet/variables.tf @@ -0,0 +1,51 @@ +### GENERAL +variable "tags" { type = map(string) } +variable "location" { type = string } +variable "name_prefix" { type = string } +variable "resource_group_name" { type = string } + +variable "vnets" { + 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/tests/modules/vnet/versions.tf b/tests/modules/vnet/versions.tf new file mode 100644 index 00000000..0fc8751d --- /dev/null +++ b/tests/modules/vnet/versions.tf @@ -0,0 +1,16 @@ +terraform { + required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} From 60f8c075080fd01223a79f8041e8f46f5b500127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <lpawlega@paloaltonetworks.com> Date: Wed, 4 Oct 2023 12:07:55 +0200 Subject: [PATCH 05/49] move tests to a separate branch --- modules/vnet/main_test.go | 63 ------------------------ modules/vnet/variables.tf | 1 + tests/modules/vnet/main.tf | 33 ------------- tests/modules/vnet/outputs.tf | 29 ----------- tests/modules/vnet/plan.tfvars | 71 --------------------------- tests/modules/vnet/standard_plan.json | 32 ------------ tests/modules/vnet/variables.tf | 51 ------------------- tests/modules/vnet/versions.tf | 16 ------ 8 files changed, 1 insertion(+), 295 deletions(-) delete mode 100644 tests/modules/vnet/main.tf delete mode 100644 tests/modules/vnet/outputs.tf delete mode 100644 tests/modules/vnet/plan.tfvars delete mode 100644 tests/modules/vnet/standard_plan.json delete mode 100644 tests/modules/vnet/variables.tf delete mode 100644 tests/modules/vnet/versions.tf diff --git a/modules/vnet/main_test.go b/modules/vnet/main_test.go index 785ebe6c..b7db98e9 100644 --- a/modules/vnet/main_test.go +++ b/modules/vnet/main_test.go @@ -1,74 +1,11 @@ package vnet import ( - "encoding/json" - "os" "testing" "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" - "github.com/gruntwork-io/terratest/modules/logger" - "github.com/gruntwork-io/terratest/modules/terraform" ) -const testModulePath = "../../tests/modules/vnet" - func TestValidate(t *testing.T) { testskeleton.ValidateCode(t, nil) } - -func CreateTerraformOptions(t *testing.T) *terraform.Options { - terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ - TerraformDir: testModulePath, - VarFiles: []string{"plan.tfvars"}, - Logger: logger.Default, - Lock: true, - Upgrade: true, - SetVarsAfterVarFiles: true, - PlanFilePath: "plan.tfplan", - }) - - return terraformOptions -} - -func TestSetup(t *testing.T) { - terraformOptions := CreateTerraformOptions(t) - - tfplan := terraform.InitAndPlanAndShowWithStruct(t, terraformOptions) - - standardPlan := make(map[string][]string) - standardPlan["plannedValues"] = make([]string, len(tfplan.ResourcePlannedValuesMap)) - standardPlan["changedValues"] = make([]string, len(tfplan.ResourcePlannedValuesMap)) - - i := 0 - for k := range tfplan.ResourcePlannedValuesMap { - standardPlan["plannedValues"][i] = k - i++ - } - i = 0 - for k := range tfplan.ResourceChangesMap { - standardPlan["changedValues"][i] = k - i++ - } - standardPlanJSON, _ := json.MarshalIndent(standardPlan, "", " ") - println() - println(string(standardPlanJSON)) - println() -} - -func TestPlan(t *testing.T) { - terraformOptions := CreateTerraformOptions(t) - - tfplan := terraform.InitAndPlanAndShowWithStruct(t, terraformOptions) - - standardPlanJSON, _ := os.ReadFile(testModulePath + "/standard_plan.json") - var standardPlan map[string][]string - json.Unmarshal(standardPlanJSON, &standardPlan) - - for _, planned := range standardPlan["plannedValues"] { - terraform.AssertPlannedValuesMapKeyExists(t, tfplan, planned) - } - for _, changed := range standardPlan["changedValues"] { - terraform.AssertResourceChangesMapKeyExists(t, tfplan, changed) - } - -} diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index 64d96e85..2a5b60e0 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -181,6 +181,7 @@ variable "route_tables" { variable "create_subnets" { description = "If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets." default = true + nullable = false type = bool } diff --git a/tests/modules/vnet/main.tf b/tests/modules/vnet/main.tf deleted file mode 100644 index 241e0d9e..00000000 --- a/tests/modules/vnet/main.tf +++ /dev/null @@ -1,33 +0,0 @@ -# Create or source the Resource Group. -resource "azurerm_resource_group" "this" { - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../../modules/vnet" - - for_each = var.vnets - - name = "${var.name_prefix}${each.value.name}" - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, azurerm_resource_group.this.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets - - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - tags = var.tags -} diff --git a/tests/modules/vnet/outputs.tf b/tests/modules/vnet/outputs.tf deleted file mode 100644 index e6bb0b88..00000000 --- a/tests/modules/vnet/outputs.tf +++ /dev/null @@ -1,29 +0,0 @@ -# output "virtual_network_id" { -# description = "The identifier of the created or sourced Virtual Network." -# value = local.virtual_network.id -# } - -# output "vnet_cidr" { -# description = "VNET address space." -# value = local.virtual_network.address_space -# } - -# output "subnet_ids" { -# description = "The identifiers of the created or sourced Subnets." -# value = { for k, v in local.subnets : k => v.id } -# } - -# output "subnet_cidrs" { -# description = "Subnet CIDRs (sourced or created)." -# value = { for k, v in local.subnets : k => v.address_prefixes[0] } -# } - -# output "network_security_group_ids" { -# description = "The identifiers of the created Network Security Groups." -# value = { for k, v in azurerm_network_security_group.this : k => v.id } -# } - -# output "route_table_ids" { -# description = "The identifiers of the created Route Tables." -# value = { for k, v in azurerm_route_table.this : k => v.id } -# } diff --git a/tests/modules/vnet/plan.tfvars b/tests/modules/vnet/plan.tfvars deleted file mode 100644 index 78d6dc6b..00000000 --- a/tests/modules/vnet/plan.tfvars +++ /dev/null @@ -1,71 +0,0 @@ -location = "North Europe" -resource_group_name = "vnet-rg" -name_prefix = "terratest-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terratest" -} - -vnets = { - empty = { - name = "empty" - address_space = ["10.0.1.0/25"] - } - subnetted = { - name = "subnetted-vnet" - address_space = ["10.0.2.0/25"] - subnets = { - subnet_a = { - name = "fosix-subnet_a" - address_prefixes = ["10.0.2.0/26"] - } - subnet_b = { - name = "fosix-subnet_b" - address_prefixes = ["10.0.2.64/26"] - } - } - } - 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"] - source_port_range = "*" - destination_address_prefix = "10.0.0.0/25" - 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" - enable_storage_service_endpoint = true - } - } - } -} diff --git a/tests/modules/vnet/standard_plan.json b/tests/modules/vnet/standard_plan.json deleted file mode 100644 index 76de85e5..00000000 --- a/tests/modules/vnet/standard_plan.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "changedValues": [ - "azurerm_resource_group.this", - "module.vnet[\"non-empty\"].azurerm_route.this[\"rt-udr\"]", - "module.vnet[\"non-empty\"].azurerm_subnet.this[\"some_subnet\"]", - "module.vnet[\"non-empty\"].azurerm_subnet_route_table_association.this[\"some_subnet\"]", - "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_a\"]", - "module.vnet[\"non-empty\"].azurerm_network_security_group.this[\"nsg\"]", - "module.vnet[\"non-empty\"].azurerm_network_security_rule.this[\"nsg-a_rule_name\"]", - "module.vnet[\"non-empty\"].azurerm_subnet_network_security_group_association.this[\"some_subnet\"]", - "module.vnet[\"subnetted\"].azurerm_virtual_network.this[0]", - "module.vnet[\"non-empty\"].azurerm_virtual_network.this[0]", - "module.vnet[\"empty\"].azurerm_virtual_network.this[0]", - "module.vnet[\"non-empty\"].azurerm_route_table.this[\"rt\"]", - "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_b\"]" - ], - "plannedValues": [ - "module.vnet[\"non-empty\"].azurerm_virtual_network.this[0]", - "module.vnet[\"non-empty\"].azurerm_subnet_network_security_group_association.this[\"some_subnet\"]", - "azurerm_resource_group.this", - "module.vnet[\"non-empty\"].azurerm_route.this[\"rt-udr\"]", - "module.vnet[\"non-empty\"].azurerm_route_table.this[\"rt\"]", - "module.vnet[\"non-empty\"].azurerm_subnet.this[\"some_subnet\"]", - "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_b\"]", - "module.vnet[\"non-empty\"].azurerm_subnet_route_table_association.this[\"some_subnet\"]", - "module.vnet[\"empty\"].azurerm_virtual_network.this[0]", - "module.vnet[\"non-empty\"].azurerm_network_security_group.this[\"nsg\"]", - "module.vnet[\"non-empty\"].azurerm_network_security_rule.this[\"nsg-a_rule_name\"]", - "module.vnet[\"subnetted\"].azurerm_subnet.this[\"subnet_a\"]", - "module.vnet[\"subnetted\"].azurerm_virtual_network.this[0]" - ] -} \ No newline at end of file diff --git a/tests/modules/vnet/variables.tf b/tests/modules/vnet/variables.tf deleted file mode 100644 index b80059fd..00000000 --- a/tests/modules/vnet/variables.tf +++ /dev/null @@ -1,51 +0,0 @@ -### GENERAL -variable "tags" { type = map(string) } -variable "location" { type = string } -variable "name_prefix" { type = string } -variable "resource_group_name" { type = string } - -variable "vnets" { - 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/tests/modules/vnet/versions.tf b/tests/modules/vnet/versions.tf deleted file mode 100644 index 0fc8751d..00000000 --- a/tests/modules/vnet/versions.tf +++ /dev/null @@ -1,16 +0,0 @@ -terraform { - required_version = ">= 1.2, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - } -} - -provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} From fe1d399cb7ab8a1ce33f7bbbc5a75e590b37c882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Wed, 18 Oct 2023 07:51:02 +0200 Subject: [PATCH 06/49] refactor(module/vnet): module refactor + adjusted examples (#338) --- examples/common_vmseries/example.tfvars | 16 +- examples/common_vmseries/main.tf | 22 +- examples/common_vmseries/variables.tf | 58 +++- .../example.tfvars | 18 +- .../common_vmseries_and_autoscale/main.tf | 21 +- .../variables.tf | 58 +++- examples/dedicated_vmseries/example.tfvars | 16 +- examples/dedicated_vmseries/main.tf | 21 +- examples/dedicated_vmseries/variables.tf | 58 +++- .../example.tfvars | 18 +- .../dedicated_vmseries_and_autoscale/main.tf | 21 +- .../variables.tf | 58 +++- examples/gwlb_with_vmseries/example.tfvars | 8 +- examples/gwlb_with_vmseries/main.tf | 47 ++-- examples/gwlb_with_vmseries/variables.tf | 68 ++++- examples/standalone_panorama/example.tfvars | 3 +- examples/standalone_panorama/main.tf | 21 +- examples/standalone_panorama/variables.tf | 61 +++- examples/standalone_vmseries/example.tfvars | 3 +- examples/standalone_vmseries/main.tf | 21 +- examples/standalone_vmseries/variables.tf | 58 +++- examples/vnet/.header.md | 5 - examples/vnet/README.md | 195 ------------- examples/vnet/brownfield.tfvars | 32 --- examples/vnet/example.tfvars | 142 +--------- examples/vnet/main_test.go | 62 ----- examples/vnet/outputs.tf | 39 --- examples/vnet/variables.tf | 4 +- examples/vnet/versions.tf | 2 +- modules/vnet/.header.md | 120 +++++++- modules/vnet/README.md | 249 ++++++++++++----- modules/vnet/main.tf | 2 +- modules/vnet/variables.tf | 262 +++++++++++++++--- 33 files changed, 1053 insertions(+), 736 deletions(-) delete mode 100644 examples/vnet/.header.md delete mode 100644 examples/vnet/README.md delete mode 100644 examples/vnet/brownfield.tfvars delete mode 100644 examples/vnet/main_test.go delete mode 100644 examples/vnet/outputs.tf diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 5d1371da..3e439335 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -17,7 +17,8 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" @@ -38,10 +39,12 @@ vnets = { name = "mgmt-rt" routes = { "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } @@ -51,15 +54,18 @@ vnets = { name = "private-rt" routes = { "default" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" } "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } @@ -69,10 +75,12 @@ vnets = { name = "public-rt" routes = { "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 22042e08..77df6a95 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -45,23 +45,27 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } + module "natgw" { source = "../../modules/natgw" diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 54f10314..3ab1d4f6 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -55,17 +55,59 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "natgws" { diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index cdf0a7a4..a69c6b80 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -17,7 +17,8 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" @@ -38,14 +39,17 @@ vnets = { name = "mgmt-rt" routes = { "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } "appgw_blackhole" = { + name = "appgw-blackhole-udr" address_prefix = "10.0.0.48/28" next_hop_type = "None" } @@ -55,19 +59,23 @@ vnets = { name = "private-rt" routes = { "default" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" } "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } "appgw_blackhole" = { + name = "appgw-blackhole-udr" address_prefix = "10.0.0.48/28" next_hop_type = "None" } @@ -77,10 +85,12 @@ vnets = { name = "public-rt" routes = { "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index aaa77147..f196086a 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -39,19 +39,22 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index e3845c7a..64821d3b 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -55,17 +55,59 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "natgws" { diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index e408b1d2..2ac20c25 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -17,7 +17,8 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" @@ -38,10 +39,12 @@ vnets = { name = "mgmt-rt" routes = { "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } @@ -51,15 +54,18 @@ vnets = { name = "private-rt" routes = { "default" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" } "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } @@ -69,10 +75,12 @@ vnets = { name = "public-rt" routes = { "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index f9a7a7f7..295e249d 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -45,19 +45,22 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index a47878bf..5f2929ee 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -55,17 +55,59 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "natgws" { diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index a476b17e..5579d8a8 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -17,7 +17,8 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" @@ -38,14 +39,17 @@ vnets = { name = "mgmt-rt" routes = { "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } "appgw_blackhole" = { + name = "appgw-blackhole-udr" address_prefix = "10.0.0.48/28" next_hop_type = "None" } @@ -55,19 +59,23 @@ vnets = { name = "private-rt" routes = { "default" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" } "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "public_blackhole" = { + name = "public-blackhole-udr" address_prefix = "10.0.0.32/28" next_hop_type = "None" } "appgw_blackhole" = { + name = "appgw-blackhole-udr" address_prefix = "10.0.0.48/28" next_hop_type = "None" } @@ -77,10 +85,12 @@ vnets = { name = "public-rt" routes = { "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.0.0/28" next_hop_type = "None" } "private_blackhole" = { + name = "private-blackhole-udr" address_prefix = "10.0.0.16/28" next_hop_type = "None" } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index aaa77147..f196086a 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -39,19 +39,22 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index e3845c7a..64821d3b 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -55,17 +55,59 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "natgws" { diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 66825e2a..45278cce 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -29,7 +29,8 @@ vnets = { mgmt = { name = "vmseries-mgmt" rules = { - vmseries-mgmt-allow-inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" @@ -50,6 +51,7 @@ vnets = { name = "vmseries-mgmt" routes = { data-blackhole = { + name = "data-blackhole-udr" address_prefix = "10.0.1.16/28" next_hop_type = "None" } @@ -59,6 +61,7 @@ vnets = { name = "vmseries-data" routes = { mgmt-blackhole = { + name = "mgmt-blackhole-udr" address_prefix = "10.0.1.0/28" next_hop_type = "None" } @@ -81,7 +84,8 @@ vnets = { web = { name = "app1-web" rules = { - application-allow-inbound = { + application-inbound = { + name = "application-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index a5ec6994..8cf983c8 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -1,7 +1,7 @@ locals { - resource_group_name = var.create_resource_group ? azurerm_resource_group.this[0].name : data.azurerm_resource_group.this[0].name - vmseries_password = try(var.vmseries_common.password, random_password.vmseries[0].result) - appvms_password = try(var.appvms_common.password, random_password.appvms[0].result) + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] + vmseries_password = try(var.vmseries_common.password, random_password.vmseries[0].result) + appvms_password = try(var.appvms_common.password, random_password.appvms[0].result) } # Obtain Public IP address of deployment machine @@ -27,21 +27,26 @@ data "azurerm_resource_group" "this" { # VNets module "vnet" { + source = "../../modules/vnet" + for_each = var.vnets - source = "../../modules/vnet" - name = each.value.name - name_prefix = var.name_prefix + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group_name) - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } @@ -52,7 +57,7 @@ module "gwlb" { source = "../../modules/gwlb" name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group_name) + resource_group_name = try(each.value.resource_group_name, local.resource_group.name) location = var.location backends = each.value.backends @@ -81,7 +86,7 @@ module "ai" { ) name = "${var.name_prefix}${each.key}" - resource_group_name = local.resource_group_name + resource_group_name = local.resource_group.name location = var.location workspace_mode = try(var.application_insights.workspace_mode, null) @@ -126,7 +131,7 @@ module "bootstrap" { name = each.value.name create_storage_account = try(each.value.create_storage, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group_name) + resource_group_name = try(each.value.resource_group_name, local.resource_group.name) location = var.location storage_acl = try(each.value.storage_acl, false) @@ -143,7 +148,7 @@ module "bootstrap_share" { create_storage_account = false name = module.bootstrap[each.value.bootstrap_storage.key].storage_account.name - resource_group_name = try(var.bootstrap_storages[each.value.bootstrap_storage.key].resource_group_name, local.resource_group_name) + resource_group_name = try(var.bootstrap_storages[each.value.bootstrap_storage.key].resource_group_name, local.resource_group.name) location = var.location storage_share_name = "${var.name_prefix}${each.value.name}" @@ -180,7 +185,7 @@ resource "azurerm_availability_set" "this" { for_each = var.availability_sets name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group_name + resource_group_name = local.resource_group.name location = var.location platform_update_domain_count = try(each.value.update_domain_count, null) platform_fault_domain_count = try(each.value.fault_domain_count, null) @@ -194,7 +199,7 @@ module "vmseries" { name = "${var.name_prefix}${each.value.name}" location = var.location - resource_group_name = local.resource_group_name + resource_group_name = local.resource_group.name enable_zones = var.enable_zones avzone = try(each.value.avzone, 1) avset_id = try(azurerm_availability_set.this[each.value.availability_set_key].id, null) @@ -249,7 +254,7 @@ module "load_balancer" { name = "${var.name_prefix}${each.value.name}" location = var.location - resource_group_name = local.resource_group_name + resource_group_name = local.resource_group.name enable_zones = var.enable_zones avzones = try(each.value.avzones, null) @@ -293,7 +298,7 @@ module "appvm" { name = "${var.name_prefix}${each.value.name}" location = var.location - resource_group_name = local.resource_group_name + resource_group_name = local.resource_group.name avzone = each.value.avzone interfaces = [ diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index 3879bf54..bf006d10 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -39,19 +39,63 @@ variable "tags" { # VNets variable "vnets" { description = <<-EOF - Map with VNet definitions. Each item supports following inputs for `vnet` module: - - `name` - (required|string) VNet name. - - `create_virtual_network` - (optional|bool) Whether to create a new or source an existing VNet, defaults to `true`. - - `address_space` - (optional|list) List of CIDRs for the new VNet. - - `resource_group_name` - (optional|string) VNet's Resource Group, by default the one specified by `var.resource_group_name`. - - `create_subnets` - (optional|bool) Whether to create or source items from `subnets`, defaults to `true`. - - `subnets` - (required|map) Subnet definitions. - - `network_security_groups` - (optional|map) NSGs to create. - - `route_tables` - (optional|map) Route Tables to create. - - Please consult [module documentation](../../modules/vnet/README.md) for details. + 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 `true`) 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`, optional, 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 = any + + type = map(object({ + name = string + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) } # GWLB diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index ceba8caa..6726f2b9 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -20,7 +20,8 @@ vnets = { "panorama" = { name = "panorama-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index b39c702b..b6df3d77 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -38,19 +38,22 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index f4b17b97..ea5e0f8a 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -51,23 +51,64 @@ variable "enable_zones" { ### VNET variable "vnets" { description = <<-EOF - A map defining VNETs. A key is the VNET name, value is a set of properties like described below. + A map defining VNETs. For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : a name of a Virtual Network - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +} ### PANORAMA diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index e704be3c..d7ceb202 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -18,7 +18,8 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 22042e08..a01f8cdc 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -45,19 +45,22 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = "${var.name_prefix}${each.value.name}" + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) - subnets = each.value.subnets + create_subnets = each.value.create_subnets + subnets = each.value.create_subnets ? { + for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } : each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 6840bddc..a60942fa 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -55,17 +55,59 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create + - `create_subnets` - (`bool`, optional, 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 of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "natgws" { diff --git a/examples/vnet/.header.md b/examples/vnet/.header.md deleted file mode 100644 index 24dfd8ae..00000000 --- a/examples/vnet/.header.md +++ /dev/null @@ -1,5 +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. 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 @@ -<!-- BEGIN_TF_DOCS --> -# 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 - -<sup>[back to list](#modules-required-inputs)</sup> - - - -#### resource_group_name - -Name of the Resource Group. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### 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) - })), {}) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - - - -### Optional Inputs - - -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -#### 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: `` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### 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` - -<sup>[back to list](#modules-optional-inputs)</sup> - - - - -<!-- END_TF_DOCS --> \ 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 index f1c2a951..ba36981b 100644 --- a/examples/vnet/example.tfvars +++ b/examples/vnet/example.tfvars @@ -10,46 +10,26 @@ tags = { # --- 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"] + address_space = ["10.0.0.0/29"] } non-empty = { name = "non-empty" - address_space = ["10.0.0.0/24"] + address_space = ["8.0.0.0/5"] 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 = "*" + 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 = "*" + source_port_ranges = ["33", "44-55"] destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] } @@ -77,106 +57,4 @@ vnets = { } } } - # "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/outputs.tf b/examples/vnet/outputs.tf deleted file mode 100644 index 892ab61b..00000000 --- a/examples/vnet/outputs.tf +++ /dev/null @@ -1,39 +0,0 @@ -# output "username" { -# description = "Initial administrative username to use for VM-Series." -# value = var.vmseries_username -# } - -# output "password" { -# description = "Initial administrative password to use for VM-Series." -# value = local.vmseries_password -# sensitive = true -# } - -# output "natgw_public_ips" { -# description = "Nat Gateways Public IP resources." -# value = length(var.natgws) > 0 ? { for k, v in module.natgw : k => { -# pip = v.natgw_pip -# pip_prefix = v.natgw_pip_prefix -# } } : null -# } - -# output "metrics_instrumentation_keys" { -# description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." -# value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null -# sensitive = true -# } - -# output "lb_frontend_ips" { -# description = "IP Addresses of the load balancers." -# value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null -# } - -# output "vmseries_mgmt_ips" { -# description = "IP addresses for the VMSeries management interface." -# value = { for k, v in module.vmseries : k => v.mgmt_ip_address } -# } - -# output "bootstrap_storage_urls" { -# value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null -# sensitive = true -# } diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf index a0cea61d..e92c6532 100644 --- a/examples/vnet/variables.tf +++ b/examples/vnet/variables.tf @@ -48,12 +48,12 @@ variable "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. + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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 + - `create_subnets` - (`bool`, optional, 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) diff --git a/examples/vnet/versions.tf b/examples/vnet/versions.tf index 95b07f02..50ff584a 100644 --- a/examples/vnet/versions.tf +++ b/examples/vnet/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" diff --git a/modules/vnet/.header.md b/modules/vnet/.header.md index ca5a5749..3a835852 100644 --- a/modules/vnet/.header.md +++ b/modules/vnet/.header.md @@ -1,7 +1,123 @@ # Palo Alto Networks VNet Module for Azure -A terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. +A Terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. ## Usage -For usage refer to any example module. +This module is designed to work in several *modes* depending on which variables or flags are set. Most common usage scenarios are: + +- create all - creates a VNET, Subnet, NSGs and Route Tables. In this example the two latter are assigned to the Subnet. The NSG and Route Table have rules defined: + + ```hcl + name = "transit" + resource_group_name = "existing-rg" + address_space = ["10.0.0.0/25"] + network_security_groups = { + inbound = { + name = "inbound-nsg" + rules = { + mgmt_inbound = { + name = "allow-traffic" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.2.3.4"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } + } + route_tables = { + default = { + name = "default-rt" + routes = { + "default" = { + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "5.6.7.8" + } + } + } + } + subnets = { + "subnet" = { + name = "snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group = "inbound" + route_table = "default" + } + } + ``` + +- source a VNET but create Subnets, NSGs and Route Tables. This is a similar example to the above one, NSG and Route Table are empty this time: + + ```hcl + create_virtual_network = false + name = "existing-vnet" + resource_group_name = "existing-rg" + network_security_groups = { + inbound = { name = "inbound-nsg" } + } + route_tables = { + default = { name = "default-rt" } + } + subnets = { + "subnet" = { + name = "snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group = "inbound" + route_table = "default" + } + } + ``` + +- source a VNET and Subnet, but create NSGs and Route Tables. This is a common brownfield use case: we will source Subnets, and create and assign NSGs and Route Tables to them: + + ```hcl + create_virtual_network = false + name = "existing-vnet" + resource_group_name = "existing-rg" + network_security_groups = { + inbound = { + name = "inbound-nsg" + rules = { + mgmt_inbound = { + name = "allow-traffic" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.2.3.4"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } + } + route_tables = { + default = { + name = "default-rt" + routes = { + "default" = { + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "5.6.7.8" + } + } + } + } + create_subnets = false + subnets = { + "subnet" = { + name = "snet" + network_security_group = "inbound" + route_table = "default" + } + } + ``` diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 8e2bade8..b6e9b733 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -1,32 +1,147 @@ <!-- BEGIN_TF_DOCS --> # Palo Alto Networks VNet Module for Azure -A terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. +A Terraform module for deploying a Virtual Network and its components required for the VM-Series firewalls in Azure. ## Usage -For usage refer to any example module. +This module is designed to work in several *modes* depending on which variables or flags are set. Most common usage scenarios are: + +- create all - creates a VNET, Subnet, NSGs and Route Tables. In this example the two latter are assigned to the Subnet. The NSG and Route Table have rules defined: + ```hcl + name = "transit" + resource_group_name = "existing-rg" + address_space = ["10.0.0.0/25"] + network_security_groups = { + inbound = { + name = "inbound-nsg" + rules = { + mgmt_inbound = { + name = "allow-traffic" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.2.3.4"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } + } + route_tables = { + default = { + name = "default-rt" + routes = { + "default" = { + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "5.6.7.8" + } + } + } + } + subnets = { + "subnet" = { + name = "snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group = "inbound" + route_table = "default" + } + } + ``` + +- source a VNET but create Subnets, NSGs and Route Tables. This is a similar example to the above one, NSG and Route Table are empty this time: + + ```hcl + create_virtual_network = false + name = "existing-vnet" + resource_group_name = "existing-rg" + network_security_groups = { + inbound = { name = "inbound-nsg" } + } + route_tables = { + default = { name = "default-rt" } + } + subnets = { + "subnet" = { + name = "snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group = "inbound" + route_table = "default" + } + } + ``` + +- source a VNET and Subnet, but create NSGs and Route Tables. This is a common brownfield use case: we will source Subnets, and create and assign NSGs and Route Tables to them: + + ```hcl + create_virtual_network = false + name = "existing-vnet" + resource_group_name = "existing-rg" + network_security_groups = { + inbound = { + name = "inbound-nsg" + rules = { + mgmt_inbound = { + name = "allow-traffic" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.2.3.4"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } + } + route_tables = { + default = { + name = "default-rt" + routes = { + "default" = { + name = "default-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_in_ip_address = "5.6.7.8" + } + } + } + } + create_subnets = false + subnets = { + "subnet" = { + name = "snet" + network_security_group = "inbound" + route_table = "default" + } + } + ``` ## Module's Required Inputs Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Network. -[`location`](#location) | `string` | Location of the resources that will be deployed. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group to use. -[`address_space`](#address_space) | `list` | The address space used by the virtual network. +[`location`](#location) | `string` | Location of the deployed resources. ## Module's Optional Inputs Name | Type | Description --- | --- | --- -[`create_virtual_network`](#create_virtual_network) | `bool` | If true, create the Virtual Network, otherwise just use a pre-existing network. -[`tags`](#tags) | `map` | Map of tags to assign to all of the created resources. +[`tags`](#tags) | `map` | Map of tags to assign to all created resources. +[`create_virtual_network`](#create_virtual_network) | `bool` | Controls Virtual Network creation. +[`address_space`](#address_space) | `list` | The address space used by the virtual network. [`network_security_groups`](#network_security_groups) | `map` | Map of objects describing Network Security Groups. [`route_tables`](#route_tables) | `map` | Map of objects describing a Route Tables. -[`create_subnets`](#create_subnets) | `bool` | If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. -[`subnets`](#subnets) | `map` | Map of objects describing subnets to create within a virtual network. +[`create_subnets`](#create_subnets) | `bool` | Controls subnet creation. +[`subnets`](#subnets) | `map` | Map of objects describing subnets to manage. @@ -83,45 +198,50 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### resource_group_name + +Name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> #### location -Location of the resources that will be deployed. +Location of the deployed resources. Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### resource_group_name -Name of the Resource Group to use. -Type: string -<sup>[back to list](#modules-required-inputs)</sup> -#### address_space -The address space used by the virtual network. You can supply more than one address space. -Type: list(string) -<sup>[back to list](#modules-required-inputs)</sup> +### Optional Inputs +#### tags -### Optional Inputs +Map of tags to assign to all created resources. +Type: map(string) +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### create_virtual_network -If true, create the Virtual Network, otherwise just use a pre-existing network. +Controls Virtual Network creation. If `true`, create the Virtual Network, otherwise just use a pre-existing network. Type: bool @@ -129,26 +249,23 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> +#### address_space -#### tags - -Map of tags to assign to all of the created resources. +The address space used by the virtual network. You can supply more than one address space. Required only when you create a VNET. -Type: map(string) +Type: list(string) -Default value: `map[]` +Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - - #### network_security_groups Map of objects describing Network Security Groups. -List of either required or important properties: +List of available properties: -- `name` - (`string`, required) name of the Network Security Group. +- `name` - (`string`, required) name of the Network Security Group. - `rules` - (`map`, optional) A list of objects representing Network Security Rules. Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: @@ -157,13 +274,13 @@ List of either required or important properties: - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol) + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source address prefixes. Tags are not allowed. - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. @@ -205,8 +322,8 @@ Example: } } }, - "network_security_group_2" = { - rules = {} + "nsg_2" = { + name = "empty-nsg } } ``` @@ -244,16 +361,16 @@ Default value: `map[]` Map of objects describing a Route Tables. -List of either required or important properties: +List of available properties: -- `name` - (`string`, required) name of a Route Table. -- `routes` - (`map`, required) a map of Route Table entries (UDRs): +- `name` - (`string`, required) name of a Route Table. +- `routes` - (`map`, required) a map of Route Table entries (UDRs): - `name` - (`string`, required) a name of a UDR. - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. - Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. - - `next_hop_in_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. - Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`. + Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. + - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. + Used only when `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. Example: ```hcl @@ -262,12 +379,14 @@ Example: name = "route_table_1" routes = { "route_1" = { + name = "route-1" address_prefix = "10.1.0.0/16" - next_hop_type = "vnetlocal" + next_hop_type = "VnetLocal" }, "route_2" = { + name = "route-2" address_prefix = "10.2.0.0/16" - next_hop_type = "vnetlocal" + next_hop_type = "VnetLocal" }, } }, @@ -275,9 +394,10 @@ Example: name = "route_table_2" routes = { "route_3" = { + name = "default-nva-route" address_prefix = "0.0.0.0/0" next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.112.0.100" + next_hop_ip_address = "10.112.0.100" } }, }, @@ -291,10 +411,10 @@ Type: map(object({ name = string routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_in_ip_address = optional(string) + name = string + address_prefix = string + next_hop_type = string + next_hop_ip_address = optional(string) })) })) ``` @@ -306,7 +426,14 @@ Default value: `map[]` #### create_subnets -If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets. +Controls subnet creation. + +Possible variants: + +- `true` - create subnets described in `var.subnets` +- `false` - source subnets described in `var.subnets` +- `false` and `var.subnets` is empty - skip subnets management. + Type: bool @@ -316,17 +443,19 @@ Default value: `true` #### subnets -Map of objects describing subnets to create within a virtual network. +Map of objects describing subnets to manage. -By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. +By the default the described subnets will be created. +If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. List of available attributes of each subnet entry: - `name` - (`string`, required) name of a subnet. - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. -- `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. -- `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. -- `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. +- `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. +- `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. +- `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. + This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Example: ```hcl @@ -334,22 +463,18 @@ Example: "management" = { name = "management-snet" address_prefixes = ["10.100.0.0/24"] - network_security_group = "network_security_group_1" - route_table = "route_table_1" + network_security_group_key = "network_security_group_1" enable_storage_service_endpoint = true }, "private" = { - name = "private-snet" - address_prefixes = ["10.100.1.0/24"] - network_security_group = "network_security_group_2" - route_table = "route_table_2" + name = "private-snet" + address_prefixes = ["10.100.1.0/24"] + route_table_key = "route_table_2" }, "public" = { - name = "public-snet" - address_prefixes = ["10.100.2.0/24"] - network_security_group = "network_security_group_3" - route_table = "route_table_3" - }, + name = "public-snet" + address_prefixes = ["10.100.2.0/24"] + } } ``` diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index 7d3842a0..019acf48 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -119,7 +119,7 @@ resource "azurerm_route" "this" { route_table_name = azurerm_route_table.this[each.value.route_table_key].name address_prefix = each.value.route.address_prefix next_hop_type = each.value.route.next_hop_type - next_hop_in_ip_address = each.value.route.next_hop_in_ip_address + next_hop_in_ip_address = each.value.route.next_hop_type == "VirtualAppliance" ? each.value.route.next_hop_ip_address : null } resource "azurerm_subnet_network_security_group_association" "this" { diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index 2a5b60e0..fc5c4df0 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -3,40 +3,46 @@ variable "name" { type = string } -variable "create_virtual_network" { - description = "If true, create the Virtual Network, otherwise just use a pre-existing network." - default = true - type = bool +variable "resource_group_name" { + description = "Name of the Resource Group to use." + type = string } variable "location" { - description = "Location of the resources that will be deployed." + description = "Location of the deployed resources." type = string } variable "tags" { - description = "Map of tags to assign to all of the created resources." + description = "Map of tags to assign to all created resources." default = {} type = map(string) } -variable "resource_group_name" { - description = "Name of the Resource Group to use." - type = string +variable "create_virtual_network" { + description = "Controls Virtual Network creation. If `true`, create the Virtual Network, otherwise just use a pre-existing network." + default = true + nullable = false + type = bool } variable "address_space" { - description = "The address space used by the virtual network. You can supply more than one address space." + description = "The address space used by the virtual network. You can supply more than one address space. Required only when you create a VNET." + default = null type = list(string) + validation { + condition = alltrue([for v in var.address_space : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", v))]) + error_message = "All items in var.address_space should be in CIDR notation, with the maximum subnet of /29." + } } variable "network_security_groups" { description = <<-EOF Map of objects describing Network Security Groups. - List of either required or important properties: + List of available properties: - - `name` - (`string`, required) name of the Network Security Group. + - `name` - (`string`, required) name of the Network Security Group. - `rules` - (`map`, optional) A list of objects representing Network Security Rules. Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: @@ -45,13 +51,13 @@ variable "network_security_groups" { - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule#protocol) + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefixe`) a list of source address prefixes. Tags are not allowed. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source address prefixes. Tags are not allowed. - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. @@ -93,8 +99,8 @@ variable "network_security_groups" { } } }, - "network_security_group_2" = { - rules = {} + "nsg_2" = { + name = "empty-nsg } } ``` @@ -119,22 +125,140 @@ variable "network_security_groups" { destination_address_prefixes = optional(list(string)) })), {}) })) + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + rule.priority >= 100 && rule.priority <= 4096 + ] + ])) + error_message = "The `priority` should be a value between 100 and 4096." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + contains(["Inbound", "Outbound"], rule.direction) + ] + ])) + error_message = "The `direction` property should be one of Inbound or Outbound." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + contains(["Allow", "Deny"], rule.access) + ] + ])) + error_message = "The `access` property should be one of Allow or Deny." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + contains(["Tcp", "Udp", "Icmp", "*"], rule.protocol) + ] + ])) + error_message = "The `protocol` property should be one of Tcp, Udp, Icmp or *." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + rule.source_port_range == null ? true : can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.source_port_range)) + ] + ])) + error_message = "The `source_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + for _, range in(rule.source_port_ranges != null ? rule.source_port_ranges : []) : + can(regex("^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", range)) + ] + ] + ])) + error_message = "The `source_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + rule.destination_port_range == null ? true : can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.destination_port_range)) + ] + ])) + error_message = "The `destination_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + for _, range in(rule.destination_port_ranges != null ? rule.destination_port_ranges : []) : + can(regex("^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", range)) + ] + ] + ])) + error_message = "The `destination_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + rule.source_address_prefix != null ? can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.source_address_prefix)) : true + ] + ] + ])) + error_message = "The `source_address_prefix` can be either '*', a CIDR or an Azure Service Tag." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + for _, prefix in(rule.source_address_prefixes != null ? rule.source_address_prefixes : []) : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", prefix)) + ] + ] + ])) + error_message = "The `source_address_prefixes` can be a list of CIDRs." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + rule.destination_address_prefix != null ? can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.destination_address_prefix)) : true + ] + ] + ])) + error_message = "The `destination_address_prefix` can be either '*', a CIDR or an Azure Service Tag." + } + validation { + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : [ + for _, prefix in(rule.destination_address_prefixes != null ? rule.destination_address_prefixes : []) : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", prefix)) + ] + ] + ])) + error_message = "The `destination_address_prefixes` can be a list of CIDRs." + } } variable "route_tables" { description = <<-EOF Map of objects describing a Route Tables. - List of either required or important properties: + List of available properties: - - `name` - (`string`, required) name of a Route Table. - - `routes` - (`map`, required) a map of Route Table entries (UDRs): + - `name` - (`string`, required) name of a Route Table. + - `routes` - (`map`, required) a map of Route Table entries (UDRs): - `name` - (`string`, required) a name of a UDR. - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. - Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. - - `next_hop_in_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. - Next hop values are only allowed in routes where the next hop type is `VirtualAppliance`. + Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. + - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. + Used only when `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. Example: ```hcl @@ -143,12 +267,14 @@ variable "route_tables" { name = "route_table_1" routes = { "route_1" = { + name = "route-1" address_prefix = "10.1.0.0/16" - next_hop_type = "vnetlocal" + next_hop_type = "VnetLocal" }, "route_2" = { + name = "route-2" address_prefix = "10.2.0.0/16" - next_hop_type = "vnetlocal" + next_hop_type = "VnetLocal" }, } }, @@ -156,9 +282,10 @@ variable "route_tables" { name = "route_table_2" routes = { "route_3" = { + name = "default-nva-route" address_prefix = "0.0.0.0/0" next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.112.0.100" + next_hop_ip_address = "10.112.0.100" } }, }, @@ -170,16 +297,52 @@ variable "route_tables" { type = map(object({ name = string routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_in_ip_address = optional(string) + name = string + address_prefix = string + next_hop_type = string + next_hop_ip_address = optional(string) })) })) + validation { + condition = alltrue(flatten([ + for _, rt in var.route_tables : [ + for _, udr in rt.routes : [ + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", udr.address_prefix)) + ] + ] + ])) + error_message = "The `address_prefix` should be in CIDR notation." + } + validation { + condition = alltrue(flatten([ + for _, rt in var.route_tables : [ + for _, udr in rt.routes : can(udr.next_hop_type) ? contains(["VirtualNetworkGateway", "VnetLocal", "Internet", "VirtualAppliance", "None"], udr.next_hop_type) : true + ] + ])) + error_message = "The `next_hop_type` route property should have value of either: \"VirtualNetworkGateway\", \"VnetLocal\", \"Internet\", \"VirtualAppliance\" or \"None\"." + } + validation { + condition = alltrue(flatten([ + for _, rt in var.route_tables : [ + for _, udr in rt.routes : [ + udr.next_hop_ip_address != null ? can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", udr.next_hop_ip_address)) : true + ] + ] + ])) + error_message = "The `next_hop_ip_address` should be a valid IPv4 address." + } } variable "create_subnets" { - description = "If true, create the Subnets inside the Virtual Network, otherwise use a pre-existing subnets." + description = <<-EOF + Controls subnet creation. + + Possible variants: + + - `true` - create subnets described in `var.subnets` + - `false` - source subnets described in `var.subnets` + - `false` and `var.subnets` is empty - skip subnets management. + EOF default = true nullable = false type = bool @@ -187,17 +350,19 @@ variable "create_subnets" { variable "subnets" { description = <<-EOF - Map of objects describing subnets to create within a virtual network. + Map of objects describing subnets to manage. - By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. + By the default the described subnets will be created. + If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. List of available attributes of each subnet entry: - `name` - (`string`, required) name of a subnet. - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. - - `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. - - `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. - - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. + - `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. + - `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. + - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. + This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. Example: ```hcl @@ -205,22 +370,18 @@ variable "subnets" { "management" = { name = "management-snet" address_prefixes = ["10.100.0.0/24"] - network_security_group = "network_security_group_1" - route_table = "route_table_1" + network_security_group_key = "network_security_group_1" enable_storage_service_endpoint = true }, "private" = { - name = "private-snet" - address_prefixes = ["10.100.1.0/24"] - network_security_group = "network_security_group_2" - route_table = "route_table_2" + name = "private-snet" + address_prefixes = ["10.100.1.0/24"] + route_table_key = "route_table_2" }, "public" = { - name = "public-snet" - address_prefixes = ["10.100.2.0/24"] - network_security_group = "network_security_group_3" - route_table = "route_table_3" - }, + name = "public-snet" + address_prefixes = ["10.100.2.0/24"] + } } ``` EOF @@ -233,4 +394,13 @@ variable "subnets" { route_table_key = optional(string) enable_storage_service_endpoint = optional(bool, false) })) + validation { + condition = alltrue(flatten([ + for _, snet in var.subnets : [ + for _, cidr in snet.address_prefixes : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", cidr)) + ] + ])) + error_message = "The `address_prefixes` should be list of CIDR blocks, with the maximum subnet of /29." + } } From 5601617c76e759fca13d82fd0c526377b6eb6de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Fri, 3 Nov 2023 12:51:03 +0100 Subject: [PATCH 07/49] refactor(module/vnet): documentation update and validation fixes (#351) --- examples/common_vmseries/main.tf | 2 +- examples/common_vmseries/variables.tf | 26 +- .../common_vmseries_and_autoscale/main.tf | 2 +- .../variables.tf | 26 +- examples/dedicated_vmseries/main.tf | 2 +- examples/dedicated_vmseries/variables.tf | 26 +- .../dedicated_vmseries_and_autoscale/main.tf | 2 +- .../variables.tf | 26 +- examples/gwlb_with_vmseries/main.tf | 2 +- examples/gwlb_with_vmseries/variables.tf | 26 +- examples/standalone_panorama/main.tf | 2 +- examples/standalone_panorama/variables.tf | 26 +- examples/standalone_vmseries/main.tf | 3 +- examples/standalone_vmseries/variables.tf | 26 +- examples/vnet/brownfield/main.tf | 37 +++ examples/vnet/example.tfvars | 50 +++- examples/vnet/main.tf | 2 +- examples/vnet/variables.tf | 44 ++-- modules/vnet/README.md | 98 +++++--- modules/vnet/main.tf | 7 + modules/vnet/variables.tf | 225 +++++++++++++----- 21 files changed, 468 insertions(+), 192 deletions(-) create mode 100644 examples/vnet/brownfield/main.tf diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 77df6a95..458c8e0e 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -45,7 +45,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 3ab1d4f6..8362318e 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index f196086a..d7538611 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -39,7 +39,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 64821d3b..77e07d31 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 295e249d..a6beab35 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -45,7 +45,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 5f2929ee..ee45008b 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index f196086a..d7538611 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -39,7 +39,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 64821d3b..77e07d31 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 8cf983c8..cb583e92 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -31,7 +31,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index bf006d10..396de21e 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -43,16 +43,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index b6df3d77..cf455633 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -38,7 +38,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index ea5e0f8a..33b9cf5f 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index a01f8cdc..458c8e0e 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -45,7 +45,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location @@ -65,6 +65,7 @@ module "vnet" { tags = var.tags } + module "natgw" { source = "../../modules/natgw" diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index a60942fa..e6e3f6c0 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -55,16 +55,22 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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({ diff --git a/examples/vnet/brownfield/main.tf b/examples/vnet/brownfield/main.tf new file mode 100644 index 00000000..cbca165a --- /dev/null +++ b/examples/vnet/brownfield/main.tf @@ -0,0 +1,37 @@ +terraform { + required_version = ">= 1.3, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + } +} + +provider "azurerm" { + features { + } +} + +resource "azurerm_resource_group" "this" { + name = "fosix-vnet-brownfield" + location = "North Europe" +} + +resource "azurerm_virtual_network" "this" { + name = "fosix-brownfield-vnet" + location = azurerm_resource_group.this.location + resource_group_name = azurerm_resource_group.this.name + address_space = ["10.0.0.0/8"] +} + +resource "azurerm_subnet" "this" { + for_each = { + one-snet = "10.0.0.0/24" + two-snet = "10.0.1.0/24" + } + + name = each.key + resource_group_name = azurerm_resource_group.this.name + virtual_network_name = azurerm_virtual_network.this.name + address_prefixes = [each.value] +} \ No newline at end of file diff --git a/examples/vnet/example.tfvars b/examples/vnet/example.tfvars index ba36981b..91882a5f 100644 --- a/examples/vnet/example.tfvars +++ b/examples/vnet/example.tfvars @@ -10,6 +10,16 @@ tags = { # --- VNET PART --- # vnets = { + brownfield = { + name = "fosix-brownfield-vnet" + resource_group_name = "fosix-vnet-brownfield" + create_virtual_network = false + create_subnets = false + subnets = { + "a" = { name = "one-snet" } + "b" = { name = "two-snet" } + } + } empty = { name = "empty" address_space = ["10.0.0.0/29"] @@ -35,6 +45,23 @@ vnets = { } } } + "nsg2" = { + name = "nsg2" + 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 = "*" + source_port_ranges = ["33", "44-55"] + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } } route_tables = { "rt" = { @@ -42,7 +69,22 @@ vnets = { routes = { "udr" = { name = "udr" - address_prefix = "10.0.0.0/8" + address_prefix = "10.0.0.0/24" + next_hop_type = "None" + } + "udrka" = { + name = "udrb" + address_prefix = "10.0.1.0/24" + next_hop_type = "None" + } + } + } + "rtb" = { + name = "b_udr" + routes = { + "udr" = { + name = "udr" + address_prefix = "0.0.0.0/0" next_hop_type = "None" } } @@ -55,6 +97,12 @@ vnets = { network_security_group_key = "nsg" route_table_key = "rt" } + "some_other_subnet" = { + name = "some-other-subnet" + address_prefixes = ["10.0.1.0/25"] + network_security_group_key = "nsg" + route_table_key = "rt" + } } } } diff --git a/examples/vnet/main.tf b/examples/vnet/main.tf index 46c34da1..16e2d91e 100644 --- a/examples/vnet/main.tf +++ b/examples/vnet/main.tf @@ -22,7 +22,7 @@ module "vnet" { for_each = var.vnets - name = "${var.name_prefix}${each.value.name}" + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf index e92c6532..066f2247 100644 --- a/examples/vnet/variables.tf +++ b/examples/vnet/variables.tf @@ -48,26 +48,31 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - location = optional(string) + name = string rules = optional(map(object({ name = string priority = number @@ -85,13 +90,12 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string - location = optional(string) + name = string routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_in_ip_address = optional(string) + name = string + address_prefix = string + next_hop_type = string + next_hop_ip_address = optional(string) })) })), {}) create_subnets = optional(bool, true) diff --git a/modules/vnet/README.md b/modules/vnet/README.md index b6e9b733..b6efe63d 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -127,15 +127,15 @@ This module is designed to work in several *modes* depending on which variables Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Network. -[`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group to use. -[`location`](#location) | `string` | Location of the deployed resources. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. ## Module's Optional Inputs Name | Type | Description --- | --- | --- -[`tags`](#tags) | `map` | Map of tags to assign to all created resources. +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`create_virtual_network`](#create_virtual_network) | `bool` | Controls Virtual Network creation. [`address_space`](#address_space) | `list` | The address space used by the virtual network. [`network_security_groups`](#network_security_groups) | `map` | Map of objects describing Network Security Groups. @@ -200,7 +200,7 @@ Type: string #### resource_group_name -Name of the Resource Group to use. +The name of the Resource Group to use. Type: string @@ -208,7 +208,7 @@ Type: string #### location -Location of the deployed resources. +The name of the Azure region to deploy the resources in. Type: string @@ -231,7 +231,7 @@ Type: string #### tags -Map of tags to assign to all created resources. +The map of tags to assign to all created resources. Type: map(string) @@ -241,7 +241,10 @@ Default value: `map[]` #### create_virtual_network -Controls Virtual Network creation. If `true`, create the Virtual Network, otherwise just use a pre-existing network. +Controls Virtual Network creation. + +When set to `true`, creates the Virtual Network, otherwise just use a pre-existing network. + Type: bool @@ -251,7 +254,10 @@ Default value: `true` #### address_space -The address space used by the virtual network. You can supply more than one address space. Required only when you create a VNET. +The address space used by the virtual network. + +You can supply more than one address space. Required only when you create a VNET. + Type: list(string) @@ -266,23 +272,44 @@ Map of objects describing Network Security Groups. List of available properties: - `name` - (`string`, required) name of the Network Security Group. -- `rules` - (`map`, optional) A list of objects representing Network Security Rules. +- `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. - Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: + > [!NOTE] + > All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, + > example: `21-23`. + + Following attributes are available: - `name` - (`string`, required) name of the rule - - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. - - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. - - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. - - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. - - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. - - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. - - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source address prefixes. Tags are not allowed. - - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 + and must be unique for each rule in the collection. The lower the priority number, + the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming + or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. + Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include + `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the + [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or + a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports + or ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP + range or `*` to match any IP. This can also be a tag. To see all available tags for a + region use the following command (example for US West Central): + `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source + address prefixes. Tags are not allowed. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port + or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of + destination ports or a ranges of ports. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination + CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` + for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of + destination address prefixes. Tags are not allowed. Example: ```hcl @@ -291,33 +318,36 @@ Example: name = "network_security_group_1" rules = { "AllOutbound" = { + name = = "DefaultOutbound" priority = 100 direction = "Outbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "*" source_address_prefix = "*" + destination_port_range = "*" destination_address_prefix = "*" }, "AllowSSH" = { + name = "InboundSSH" priority = 200 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "22" source_address_prefix = "*" + destination_port_range = "22" destination_address_prefix = "*" }, "AllowWebBrowsing" = { + name = "InboundWeb" priority = 300 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_ranges = ["80","443"] source_address_prefix = "*" + destination_port_ranges = ["80","443"] destination_address_prefix = "VirtualNetwork" } } @@ -342,10 +372,10 @@ map(object({ 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_port_range = optional(string) + destination_port_ranges = optional(list(string)) destination_address_prefix = optional(string) destination_address_prefixes = optional(list(string)) })), {}) @@ -451,11 +481,15 @@ If however `create_subnets` is set to `false` this is just a mapping between the List of available attributes of each subnet entry: - `name` - (`string`, required) name of a subnet. -- `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. -- `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. -- `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. -- `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. - This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. +- `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within + VNET's address space to assign to a created subnet. +- `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in + `network_security_groups` that should be assigned to this subnet. +- `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in + `route_tables` that should be assigned to this subnet. +- `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service + endpoint on a subnet. This is a suggested setting for the management interface when full + bootstrapping using an Azure Storage Account is used. Example: ```hcl diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index 019acf48..e6e612ed 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -6,6 +6,13 @@ resource "azurerm_virtual_network" "this" { resource_group_name = var.resource_group_name address_space = var.address_space tags = var.tags + + lifecycle { + precondition { + condition = length(coalesce(var.address_space, [])) > 0 + error_message = "The `var.address_space` property is required when creating a VNET." + } + } } data "azurerm_virtual_network" "this" { diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index fc5c4df0..f3ddedfc 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -4,34 +4,45 @@ variable "name" { } variable "resource_group_name" { - description = "Name of the Resource Group to use." + description = "The name of the Resource Group to use." type = string } variable "location" { - description = "Location of the deployed resources." + description = "The name of the Azure region to deploy the resources in." type = string } variable "tags" { - description = "Map of tags to assign to all created resources." + description = "The map of tags to assign to all created resources." default = {} type = map(string) } variable "create_virtual_network" { - description = "Controls Virtual Network creation. If `true`, create the Virtual Network, otherwise just use a pre-existing network." + description = <<-EOF + Controls Virtual Network creation. + + When set to `true`, creates the Virtual Network, otherwise just use a pre-existing network. + EOF default = true nullable = false type = bool } variable "address_space" { - description = "The address space used by the virtual network. You can supply more than one address space. Required only when you create a VNET." + description = <<-EOF + The address space used by the virtual network. + + You can supply more than one address space. Required only when you create a VNET. + EOF default = null type = list(string) validation { - condition = alltrue([for v in var.address_space : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", v))]) + condition = alltrue([ + for v in coalesce(var.address_space, []) : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", v)) + ]) error_message = "All items in var.address_space should be in CIDR notation, with the maximum subnet of /29." } } @@ -43,23 +54,44 @@ variable "network_security_groups" { List of available properties: - `name` - (`string`, required) name of the Network Security Group. - - `rules` - (`map`, optional) A list of objects representing Network Security Rules. + - `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. - Notice, all port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, example: `21-23`. Following attributes are available: + > [!NOTE] + > All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, + > example: `21-23`. + + Following attributes are available: - `name` - (`string`, required) name of the rule - - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 and must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule. - - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are `Inbound` and `Outbound`. - - `access` - (`string`, required) specifies whether network traffic is allowed or denied. Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or a range of ports. This can also be an `*` to match all. - - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports or ranges of ports. - - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port or a range of ports. This can also be an `*` to match all. - - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of destination ports or a ranges of ports. - - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP range or `*` to match any IP. This can also be a tag. To see all available tags for a region use the following command (example for US West Central): `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source address prefixes. Tags are not allowed. - - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` for details. - - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of destination address prefixes. Tags are not allowed. + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 + and must be unique for each rule in the collection. The lower the priority number, + the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming + or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. + Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include + `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the + [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or + a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports + or ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP + range or `*` to match any IP. This can also be a tag. To see all available tags for a + region use the following command (example for US West Central): + `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source + address prefixes. Tags are not allowed. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port + or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of + destination ports or a ranges of ports. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination + CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` + for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of + destination address prefixes. Tags are not allowed. Example: ```hcl @@ -68,33 +100,36 @@ variable "network_security_groups" { name = "network_security_group_1" rules = { "AllOutbound" = { + name = = "DefaultOutbound" priority = 100 direction = "Outbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "*" source_address_prefix = "*" + destination_port_range = "*" destination_address_prefix = "*" }, "AllowSSH" = { + name = "InboundSSH" priority = 200 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_range = "22" source_address_prefix = "*" + destination_port_range = "22" destination_address_prefix = "*" }, "AllowWebBrowsing" = { + name = "InboundWeb" priority = 300 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" - destination_port_ranges = ["80","443"] source_address_prefix = "*" + destination_port_ranges = ["80","443"] destination_address_prefix = "VirtualNetwork" } } @@ -117,15 +152,26 @@ variable "network_security_groups" { 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_port_range = optional(string) + destination_port_ranges = optional(list(string)) destination_address_prefix = optional(string) destination_address_prefixes = optional(list(string)) })), {}) })) - validation { + validation { # name + condition = length([for _, v in var.network_security_groups : v.name]) == length(distinct([for _, v in var.network_security_groups : v.name])) + error_message = "The `name` property has to be unique." + } + validation { # rule.name + condition = alltrue([ + for _, nsg in var.network_security_groups : + length([for _, rule in nsg.rules : rule.name]) == length(distinct([for _, rule in nsg.rules : rule.name])) + ]) + error_message = "The `rule.name` property has to be unique in a particular NSG." + } + validation { # rule.priority condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -134,7 +180,7 @@ variable "network_security_groups" { ])) error_message = "The `priority` should be a value between 100 and 4096." } - validation { + validation { # rule.direction condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -143,7 +189,7 @@ variable "network_security_groups" { ])) error_message = "The `direction` property should be one of Inbound or Outbound." } - validation { + validation { # rule.access condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -152,7 +198,7 @@ variable "network_security_groups" { ])) error_message = "The `access` property should be one of Allow or Deny." } - validation { + validation { # rule.protocol condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -161,82 +207,120 @@ variable "network_security_groups" { ])) error_message = "The `protocol` property should be one of Tcp, Udp, Icmp or *." } - validation { + validation { # rule.source_port_range(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - rule.source_port_range == null ? true : can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.source_port_range)) + (rule.source_port_range == null || rule.source_port_ranges == null) && !(rule.source_port_range == null && rule.source_port_ranges == null) + ] + ])) + error_message = "The `source_port_range` and `source_port_ranges` properties are required but mutually exclusive." + } + validation { # rule.source_port_range + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.source_port_range)) + if rule.source_port_range != null ] ])) error_message = "The `source_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." } - validation { + validation { # rule.source_port_ranges condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ - for _, range in(rule.source_port_ranges != null ? rule.source_port_ranges : []) : + for _, range in coalesce(rule.source_port_ranges, []) : can(regex("^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", range)) ] ] ])) error_message = "The `source_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." } - validation { + validation { # rule.destination_port_range(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - rule.destination_port_range == null ? true : can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.destination_port_range)) + (rule.destination_port_range == null || rule.destination_port_ranges == null) && !(rule.destination_port_range == null && rule.destination_port_ranges == null) + ] + ])) + error_message = "The `destination_port_range` and `destination_port_ranges` properties are required but mutually exclusive." + } + validation { # rule.destination_port_range + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + can(regex("^\\*$|^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", rule.destination_port_range)) + if rule.destination_port_range != null ] ])) error_message = "The `destination_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." } - validation { + validation { # rule.destination_port_ranges condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ - for _, range in(rule.destination_port_ranges != null ? rule.destination_port_ranges : []) : + for _, range in coalesce(rule.destination_port_ranges, []) : can(regex("^\\d{1,4}[0-5]?(\\-\\d{1,4}[0-5])?$", range)) ] ] ])) error_message = "The `destination_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." } - validation { + validation { # rule.source_address_prefix(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ - for _, rule in nsg.rules : [ - rule.source_address_prefix != null ? can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.source_address_prefix)) : true - ] + for _, rule in nsg.rules : + (rule.source_address_prefix == null || rule.source_address_prefixes == null) && !(rule.source_address_prefix == null && rule.source_address_prefixes == null) + ] + ])) + error_message = "The `source_address_prefixes` and `source_address_prefixes` properties are required but mutually exclusive." + } + validation { # rule.source_address_prefix + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.source_address_prefix)) + if rule.source_address_prefix != null ] ])) error_message = "The `source_address_prefix` can be either '*', a CIDR or an Azure Service Tag." } - validation { + validation { # rule.source_address_prefixes condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ - for _, prefix in(rule.source_address_prefixes != null ? rule.source_address_prefixes : []) : + for _, prefix in coalesce(rule.source_address_prefixes, []) : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", prefix)) ] ] ])) error_message = "The `source_address_prefixes` can be a list of CIDRs." } - validation { + validation { # rule.destination_address_prefix(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ - for _, rule in nsg.rules : [ - rule.destination_address_prefix != null ? can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.destination_address_prefix)) : true - ] + for _, rule in nsg.rules : + (rule.destination_address_prefix == null || rule.destination_address_prefixes == null) && !(rule.destination_address_prefix == null && rule.destination_address_prefixes == null) + ] + ])) + error_message = "The `destination_address_prefix` and `destination_address_prefixes` properties are required but mutually exclusive." + } + validation { # rule.destination_address_prefix + condition = alltrue(flatten([ + for _, nsg in var.network_security_groups : [ + for _, rule in nsg.rules : + can(regex("^\\*$|^[A-Za-z]+$|^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", rule.destination_address_prefix)) + if rule.destination_address_prefix != null ] ])) error_message = "The `destination_address_prefix` can be either '*', a CIDR or an Azure Service Tag." } - validation { + validation { # rule.destination_address_prefixes condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ - for _, prefix in(rule.destination_address_prefixes != null ? rule.destination_address_prefixes : []) : + for _, prefix in coalesce(rule.destination_address_prefixes, []) : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}(\\/[12]?[0-9]|\\/3[0-2])?$", prefix)) ] ] @@ -303,7 +387,18 @@ variable "route_tables" { next_hop_ip_address = optional(string) })) })) - validation { + validation { # name + condition = length([for _, v in var.route_tables : v.name]) == length(distinct([for _, v in var.route_tables : v.name])) + error_message = "The `name` property has to be unique." + } + validation { # route.name + condition = alltrue([ + for _, rt in var.route_tables : + length([for _, udr in rt.routes : udr.name]) == length(distinct([for _, udr in rt.routes : udr.name])) + ]) + error_message = "The `rule.name` property has to be unique in a particular NSG." + } + validation { # route.address_prefix condition = alltrue(flatten([ for _, rt in var.route_tables : [ for _, udr in rt.routes : [ @@ -313,7 +408,7 @@ variable "route_tables" { ])) error_message = "The `address_prefix` should be in CIDR notation." } - validation { + validation { # route.next_hop_type condition = alltrue(flatten([ for _, rt in var.route_tables : [ for _, udr in rt.routes : can(udr.next_hop_type) ? contains(["VirtualNetworkGateway", "VnetLocal", "Internet", "VirtualAppliance", "None"], udr.next_hop_type) : true @@ -321,12 +416,12 @@ variable "route_tables" { ])) error_message = "The `next_hop_type` route property should have value of either: \"VirtualNetworkGateway\", \"VnetLocal\", \"Internet\", \"VirtualAppliance\" or \"None\"." } - validation { + validation { # route.next_hop_ip_address condition = alltrue(flatten([ for _, rt in var.route_tables : [ - for _, udr in rt.routes : [ - udr.next_hop_ip_address != null ? can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", udr.next_hop_ip_address)) : true - ] + for _, udr in rt.routes : + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", udr.next_hop_ip_address)) + if udr.next_hop_ip_address != null ] ])) error_message = "The `next_hop_ip_address` should be a valid IPv4 address." @@ -358,11 +453,15 @@ variable "subnets" { List of available attributes of each subnet entry: - `name` - (`string`, required) name of a subnet. - - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within VNET's address space to assign to a created subnet. - - `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in `network_security_groups` that should be assigned to this subnet. - - `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in `route_tables` that should be assigned to this subnet. - - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service endpoint on a subnet. - This is a suggested setting for the management interface when full bootstrapping using an Azure Storage Account is used. + - `address_prefixes` - (`list(string)`, required when `create_subnets = true`) a list of address prefixes within + VNET's address space to assign to a created subnet. + - `network_security_group_key` - (`string`, optional, defaults to `null`) a key identifying an NSG defined in + `network_security_groups` that should be assigned to this subnet. + - `route_table_key` - (`string`, optional, defaults to `null`) a key identifying a Route Table defined in + `route_tables` that should be assigned to this subnet. + - `enable_storage_service_endpoint` - (`bool`, optional, defaults to `false`) a flag that enables `Microsoft.Storage` service + endpoint on a subnet. This is a suggested setting for the management interface when full + bootstrapping using an Azure Storage Account is used. Example: ```hcl @@ -394,7 +493,11 @@ variable "subnets" { route_table_key = optional(string) enable_storage_service_endpoint = optional(bool, false) })) - validation { + validation { # name + condition = length([for _, v in var.subnets : v.name]) == length(distinct([for _, v in var.subnets : v.name])) + error_message = "The `name` property has to be unique." + } + validation { # subnet.address_prefixes condition = alltrue(flatten([ for _, snet in var.subnets : [ for _, cidr in snet.address_prefixes : From 60ba7893636d6dc9405c697b022d82090fe9a027 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:19:54 +0100 Subject: [PATCH 08/49] refactor(module/vnet_peering)!: module refactor and adjusted examples (#355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Pawlęga <42772730+FoSix@users.noreply.github.com> --- examples/common_vmseries/main.tf | 4 +- .../common_vmseries_and_autoscale/main.tf | 4 +- examples/dedicated_vmseries/main.tf | 4 +- .../dedicated_vmseries_and_autoscale/main.tf | 4 +- examples/gwlb_with_vmseries/main.tf | 4 +- examples/standalone_panorama/main.tf | 4 +- examples/standalone_vmseries/main.tf | 4 +- examples/test_infrastructure/example.tfvars | 22 +-- examples/test_infrastructure/main.tf | 21 ++- examples/test_infrastructure/variables.tf | 73 ++++++-- examples/vnet/main.tf | 12 +- modules/vnet_peering/.header.md | 21 +++ modules/vnet_peering/README.md | 158 ++++++++++++++---- modules/vnet_peering/main.tf | 24 +-- modules/vnet_peering/variables.tf | 64 ++++--- modules/vnet_peering/versions.tf | 2 +- 16 files changed, 298 insertions(+), 127 deletions(-) create mode 100644 modules/vnet_peering/.header.md diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 458c8e0e..4a84da47 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -53,9 +53,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index d7538611..b9c6d29e 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -47,9 +47,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index a6beab35..914f5532 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -53,9 +53,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index d7538611..b9c6d29e 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -47,9 +47,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index cb583e92..f2c80215 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -39,9 +39,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index cf455633..bec62393 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -46,9 +46,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 458c8e0e..4a84da47 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -53,9 +53,7 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } diff --git a/examples/test_infrastructure/example.tfvars b/examples/test_infrastructure/example.tfvars index cf07db3a..6aa15fb9 100644 --- a/examples/test_infrastructure/example.tfvars +++ b/examples/test_infrastructure/example.tfvars @@ -14,16 +14,17 @@ vnets = { name = "spoke-east" address_space = ["10.100.0.0/25"] # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-dedicated" # TODO: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET + # hub_resource_group_name = "example-transit-vnet-common" # TODO: replace with the name of transit VNET's Resource Group Name + # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET route_tables = { nva = { name = "east2NVA" routes = { "2NVA" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" # TODO: this by default matches the private IP of the private Load Balancer deployed in any of the examples; adjust if needed + name = "2NVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" # TODO: this by default matches the private IP of the private Load Balancer deployed in any of the examples; adjust if needed } } } @@ -44,16 +45,17 @@ vnets = { name = "spoke-west" address_space = ["10.100.1.0/25"] # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-dedicated" # TODO: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET + # hub_resource_group_name = "example-transit-vnet-common" # TODO: replace with the name of transit VNET's Resource Group Name + # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET route_tables = { nva = { name = "west2NVA" routes = { "2NVA" = { - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_in_ip_address = "10.0.0.30" # TODO: replace with IP address of the private Load Balancer in the transit VNET + name = "2NVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" # TODO: replace with IP address of the private Load Balancer in the transit VNET } } } diff --git a/examples/test_infrastructure/main.tf b/examples/test_infrastructure/main.tf index f87ecc1a..8a902444 100644 --- a/examples/test_infrastructure/main.tf +++ b/examples/test_infrastructure/main.tf @@ -38,33 +38,36 @@ module "vnet" { for_each = var.vnets - name = each.value.name - name_prefix = var.name_prefix - create_virtual_network = try(each.value.create_virtual_network, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location - address_space = try(each.value.create_virtual_network, true) ? each.value.address_space : [] + address_space = each.value.address_space - create_subnets = try(each.value.create_subnets, true) + create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = try(each.value.network_security_groups, {}) - route_tables = try(each.value.route_tables, {}) + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } module "vnet_peering" { source = "../../modules/vnet_peering" - for_each = { for k, v in var.vnets : k => v if can(v.hub_vnet_name) } + for_each = { for k, v in var.vnets : k => v if v.hub_vnet_name != null } local_peer_config = { + name = "peer-${each.value.name}-to-${each.value.hub_vnet_name}" resource_group_name = local.resource_group.name vnet_name = "${var.name_prefix}${each.value.name}" } remote_peer_config = { + name = "peer-${each.value.hub_vnet_name}-to-${each.value.name}" resource_group_name = try(each.value.hub_resource_group_name, local.resource_group.name) vnet_name = each.value.hub_vnet_name } diff --git a/examples/test_infrastructure/variables.tf b/examples/test_infrastructure/variables.tf index bb5d4b89..34f73ddd 100644 --- a/examples/test_infrastructure/variables.tf +++ b/examples/test_infrastructure/variables.tf @@ -48,20 +48,67 @@ variable "vnets" { For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) - - `name` : A name of a VNET. - - `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name` - - `address_space` : a list of CIDRs for VNET - - `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside - - `hub_vnet_name` : (default: `null`) name of an existing transit VNET. Setting this value triggers peering between the spoke and the transit VNET - - `hub_resource_group_name`: (default: current RG) name of a Resource Group hosting a transit VNET, when skipped, the local Resource Group will be used - - - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create - - - `network_security_groups` : map of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, optional, 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 = any + + type = map(object({ + name = string + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = optional(string) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 "hub_resource_group_name" { diff --git a/examples/vnet/main.tf b/examples/vnet/main.tf index 16e2d91e..8f805125 100644 --- a/examples/vnet/main.tf +++ b/examples/vnet/main.tf @@ -30,13 +30,15 @@ module "vnet" { address_space = each.value.address_space create_subnets = each.value.create_subnets - subnets = each.value.create_subnets ? { - for k, v in each.value.subnets : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } : each.value.subnets + subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags diff --git a/modules/vnet_peering/.header.md b/modules/vnet_peering/.header.md new file mode 100644 index 00000000..071728f6 --- /dev/null +++ b/modules/vnet_peering/.header.md @@ -0,0 +1,21 @@ +# Palo Alto Networks VNet Peering Module for Azure + +A terraform module for deploying a Virtual Network Peering and its components required for the VM-Series firewalls in Azure. + +## Usage + +Simple usage example: + +```hcl +local_peer_config = { + name = "peer-local_vnet-to-remote_vnet" + resource_group_name = "local_resourcegroup_name" + vnet_name = "local_vnet_name" +} + +remote_peer_config = { + name = "peer-remote_vnet-to-local_vnet" + resource_group_name = "remote_resourcegroup_name" + vnet_name = "remote_vnet_name" +} +``` \ No newline at end of file diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index ed8237e8..23a16161 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -1,53 +1,141 @@ +<!-- BEGIN_TF_DOCS --> # Palo Alto Networks VNet Peering Module for Azure A terraform module for deploying a Virtual Network Peering and its components required for the VM-Series firewalls in Azure. ## Usage -For usage refer to any example module. +Simple usage example: -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements +```hcl +local_peer_config = { + name = "peer-local_vnet-to-remote_vnet" + resource_group_name = "local_resourcegroup_name" + vnet_name = "local_vnet_name" +} -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | +remote_peer_config = { + name = "peer-remote_vnet-to-local_vnet" + resource_group_name = "remote_resourcegroup_name" + vnet_name = "remote_vnet_name" +} +``` -### Providers +## Module's Required Inputs -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | +Name | Type | Description +--- | --- | --- +[`local_peer_config`](#local_peer_config) | `object` | A map that contains the local peer configuration. +[`remote_peer_config`](#remote_peer_config) | `object` | A map that contains the remote peer configuration. -### Modules -No modules. -### Resources -| Name | Type | -|------|------| -| [azurerm_virtual_network_peering.local](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource | -| [azurerm_virtual_network_peering.remote](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource | -| [azurerm_virtual_network.local_peer](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | -| [azurerm_virtual_network.remote_peer](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source | +## Module's Outputs -### Inputs +Name | Description +--- | --- +`local_peering_name` | The name of the local VNET peering. +`remote_peering_name` | The name of the remote VNET peering. +`local_peering_id` | The ID of the local VNET peering. +`remote_peering_id` | The ID of the remote VNET peering. -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix name appended to the peering names. | `string` | `""` | no | -| <a name="input_local_peer_config"></a> [local\_peer\_config](#input\_local\_peer\_config) | A map that contains the local peer configuration.<br>Mandatory Values: <br>- `vnet_name` - (`string`, required) the local peer VNET name.<br>- `resource_group_name - (`string`, required) : the resource group name of the local peer<br>- `allow\_virtual\_network\_access - (`bool`, optional, defaults to `true`) : allows communication between the two peering VNETs<br>- `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) : allows traffic forwarded from the remote VNET but not originated from within it<br>- `allow_gateway_transit` - (`bool`, optional, defaults to `false`) : controls the learning of routes from local VNET (gateway or route server) into the remote VNET. Must be true if `use_remote_gateways` is `true` for remote peer<br>- `use_remote_gateways` - (`bool`, optional, defaults to `false`) : controls the learning of routes from the remote VNET (gateway or route server) into the local VNET<br>- `name` - (`string`, optional, defaults to `<var.name_prefix><var.local_peer_config.vnet_name>-to-<var.remote_peer_config.vnet_name>`) : the name of the local VNET peering | `map(any)` | n/a | yes | -| <a name="input_remote_peer_config"></a> [remote\_peer\_config](#input\_remote\_peer\_config) | A map that contains the remote peer configuration.<br>Mandatory Values :<br>- `vnet_name` - (`string`, required) : the remote peer VNET name.<br>- `resource_group_name - (`string`, required) : the resource group name of the remote peer<br>- `allow\_virtual\_network\_access - (`bool`, optional, defaults to `true`) : allows communication between the two peering VNETs<br>- `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) : allows traffic forwarded from the local VNET but not originated from within it<br>- `allow_gateway_transit` - (`bool`, optional, defaults to `false`) : controls the learning of routes from remote VNET (gateway or route server) into the local VNET. Must be true if `use_remote_gateways` is `true` for local peer<br>- `use_remote_gateways` - (`bool`, optional, defaults to `false`) : controls the learning of routes from the local VNET (gateway or route server) into the remote VNET<br>- `name` - (`string`, optional, defaults to `<var.name_prefix><var.remote_peer_config.vnet_name>-to-<var.local_peer_config.vnet_name>`) : the name of the local VNET peering | `map(any)` | n/a | yes | +## Module's Nameplate -### Outputs -| Name | Description | -|------|-------------| -| <a name="output_local_peering_name"></a> [local\_peering\_name](#output\_local\_peering\_name) | The name of the local VNET peering. | -| <a name="output_remote_peering_name"></a> [remote\_peering\_name](#output\_remote\_peering\_name) | The name of the remote VNET peering. | -| <a name="output_local_peering_id"></a> [local\_peering\_id](#output\_local\_peering\_id) | The ID of the local VNET peering. | -| <a name="output_remote_peering_id"></a> [remote\_peering\_id](#output\_remote\_peering\_id) | The ID of the remote VNET peering. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> \ No newline at end of file +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: + +- `virtual_network_peering` (managed) +- `virtual_network_peering` (managed) +- `virtual_network` (data) +- `virtual_network` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### local_peer_config + +A map that contains the local peer configuration. +Mandatory Values: +- `name` - (`string`, required) the name of the local VNET peering. +- `resource_group_name` - (`string`, required) the resource group name of the local peer. +- `vnet_name` - (`string`, required) the local peer VNET name. +- `allow_virtual_network_access` - (`bool`, optional, defaults to `true`) allows communication between the two peering VNETs. +- `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) allows traffic forwarded from the remote VNET but not + originated from within it. +- `allow_gateway_transit` - (`bool`, optional, defaults to `false`) controls the learning of routes from local VNET + (gateway or route server) into the remote VNET. Must be true if `use_remote_gateways` is + `true` for remote peer. +- `use_remote_gateways` - (`bool`, optional, defaults to `false`) controls the learning of routes from the remote VNET + (gateway or route server) into the local VNET. + + +Type: + +```hcl +object({ + name = string + resource_group_name = string + vnet_name = string + allow_virtual_network_access = optional(bool, true) + allow_forwarded_traffic = optional(bool, true) + allow_gateway_transit = optional(bool, false) + use_remote_gateways = optional(bool, false) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### remote_peer_config + +A map that contains the remote peer configuration. +Mandatory Values: +- `name` - (`string`, required) the name of the remote VNET peering. +- `resource_group_name` - (`string`, required) the resource group name of the remote peer. +- `vnet_name` - (`string`, required) the remote peer VNET name. +- `allow_virtual_network_access` - (`bool`, optional, defaults to `true`) allows communication between the two peering VNETs. +- `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) allows traffic forwarded from the local VNET but not + originated from within it. +- `allow_gateway_transit` - (`bool`, optional, defaults to `false`) controls the learning of routes from remote VNET + (gateway or route server) into the local VNET. Must be true if `use_remote_gateways` is + `true` for remote peer. +- `use_remote_gateways` - (`bool`, optional, defaults to `false`) controls the learning of routes from the local VNET + (gateway or route server) into the remote VNET. + + +Type: + +```hcl +object({ + name = string + resource_group_name = string + vnet_name = string + allow_virtual_network_access = optional(bool, true) + allow_forwarded_traffic = optional(bool, true) + allow_gateway_transit = optional(bool, false) + use_remote_gateways = optional(bool, false) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet_peering/main.tf b/modules/vnet_peering/main.tf index 91c19687..d96db6a1 100644 --- a/modules/vnet_peering/main.tf +++ b/modules/vnet_peering/main.tf @@ -1,31 +1,35 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network data "azurerm_virtual_network" "local_peer" { name = var.local_peer_config.vnet_name resource_group_name = var.local_peer_config.resource_group_name } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network data "azurerm_virtual_network" "remote_peer" { name = var.remote_peer_config.vnet_name resource_group_name = var.remote_peer_config.resource_group_name } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering resource "azurerm_virtual_network_peering" "local" { - name = try(var.local_peer_config.name, "${var.local_peer_config.vnet_name}-to-${var.remote_peer_config.vnet_name}") + name = var.local_peer_config.name resource_group_name = var.local_peer_config.resource_group_name virtual_network_name = var.local_peer_config.vnet_name remote_virtual_network_id = data.azurerm_virtual_network.remote_peer.id - allow_virtual_network_access = try(var.local_peer_config.allow_virtual_network_access, true) - allow_forwarded_traffic = try(var.local_peer_config.allow_forwarded_traffic, true) - allow_gateway_transit = try(var.local_peer_config.allow_gateway_transit, false) - use_remote_gateways = try(var.local_peer_config.use_remote_gateways, false) + allow_virtual_network_access = var.local_peer_config.allow_virtual_network_access + allow_forwarded_traffic = var.local_peer_config.allow_forwarded_traffic + allow_gateway_transit = var.local_peer_config.allow_gateway_transit + use_remote_gateways = var.local_peer_config.use_remote_gateways } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering resource "azurerm_virtual_network_peering" "remote" { - name = try(var.remote_peer_config.name, "${var.remote_peer_config.vnet_name}-to-${var.local_peer_config.vnet_name}") + name = var.remote_peer_config.name resource_group_name = var.remote_peer_config.resource_group_name virtual_network_name = var.remote_peer_config.vnet_name remote_virtual_network_id = data.azurerm_virtual_network.local_peer.id - allow_virtual_network_access = try(var.remote_peer_config.allow_virtual_network_access, true) - allow_forwarded_traffic = try(var.remote_peer_config.allow_forwarded_traffic, true) - allow_gateway_transit = try(var.remote_peer_config.allow_gateway_transit, false) - use_remote_gateways = try(var.remote_peer_config.use_remote_gateways, false) + allow_virtual_network_access = var.remote_peer_config.allow_virtual_network_access + allow_forwarded_traffic = var.remote_peer_config.allow_forwarded_traffic + allow_gateway_transit = var.remote_peer_config.allow_gateway_transit + use_remote_gateways = var.remote_peer_config.use_remote_gateways } \ No newline at end of file diff --git a/modules/vnet_peering/variables.tf b/modules/vnet_peering/variables.tf index 3ea3d060..e0e071a8 100644 --- a/modules/vnet_peering/variables.tf +++ b/modules/vnet_peering/variables.tf @@ -1,35 +1,53 @@ -variable "name_prefix" { - description = "Prefix name appended to the peering names." - default = "" - type = string -} - variable "local_peer_config" { description = <<-EOF A map that contains the local peer configuration. Mandatory Values: - - `vnet_name` - (`string`, required) the local peer VNET name. - - `resource_group_name - (`string`, required) : the resource group name of the local peer - - `allow_virtual_network_access - (`bool`, optional, defaults to `true`) : allows communication between the two peering VNETs - - `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) : allows traffic forwarded from the remote VNET but not originated from within it - - `allow_gateway_transit` - (`bool`, optional, defaults to `false`) : controls the learning of routes from local VNET (gateway or route server) into the remote VNET. Must be true if `use_remote_gateways` is `true` for remote peer - - `use_remote_gateways` - (`bool`, optional, defaults to `false`) : controls the learning of routes from the remote VNET (gateway or route server) into the local VNET - - `name` - (`string`, optional, defaults to `<var.name_prefix><var.local_peer_config.vnet_name>-to-<var.remote_peer_config.vnet_name>`) : the name of the local VNET peering + - `name` - (`string`, required) the name of the local VNET peering. + - `resource_group_name` - (`string`, required) the resource group name of the local peer. + - `vnet_name` - (`string`, required) the local peer VNET name. + - `allow_virtual_network_access` - (`bool`, optional, defaults to `true`) allows communication between the two peering VNETs. + - `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) allows traffic forwarded from the remote VNET but not + originated from within it. + - `allow_gateway_transit` - (`bool`, optional, defaults to `false`) controls the learning of routes from local VNET + (gateway or route server) into the remote VNET. Must be true if `use_remote_gateways` is + `true` for remote peer. + - `use_remote_gateways` - (`bool`, optional, defaults to `false`) controls the learning of routes from the remote VNET + (gateway or route server) into the local VNET. EOF - type = map(any) + type = object({ + name = string + resource_group_name = string + vnet_name = string + allow_virtual_network_access = optional(bool, true) + allow_forwarded_traffic = optional(bool, true) + allow_gateway_transit = optional(bool, false) + use_remote_gateways = optional(bool, false) + }) } variable "remote_peer_config" { description = <<-EOF A map that contains the remote peer configuration. - Mandatory Values : - - `vnet_name` - (`string`, required) : the remote peer VNET name. - - `resource_group_name - (`string`, required) : the resource group name of the remote peer - - `allow_virtual_network_access - (`bool`, optional, defaults to `true`) : allows communication between the two peering VNETs - - `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) : allows traffic forwarded from the local VNET but not originated from within it - - `allow_gateway_transit` - (`bool`, optional, defaults to `false`) : controls the learning of routes from remote VNET (gateway or route server) into the local VNET. Must be true if `use_remote_gateways` is `true` for local peer - - `use_remote_gateways` - (`bool`, optional, defaults to `false`) : controls the learning of routes from the local VNET (gateway or route server) into the remote VNET - - `name` - (`string`, optional, defaults to `<var.name_prefix><var.remote_peer_config.vnet_name>-to-<var.local_peer_config.vnet_name>`) : the name of the local VNET peering + Mandatory Values: + - `name` - (`string`, required) the name of the remote VNET peering. + - `resource_group_name` - (`string`, required) the resource group name of the remote peer. + - `vnet_name` - (`string`, required) the remote peer VNET name. + - `allow_virtual_network_access` - (`bool`, optional, defaults to `true`) allows communication between the two peering VNETs. + - `allow_forwarded_traffic` - (`bool`, optional, defaults to `true`) allows traffic forwarded from the local VNET but not + originated from within it. + - `allow_gateway_transit` - (`bool`, optional, defaults to `false`) controls the learning of routes from remote VNET + (gateway or route server) into the local VNET. Must be true if `use_remote_gateways` is + `true` for remote peer. + - `use_remote_gateways` - (`bool`, optional, defaults to `false`) controls the learning of routes from the local VNET + (gateway or route server) into the remote VNET. EOF - type = map(any) + type = object({ + name = string + resource_group_name = string + vnet_name = string + allow_virtual_network_access = optional(bool, true) + allow_forwarded_traffic = optional(bool, true) + allow_gateway_transit = optional(bool, false) + use_remote_gateways = optional(bool, false) + }) } \ No newline at end of file diff --git a/modules/vnet_peering/versions.tf b/modules/vnet_peering/versions.tf index 7690b8e4..48646533 100644 --- a/modules/vnet_peering/versions.tf +++ b/modules/vnet_peering/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 b97a5b067d577d495895f8bb2380f5d230d60d88 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Tue, 7 Nov 2023 15:34:28 +0100 Subject: [PATCH 09/49] refactor(module/virtual_network_gateway): Refactor module for VNG (#339) --- examples/virtual_network_gateway/.header.md | 5 + examples/virtual_network_gateway/README.md | 297 ++++++ .../virtual_network_gateway/example.tfvars | 127 +++ examples/virtual_network_gateway/main.tf | 77 ++ examples/virtual_network_gateway/outputs.tf | 9 + examples/virtual_network_gateway/variables.tf | 192 ++++ examples/virtual_network_gateway/versions.tf | 22 + modules/virtual_network_gateway/.header.md | 140 +++ modules/virtual_network_gateway/README.md | 854 ++++++++++++++++-- modules/virtual_network_gateway/main.tf | 163 ++-- modules/virtual_network_gateway/variables.tf | 428 ++++++--- modules/virtual_network_gateway/versions.tf | 2 +- 12 files changed, 2049 insertions(+), 267 deletions(-) create mode 100644 examples/virtual_network_gateway/.header.md create mode 100644 examples/virtual_network_gateway/README.md create mode 100644 examples/virtual_network_gateway/example.tfvars create mode 100644 examples/virtual_network_gateway/main.tf create mode 100644 examples/virtual_network_gateway/outputs.tf create mode 100644 examples/virtual_network_gateway/variables.tf create mode 100644 examples/virtual_network_gateway/versions.tf create mode 100644 modules/virtual_network_gateway/.header.md diff --git a/examples/virtual_network_gateway/.header.md b/examples/virtual_network_gateway/.header.md new file mode 100644 index 00000000..24dfd8ae --- /dev/null +++ b/examples/virtual_network_gateway/.header.md @@ -0,0 +1,5 @@ +# 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. diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md new file mode 100644 index 00000000..dd36f8cb --- /dev/null +++ b/examples/virtual_network_gateway/README.md @@ -0,0 +1,297 @@ +<!-- BEGIN_TF_DOCS --> +# 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. +[`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of virtual_network_gateways to create. + + +## 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 Outputs + +Name | Description +--- | --- +`vng_public_ips` | IP Addresses of the VNGs. +`vng_ipsec_policy` | IPsec policy used for Virtual Network Gateway connection + +## 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. +`vng` | - | ../../modules/virtual_network_gateway | Create virtual network gateway + + +Resources used in this module: + +- `resource_group` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### virtual_network_gateways + +Map of virtual_network_gateways to create + +Type: + +```hcl +map(object({ + name = string + zones = optional(list(string)) + type = optional(string) + vpn_type = optional(string) + sku = optional(string) + + active_active = optional(bool) + default_local_network_gateway_id = optional(string) + edge_zone = optional(string) + enable_bgp = optional(bool) + generation = optional(string) + private_ip_address_enabled = optional(bool) + + ip_configuration = list(object({ + name = optional(string) + create_public_ip = bool + public_ip_name = optional(string) + private_ip_address_allocation = optional(string, "Dynamic") + vnet_key = string + subnet_name = string + })) + + vpn_client_configuration = optional(list(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificate = optional(object({ + name = string + public_cert_data = string + })) + revoked_certificate = optional(object({ + name = string + thumbprint = string + })) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })), []) + azure_bgp_peers_addresses = map(string) + local_bgp_settings = object({ + asn = string + peering_addresses = map(object({ + apipa_addresses = list(string) + default_addresses = optional(list(string)) + })) + peer_weight = optional(number) + }) + custom_route = optional(list(object({ + address_prefixes = optional(list(string)) + })), []) + ipsec_shared_key = optional(string) + local_network_gateways = map(object({ + local_ng_name = string + connection_name = string + remote_bgp_settings = optional(list(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + }))) + gateway_address = optional(string) + address_space = optional(list(string)) + custom_bgp_addresses = optional(list(object({ + primary = string + secondary = optional(string) + }))) + })) + connection_mode = optional(string) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars new file mode 100644 index 00000000..88b54484 --- /dev/null +++ b/examples/virtual_network_gateway/example.tfvars @@ -0,0 +1,127 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "vng-example" +name_prefix = "sczech-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + + +# --- VNET PART --- # +vnets = { + transit = { + name = "transit" + address_space = ["10.0.0.0/24"] + network_security_groups = {} + route_tables = { + "rt" = { + name = "rt" + routes = { + "udr" = { + name = "udr" + address_prefix = "10.0.0.0/8" + next_hop_type = "None" + } + } + } + } + subnets = { + "GatewaySubnet" = { + name = "GatewaySubnet" + address_prefixes = ["10.0.0.0/25"] + route_table_key = "rt" + } + } + } +} + +# --- VNG PART --- # +virtual_network_gateways = { + "vng" = { + name = "vng" + type = "Vpn" + sku = "VpnGw2AZ" + generation = "Generation2" + active_active = true + enable_bgp = true + zones = ["1", "2", "3"] + ip_configuration = [ + { + name = "001" + create_public_ip = true + public_ip_name = "pip1" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + }, + { + name = "002" + create_public_ip = true + public_ip_name = "pip2" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + } + ] + ipsec_shared_key = "test123" + azure_bgp_peers_addresses = { + primary_1 = "169.254.21.2" + secondary_1 = "169.254.22.2" + } + local_bgp_settings = { + asn = "65002" + peering_addresses = { + "001" = { + apipa_addresses = ["primary_1"] + }, + "002" = { + apipa_addresses = ["secondary_1"] + } + } + } + local_network_gateways = { + "lg1" = { + local_ng_name = "lg1" + connection_name = "cn1" + gateway_address = "8.8.8.8" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.21.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + }, + "lg2" = { + local_ng_name = "lg2" + connection_name = "cn2" + gateway_address = "4.4.4.4" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.22.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + } + } + connection_mode = "InitiatorOnly" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf new file mode 100644 index 00000000..4bbcdee8 --- /dev/null +++ b/examples/virtual_network_gateway/main.tf @@ -0,0 +1,77 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + network_security_groups = each.value.network_security_groups + route_tables = each.value.route_tables + + tags = var.tags +} + +# Create virtual network gateway +module "vng" { + source = "../../modules/virtual_network_gateway" + + for_each = var.virtual_network_gateways + + location = var.location + resource_group_name = local.resource_group.name + name = each.value.name + zones = each.value.zones + + type = each.value.type + vpn_type = each.value.vpn_type + sku = each.value.sku + + active_active = each.value.active_active + default_local_network_gateway_id = each.value.default_local_network_gateway_id + edge_zone = each.value.edge_zone + enable_bgp = each.value.enable_bgp + generation = each.value.generation + private_ip_address_enabled = each.value.private_ip_address_enabled + + ip_configuration = [ + for ip_configuration in each.value.ip_configuration : + merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) + ] + + vpn_client_configuration = each.value.vpn_client_configuration + azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses + local_bgp_settings = each.value.local_bgp_settings + custom_route = each.value.custom_route + ipsec_shared_key = each.value.ipsec_shared_key + local_network_gateways = each.value.local_network_gateways + connection_mode = each.value.connection_mode + ipsec_policies = each.value.ipsec_policies + + tags = var.tags +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/outputs.tf b/examples/virtual_network_gateway/outputs.tf new file mode 100644 index 00000000..469cc0fd --- /dev/null +++ b/examples/virtual_network_gateway/outputs.tf @@ -0,0 +1,9 @@ +output "vng_public_ips" { + description = "IP Addresses of the VNGs." + value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.public_ip } : null +} + +output "vng_ipsec_policy" { + description = "IPsec policy used for Virtual Network Gateway connection" + value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.ipsec_policy } : null +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf new file mode 100644 index 00000000..9e1d6392 --- /dev/null +++ b/examples/virtual_network_gateway/variables.tf @@ -0,0 +1,192 @@ +### 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) + })), {}) + })) +} + +### Virtual Network Gateway +variable "virtual_network_gateways" { + description = "Map of virtual_network_gateways to create" + type = map(object({ + name = string + zones = optional(list(string)) + type = optional(string) + vpn_type = optional(string) + sku = optional(string) + + active_active = optional(bool) + default_local_network_gateway_id = optional(string) + edge_zone = optional(string) + enable_bgp = optional(bool) + generation = optional(string) + private_ip_address_enabled = optional(bool) + + ip_configuration = list(object({ + name = optional(string) + create_public_ip = bool + public_ip_name = optional(string) + private_ip_address_allocation = optional(string, "Dynamic") + vnet_key = string + subnet_name = string + })) + + vpn_client_configuration = optional(list(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificate = optional(object({ + name = string + public_cert_data = string + })) + revoked_certificate = optional(object({ + name = string + thumbprint = string + })) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })), []) + azure_bgp_peers_addresses = map(string) + local_bgp_settings = object({ + asn = string + peering_addresses = map(object({ + apipa_addresses = list(string) + default_addresses = optional(list(string)) + })) + peer_weight = optional(number) + }) + custom_route = optional(list(object({ + address_prefixes = optional(list(string)) + })), []) + ipsec_shared_key = optional(string) + local_network_gateways = map(object({ + local_ng_name = string + connection_name = string + remote_bgp_settings = optional(list(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + }))) + gateway_address = optional(string) + address_space = optional(list(string)) + custom_bgp_addresses = optional(list(object({ + primary = string + secondary = optional(string) + }))) + })) + connection_mode = optional(string) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + })) +} diff --git a/examples/virtual_network_gateway/versions.tf b/examples/virtual_network_gateway/versions.tf new file mode 100644 index 00000000..95b07f02 --- /dev/null +++ b/examples/virtual_network_gateway/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/modules/virtual_network_gateway/.header.md b/modules/virtual_network_gateway/.header.md new file mode 100644 index 00000000..1568cf58 --- /dev/null +++ b/modules/virtual_network_gateway/.header.md @@ -0,0 +1,140 @@ +# Palo Alto Networks Virtual Network Gateway Module for Azure + +A terraform module for deploying a VNG (Virtual Network Gateway) and its components required for the VM-Series firewalls in Azure. + +## Usage + +In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +Then you can use below code as an example of calling module to create VNG: + +```hcl +module "vng" { + source = "../../modules/virtual_network_gateway" + + for_each = var.virtual_network_gateways + + location = var.location + resource_group_name = local.resource_group.name + name = each.value.name + zones = each.value.avzones + + type = each.value.type + vpn_type = each.value.vpn_type + sku = each.value.sku + + active_active = each.value.active_active + default_local_network_gateway_id = each.value.default_local_network_gateway_id + edge_zone = each.value.edge_zone + enable_bgp = each.value.enable_bgp + generation = each.value.generation + private_ip_address_enabled = each.value.private_ip_address_enabled + + ip_configuration = [ + for ip_configuration in each.value.ip_configuration : + merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) + ] + + vpn_client_configuration = each.value.vpn_client_configuration + azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses + local_bgp_settings = each.value.local_bgp_settings + custom_route = each.value.custom_route + ipsec_shared_key = each.value.ipsec_shared_key + local_network_gateways = each.value.local_network_gateways + connection_mode = each.value.connection_mode + ipsec_policy = each.value.ipsec_policy + + tags = var.tags +} +``` + +Below there are provided sample values for `virtual_network_gateways` map: + +```hcl +virtual_network_gateways = { + "vng" = { + name = "vng" + type = "Vpn" + sku = "VpnGw2" + generation = "Generation2" + active_active = true + enable_bgp = true + ip_configuration = [ + { + name = "001" + create_public_ip = true + public_ip_name = "pip1" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + }, + { + name = "002" + create_public_ip = true + public_ip_name = "pip2" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + } + ] + ipsec_shared_key = "test123" + azure_bgp_peers_addresses = { + primary_1 = "169.254.21.2" + secondary_1 = "169.254.22.2" + } + local_bgp_settings = { + asn = "65002" + peering_addresses = { + "001" = { + apipa_addresses = ["primary_1"] + }, + "002" = { + apipa_addresses = ["secondary_1"] + } + } + } + local_network_gateways = { + "lg1" = { + local_ng_name = "lg1" + connection_name = "cn1" + gateway_address = "8.8.8.8" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.21.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + }, + "lg2" = { + local_ng_name = "lg2" + connection_name = "cn2" + gateway_address = "4.4.4.4" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.22.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + } + } + connection_mode = "InitiatorOnly" + ipsec_policy = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } +} +``` \ No newline at end of file diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index 30d0bfe1..a55ad6e4 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -1,72 +1,782 @@ -# Virtual Network Gateway - -## Purpose - -This module is used to automate deployment of Virtual Network Gateway. - -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_local_network_gateway.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/local_network_gateway) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_virtual_network_gateway.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway) | resource | -| [azurerm_virtual_network_gateway_connection.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway_connection) | resource | -| [azurerm_public_ip.exists](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of a pre-existing Resource Group to place the resources in. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Region to deploy load balancer and dependencies. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix added to all resource names created by this module | `string` | `""` | no | -| <a name="input_name_suffix"></a> [name\_suffix](#input\_name\_suffix) | A suffix added to all resource names created by this module | `string` | `""` | no | -| <a name="input_name"></a> [name](#input\_name) | The name of the Virtual Network Gateway. Changing this forces a new resource to be created | `string` | n/a | yes | -| <a name="input_tags"></a> [tags](#input\_tags) | Azure tags to apply to the created resources. | `map(string)` | `{}` | no | -| <a name="input_enable_zones"></a> [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 | -| <a name="input_avzones"></a> [avzones](#input\_avzones) | After provider version 3.x you need to specify in which availability zone(s) you want to place IP.<br>ie: for zone-redundant with 3 availability zone in current region value will be:<pre>["1","2","3"]</pre> | `list(string)` | `[]` | no | -| <a name="input_type"></a> [type](#input\_type) | The type of the Virtual Network Gateway. Valid options are Vpn or ExpressRoute. Changing the type forces a new resource to be created | `string` | n/a | yes | -| <a name="input_vpn_type"></a> [vpn\_type](#input\_vpn\_type) | The routing type of the Virtual Network Gateway. Valid options are RouteBased or PolicyBased. Defaults to RouteBased. Changing this forces a new resource to be created. | `string` | `"RouteBased"` | no | -| <a name="input_sku"></a> [sku](#input\_sku) | Configuration of the size and capacity of the virtual network gateway. Valid options are Basic, Standard, HighPerformance, UltraPerformance, ErGw1AZ, ErGw2AZ, ErGw3AZ, VpnGw1, VpnGw2, VpnGw3, VpnGw4,VpnGw5, VpnGw1AZ, VpnGw2AZ, VpnGw3AZ,VpnGw4AZ and VpnGw5AZ and depend on the type, vpn\_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway. | `string` | n/a | yes | -| <a name="input_active_active"></a> [active\_active](#input\_active\_active) | If true, an active-active Virtual Network Gateway will be created. An active-active gateway requires a HighPerformance or an UltraPerformance SKU. If false, an active-standby gateway will be created. Defaults to false. | `bool` | `false` | no | -| <a name="input_default_local_network_gateway_id"></a> [default\_local\_network\_gateway\_id](#input\_default\_local\_network\_gateway\_id) | The ID of the local network gateway through which outbound Internet traffic from the virtual network in which the gateway is created will be routed (forced tunnelling) | `string` | n/a | yes | -| <a name="input_edge_zone"></a> [edge\_zone](#input\_edge\_zone) | Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. | `string` | n/a | yes | -| <a name="input_enable_bgp"></a> [enable\_bgp](#input\_enable\_bgp) | If true, BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. Defaults to false | `bool` | `false` | no | -| <a name="input_generation"></a> [generation](#input\_generation) | The Generation of the Virtual Network gateway. Possible values include Generation1, Generation2 or None | `string` | `"Generation1"` | no | -| <a name="input_private_ip_address_enabled"></a> [private\_ip\_address\_enabled](#input\_private\_ip\_address\_enabled) | Should private IP be enabled on this gateway for connections? | `bool` | n/a | yes | -| <a name="input_ip_configuration"></a> [ip\_configuration](#input\_ip\_configuration) | List of IP configurations - every object in the list contains attributes:<br><br>- name - name of the IP configuration<br>- create\_public\_ip - boolean value, true if public IP needs to be created<br>- public\_ip\_name - name of the public IP resource used, when there is no need to create new one<br>- private\_ip\_address\_allocation - defines how the private IP address of the gateways virtual interface is assigned. Valid options are Static or Dynamic. Defaults to Dynamic.<br>- public\_ip\_standard\_sku - defaults to `false`, when set to `true` creates a Standard SKU, statically allocated public IP, otherwise it will be a Basic/Dynamic one.<br>- subnet\_id - the ID of the gateway subnet of a virtual network in which the virtual network gateway will be created.<br><br>Example:<br><br>ip\_configuration = [<br> {<br> name = "001"<br> create\_public\_ip = true<br> subnet\_id = "ID\_for\_subnet\_GatewaySubnet"<br> },<br> {<br> name = "002"<br> create\_public\_ip = true<br> subnet\_id = "ID\_for\_subnet\_GatewaySubnet"<br> }<br>] | `list(any)` | n/a | yes | -| <a name="input_vpn_client_configuration"></a> [vpn\_client\_configuration](#input\_vpn\_client\_configuration) | List of VPN client configurations - every object in the list contains attributes:<br>- address\_space - the address space out of which IP addresses for vpn clients will be taken. You can provide more than one address space, e.g. in CIDR notation.<br>- aad\_tenant - AzureAD Tenant URL<br>- aad\_audience - the client id of the Azure VPN application. See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values<br>- aad\_issuer - the STS url for your tenant<br>- root\_certificate - one or more root\_certificate blocks which are defined below. These root certificates are used to sign the client certificate used by the VPN clients to connect to the gateway.<br>- revoked\_certificate - one or more revoked\_certificate blocks which are defined below.<br>- radius\_server\_address - the address of the Radius server.<br>- radius\_server\_secret - the secret used by the Radius server.<br>- vpn\_client\_protocols - list of the protocols supported by the vpn client. The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with the use of aad\_tenant, aad\_audience and aad\_issuer.<br>- vpn\_auth\_types - list of the vpn authentication types for the virtual network gateway. The supported values are AAD, Radius and Certificate. | `list(any)` | n/a | yes | -| <a name="input_azure_bgp_peers_addresses"></a> [azure\_bgp\_peers\_addresses](#input\_azure\_bgp\_peers\_addresses) | Map of IP addresses used on Azure side for BGP. Map is used to not to duplicate IP address and refer to keys while configuring:<br>- custom\_bgp\_addresses<br>- peering\_addresses in local\_bgp\_settings<br><br>Example:<br><br>azure\_bgp\_peers\_addresses = {<br> primary\_1 = "169.254.21.2"<br> secondary\_1 = "169.254.22.2"<br> primary\_2 = "169.254.21.6"<br> secondary\_2 = "169.254.22.6"<br>} | `map(string)` | n/a | yes | -| <a name="input_local_bgp_settings"></a> [local\_bgp\_settings](#input\_local\_bgp\_settings) | Map of BGP settings:<br>- asn - the Autonomous System Number (ASN) to use as part of the BGP.<br>- peering\_addresses - a map of peering addresses, which contains 1 (for active-standby) or 2 objects (for active-active) with:<br> - key is the ip configuration name<br> - apipa\_addresses is the list of keys for IP addresses defined in variable azure\_bgp\_peers\_addresses<br>- peer\_weight - the weight added to routes which have been learned through BGP peering. Valid values can be between 0 and 100.<br><br>Example:<br><br>local\_bgp\_settings = {<br> asn = "65001"<br> peering\_addresses = {<br> "001" = {<br> apipa\_addresses = ["primary\_1", "primary\_2"]<br> },<br> "002" = {<br> apipa\_addresses = ["secondary\_1", "secondary\_2"]<br> }<br> }<br>} | `any` | n/a | yes | -| <a name="input_custom_route"></a> [custom\_route](#input\_custom\_route) | List of custom routes - every object in the list contains attributes:<br>- address\_prefixes - a list of address blocks reserved for this virtual network in CIDR notation as defined below. | `list(any)` | n/a | yes | -| <a name="input_local_network_gateways"></a> [local\_network\_gateways](#input\_local\_network\_gateways) | Map of local network gateways - every object in the map contains attributes:<br>- name - the name of the local network gateway.<br>- connection - the name of the virtual network gateway connection.<br>- remote\_bgp\_settings - block containing Local Network Gateway's BGP speaker settings:<br> - asn - the BGP speaker's ASN.<br> - bgp\_peering\_address - the BGP peering address and BGP identifier of this BGP speaker.<br> - peer\_weight - the weight added to routes learned from this BGP speaker.<br>- gateway\_address - the gateway IP address to connect with.<br>- address\_space - the list of string CIDRs representing the address spaces the gateway exposes.<br>- custom\_bgp\_addresses - Border Gateway Protocol custom IP Addresses, which can only be used on IPSec / active-active connections. Object contains 2 attributes:<br> - primary - single IP address that is part of the azurerm\_virtual\_network\_gateway ip\_configuration (first one)<br> - secondary - single IP address that is part of the azurerm\_virtual\_network\_gateway ip\_configuration (second one)<br><br>Example:<br><br>local\_network\_gateways = {<br> "lg1" = {<br> name = "001"<br> connection = "001"<br> gateway\_address = "PUBLIC\_IP\_1"<br> remote\_bgp\_settings = [{<br> asn = "65002"<br> bgp\_peering\_address = "169.254.21.1"<br> }]<br> custom\_bgp\_addresses = [<br> {<br> primary = "primary\_1"<br> secondary = "secondary\_1"<br> }<br> ]<br> }<br> "lg2" = {<br> name = "002"<br> connection = "002"<br> gateway\_address = "PUBLIC\_IP\_2"<br> remote\_bgp\_settings = [{<br> asn = "65003"<br> bgp\_peering\_address = "169.254.21.5"<br> }]<br> custom\_bgp\_addresses = [<br> {<br> primary = "primary\_2"<br> secondary = "secondary\_2"<br> }<br> ]<br> }<br> "lg3" = {<br> name = "003"<br> connection = "003"<br> gateway\_address = "PUBLIC\_IP\_3"<br> remote\_bgp\_settings = [{<br> asn = "65002"<br> bgp\_peering\_address = "169.254.22.1"<br> }]<br> custom\_bgp\_addresses = [<br> {<br> primary = "primary\_1"<br> secondary = "secondary\_1"<br> }<br> ]<br> }<br> "lg4" = {<br> name = "004"<br> connection = "004"<br> gateway\_address = "PUBLIC\_IP\_4"<br> remote\_bgp\_settings = [{<br> asn = "65003"<br> bgp\_peering\_address = "169.254.22.5"<br> }]<br> custom\_bgp\_addresses = [<br> {<br> primary = "primary\_2"<br> secondary = "secondary\_2"<br> }<br> ]<br> }<br>} | `any` | n/a | yes | -| <a name="input_ipsec_shared_key"></a> [ipsec\_shared\_key](#input\_ipsec\_shared\_key) | The shared IPSec key. | `string` | n/a | yes | -| <a name="input_connection_mode"></a> [connection\_mode](#input\_connection\_mode) | Connection mode to use. Possible values are Default, InitiatorOnly and ResponderOnly. Defaults to Default. Changing this value will force a resource to be created. | `string` | n/a | yes | -| <a name="input_ipsec_policy"></a> [ipsec\_policy](#input\_ipsec\_policy) | IPsec policy used for Virtual Network Connection with attributes:<br>- dh\_group - The DH group used in IKE phase 1 for initial SA. Valid options are DHGroup1, DHGroup14, DHGroup2, DHGroup2048, DHGroup24, ECP256, ECP384, or None.<br>- ike\_encryption - The IKE encryption algorithm. Valid options are AES128, AES192, AES256, DES, DES3, GCMAES128, or GCMAES256.<br>- ike\_integrity - The IKE integrity algorithm. Valid options are GCMAES128, GCMAES256, MD5, SHA1, SHA256, or SHA384.<br>- ipsec\_encryption - The IPSec encryption algorithm. Valid options are AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES192, GCMAES256, or None.<br>- ipsec\_integrity - The IPSec integrity algorithm. Valid options are GCMAES128, GCMAES192, GCMAES256, MD5, SHA1, or SHA256.<br>- pfs\_group - The DH group used in IKE phase 2 for new child SA. Valid options are ECP256, ECP384, PFS1, PFS14, PFS2, PFS2048, PFS24, PFSMM, or None.<br>- sa\_datasize - The IPSec SA payload size in KB. Must be at least 1024 KB. Defaults to 102400000 KB.<br>- sa\_lifetime - The IPSec SA lifetime in seconds. Must be at least 300 seconds. Defaults to 27000 seconds.<br><br>Example:<br><br>ipsec\_policy = [<br> {<br> dh\_group = "ECP384"<br> ike\_encryption = "AES256"<br> ike\_integrity = "SHA256"<br> ipsec\_encryption = "AES256"<br> ipsec\_integrity = "SHA256"<br> pfs\_group = "ECP384"<br> sa\_datasize = "102400000"<br> sa\_lifetime = "27000"<br> }<br>] | `any` | n/a | yes | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_public_ip"></a> [public\_ip](#output\_public\_ip) | Public IP addresses for Virtual Network Gateway | -| <a name="output_ipsec_policy"></a> [ipsec\_policy](#output\_ipsec\_policy) | IPsec policy used for Virtual Network Gateway connection | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +<!-- BEGIN_TF_DOCS --> +# Palo Alto Networks Virtual Network Gateway Module for Azure + +A terraform module for deploying a VNG (Virtual Network Gateway) and its components required for the VM-Series firewalls in Azure. + +## Usage + +In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +Then you can use below code as an example of calling module to create VNG: + +```hcl +module "vng" { + source = "../../modules/virtual_network_gateway" + + for_each = var.virtual_network_gateways + + location = var.location + resource_group_name = local.resource_group.name + name = each.value.name + zones = each.value.avzones + + type = each.value.type + vpn_type = each.value.vpn_type + sku = each.value.sku + + active_active = each.value.active_active + default_local_network_gateway_id = each.value.default_local_network_gateway_id + edge_zone = each.value.edge_zone + enable_bgp = each.value.enable_bgp + generation = each.value.generation + private_ip_address_enabled = each.value.private_ip_address_enabled + + ip_configuration = [ + for ip_configuration in each.value.ip_configuration : + merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) + ] + + vpn_client_configuration = each.value.vpn_client_configuration + azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses + local_bgp_settings = each.value.local_bgp_settings + custom_route = each.value.custom_route + ipsec_shared_key = each.value.ipsec_shared_key + local_network_gateways = each.value.local_network_gateways + connection_mode = each.value.connection_mode + ipsec_policy = each.value.ipsec_policy + + tags = var.tags +} +``` + +Below there are provided sample values for `virtual_network_gateways` map: + +```hcl +virtual_network_gateways = { + "vng" = { + name = "vng" + type = "Vpn" + sku = "VpnGw2" + generation = "Generation2" + active_active = true + enable_bgp = true + ip_configuration = [ + { + name = "001" + create_public_ip = true + public_ip_name = "pip1" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + }, + { + name = "002" + create_public_ip = true + public_ip_name = "pip2" + vnet_key = "transit" + subnet_name = "GatewaySubnet" + } + ] + ipsec_shared_key = "test123" + azure_bgp_peers_addresses = { + primary_1 = "169.254.21.2" + secondary_1 = "169.254.22.2" + } + local_bgp_settings = { + asn = "65002" + peering_addresses = { + "001" = { + apipa_addresses = ["primary_1"] + }, + "002" = { + apipa_addresses = ["secondary_1"] + } + } + } + local_network_gateways = { + "lg1" = { + local_ng_name = "lg1" + connection_name = "cn1" + gateway_address = "8.8.8.8" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.21.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + }, + "lg2" = { + local_ng_name = "lg2" + connection_name = "cn2" + gateway_address = "4.4.4.4" + remote_bgp_settings = [{ + asn = "65000" + bgp_peering_address = "169.254.22.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + } + } + connection_mode = "InitiatorOnly" + ipsec_policy = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } +} +``` + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Virtual Network Gateway. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`default_local_network_gateway_id`](#default_local_network_gateway_id) | `string` | The ID of the local network gateway. +[`edge_zone`](#edge_zone) | `string` | Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. +[`vpn_client_configuration`](#vpn_client_configuration) | `list` | VPN client configurations (IPSec point-to-site connections). +[`local_bgp_settings`](#local_bgp_settings) | `object` | BGP settings. +[`local_network_gateways`](#local_network_gateways) | `map` | Map of local network gateways. +[`ipsec_shared_key`](#ipsec_shared_key) | `string` | The shared IPSec key. +[`ipsec_policies`](#ipsec_policies) | `list` | IPsec policies used for Virtual Network Connection. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`type`](#type) | `string` | The type of the Virtual Network Gateway. +[`vpn_type`](#vpn_type) | `string` | The routing type of the Virtual Network Gateway. +[`sku`](#sku) | `string` | Configuration of the size and capacity of the virtual network gateway. +[`active_active`](#active_active) | `bool` | Active-active Virtual Network Gateway. +[`enable_bgp`](#enable_bgp) | `bool` | Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. +[`generation`](#generation) | `string` | The Generation of the Virtual Network gateway. +[`private_ip_address_enabled`](#private_ip_address_enabled) | `bool` | Controls whether the private IP is enabled on the gateway. +[`zones`](#zones) | `list` | After provider version 3. +[`ip_configuration`](#ip_configuration) | `list` | IP configurations. +[`azure_bgp_peers_addresses`](#azure_bgp_peers_addresses) | `map` | Map of IP addresses used on Azure side for BGP. +[`custom_route`](#custom_route) | `list` | List of custom routes. +[`connection_type`](#connection_type) | `string` | The type of VNG connection. +[`connection_mode`](#connection_mode) | `string` | The connection mode to use. + + + +## Module's Outputs + +Name | Description +--- | --- +`public_ip` | Public IP addresses for Virtual Network Gateway +`ipsec_policy` | IPsec policy used for Virtual Network Gateway connection + +## 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: + +- `local_network_gateway` (managed) +- `public_ip` (managed) +- `virtual_network_gateway` (managed) +- `virtual_network_gateway_connection` (managed) +- `public_ip` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Virtual Network Gateway. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + +#### default_local_network_gateway_id + +The ID of the local network gateway. + +Outbound Internet traffic from the virtual network, in which the gateway is created, +will be routed through local network gateway(forced tunnelling)" + + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### edge_zone + +Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + +#### vpn_client_configuration + +VPN client configurations (IPSec point-to-site connections). + +List of available attributes of each VPN client configurations: +- `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. + You can provide more than one address space, e.g. in CIDR notation. +- `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL +- `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. + See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values +- `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant +- `root_certificate` - (`object`, optional, defaults to `null`) one or more root_certificate blocks + which are defined below. These root certificates are used to sign the client + certificate used by the VPN clients to connect to the gateway. +- `revoked_certificate` - (`object`, optional, defaults to `null`) one or more revoked_certificate blocks + which are defined below. +- `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. +- `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. +- `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. + The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with + the use of aad_tenant, aad_audience and aad_issuer. +- `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for + the virtual network gateway. The supported values are AAD, Radius and Certificate. + + + +Type: + +```hcl +list(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificate = optional(object({ + name = string + public_cert_data = string + })) + revoked_certificate = optional(object({ + name = string + thumbprint = string + })) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### local_bgp_settings + +BGP settings. + +Attributes: +- `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. +- `peering_addresses` - (`map`, required) a map of peering addresses, which contains 1 (for active-standby) + or 2 objects (for active-active), where key is the ip configuration name and with attributes: + - `apipa_addresses` - (`list`, required) is the list of keys for IP addresses defined in variable azure_bgp_peers_addresses + - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to + the BGP peer of the Virtual Network Gateway. +- `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes + which have been learned through BGP peering. + +Example: + +```hcl +local_bgp_settings = { + asn = "65001" + peering_addresses = { + "001" = { + apipa_addresses = ["primary_1", "primary_2"] + }, + "002" = { + apipa_addresses = ["secondary_1", "secondary_2"] + } + } +} +``` + + +Type: + +```hcl +object({ + asn = string + peering_addresses = map(object({ + apipa_addresses = list(string) + default_addresses = optional(list(string)) + })) + peer_weight = optional(number) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### local_network_gateways + +Map of local network gateways. + +Every object in the map contains attributes: +- local_ng_name - (`string`, required) the name of the local network gateway. +- connection_name - (`string`, required) the name of the virtual network gateway connection. +- remote_bgp_settings - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: + - asn - (`string`, required) the BGP speaker's ASN. + - bgp_peering_address - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. + - peer_weight - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. +- gateway_address - (`string`, optional, defaults to `null`) the gateway IP address to connect with. +- address_space - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces + the gateway exposes. +- custom_bgp_addresses - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, + which can only be used on IPSec / active-active connections. Object contains 2 attributes: + - primary - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway + ip_configuration (first one) + - secondary - (`string`, optional, defaults to `null`) single IP address that is part of + the azurerm_virtual_network_gateway ip_configuration (second one) + +Example: + +```hcl +local_network_gateways = { + "lg1" = { + local_ng_name = "001" + connection_name = "001" + gateway_address = "PUBLIC_IP_1" + remote_bgp_settings = [{ + asn = "65002" + bgp_peering_address = "169.254.21.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + } + "lg2" = { + local_ng_name = "002" + connection_name = "002" + gateway_address = "PUBLIC_IP_2" + remote_bgp_settings = [{ + asn = "65003" + bgp_peering_address = "169.254.21.5" + }] + custom_bgp_addresses = [ + { + primary = "primary_2" + secondary = "secondary_2" + } + ] + } + "lg3" = { + local_ng_name = "003" + connection_name = "003" + gateway_address = "PUBLIC_IP_3" + remote_bgp_settings = [{ + asn = "65002" + bgp_peering_address = "169.254.22.1" + }] + custom_bgp_addresses = [ + { + primary = "primary_1" + secondary = "secondary_1" + } + ] + } + "lg4" = { + local_ng_name = "004" + connection_name = "004" + gateway_address = "PUBLIC_IP_4" + remote_bgp_settings = [{ + asn = "65003" + bgp_peering_address = "169.254.22.5" + }] + custom_bgp_addresses = [ + { + primary = "primary_2" + secondary = "secondary_2" + } + ] + } +} +``` + + +Type: + +```hcl +map(object({ + local_ng_name = string + connection_name = string + remote_bgp_settings = optional(list(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })), []) + gateway_address = optional(string) + address_space = optional(list(string), []) + custom_bgp_addresses = optional(list(object({ + primary = string + secondary = optional(string) + })), []) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### ipsec_shared_key + +The shared IPSec key. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### ipsec_policies + +IPsec policies used for Virtual Network Connection. + +Single policy contains attributes: +- `dh_group` - (`string`, required) The DH group used in IKE phase 1 for initial SA. +- `ike_encryption` - (`string`, required) The IKE encryption algorithm. +- `ike_integrity` - (`string`, required) The IKE integrity algorithm. +- `ipsec_encryption` - (`string`, required) The IPSec encryption algorithm. +- `ipsec_integrity` - (`string`, required) The IPSec integrity algorithm. +- `pfs_group` - (`string`, required) The DH group used in IKE phase 2 for new child SA. +- `sa_datasize` - (`string`, optional, defaults to `102400000`) The IPSec SA payload size in KB. + Must be at least 1024 KB. +- `sa_lifetime` - (`string`, optional, defaults to `27000`) The IPSec SA lifetime in seconds. + Must be at least 300 seconds. + +Example: + +```hcl +ipsec_policy = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "27000" + } +] +``` + + +Type: + +```hcl +list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string, "102400000") + sa_lifetime = optional(string, "27000") + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### type + +The type of the Virtual Network Gateway. + +Type: string + +Default value: `Vpn` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vpn_type + +The routing type of the Virtual Network Gateway. + +Type: string + +Default value: `RouteBased` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### sku + +Configuration of the size and capacity of the virtual network gateway. + +Valid option depends on the type, vpn_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. +Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway. + + +Type: string + +Default value: `Basic` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### active_active + +Active-active Virtual Network Gateway. + +If true, an active-active Virtual Network Gateway will be created. +An active-active gateway requires a HighPerformance or an UltraPerformance SKU. +If false, an active-standby gateway will be created. Defaults to false. + + +Type: bool + +Default value: `false` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### enable_bgp + +Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. + +Type: bool + +Default value: `false` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### generation + +The Generation of the Virtual Network gateway. + +Type: string + +Default value: `Generation1` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### private_ip_address_enabled + +Controls whether the private IP is enabled on the gateway. + +Type: bool + +Default value: `false` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### zones + +After provider version 3.x you need to specify in which availability zone(s) you want to place IP. + +For zone-redundant with 3 availability zones in current region value will be: +```["1","2","3"]``` + + +Type: list(string) + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ip_configuration + +IP configurations. + +List of available attributes of each IP configuration. + +- `name` - (`string`, required) name of the IP configuration +- `create_public_ip` - (`bool`, required) - true if public IP needs to be created +- `public_ip_name` - (`string`, required) name of the public IP resource used, when there is no need + to create new one +- `private_ip_address_allocation` - (`string`, optional, defaults to `Dynamic`) defines how the private IP address of + the gateways virtual interface is assigned. +- `subnet_id` - (`string`, required) the ID of the gateway subnet of a virtual network in which + the virtual network gateway will be created. + +Example: + +```hcl +ip_configuration = [ + { + name = "001" + create_public_ip = true + subnet_id = "ID_for_subnet_GatewaySubnet" + }, + { + name = "002" + create_public_ip = true + subnet_id = "ID_for_subnet_GatewaySubnet" + } +] +``` + + +Type: + +```hcl +list(object({ + name = string + create_public_ip = bool + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") + subnet_id = string + })) +``` + + +Default value: `[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### azure_bgp_peers_addresses + +Map of IP addresses used on Azure side for BGP. + +Map is used to not to duplicate IP address and refer to keys while configuring: +- `custom_bgp_addresses` +- `peering_addresses` in `local_bgp_settings` + +Example: + +```hcl +azure_bgp_peers_addresses = { + primary_1 = "169.254.21.2" + secondary_1 = "169.254.22.2" + primary_2 = "169.254.21.6" + secondary_2 = "169.254.22.6" +} +``` + + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### custom_route + +List of custom routes. + +Every object in the list contains attributes: +- `address_prefixes` - (`list`, optional, defaults to `null`) a list of address blocks reserved for this virtual network in CIDR notation as defined below. + + + +Type: + +```hcl +list(object({ + address_prefixes = optional(list(string)) + })) +``` + + +Default value: `[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### connection_type + +The type of VNG connection. + +Type: string + +Default value: `IPsec` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### connection_mode + +The connection mode to use. + +Type: string + +Default value: `Default` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_network_gateway/main.tf b/modules/virtual_network_gateway/main.tf index 6c86bfb5..ab7bfd67 100644 --- a/modules/virtual_network_gateway/main.tf +++ b/modules/virtual_network_gateway/main.tf @@ -1,27 +1,58 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip +resource "azurerm_public_ip" "this" { + for_each = toset([for ip_configuration in var.ip_configuration : ip_configuration.name if ip_configuration.create_public_ip]) + + resource_group_name = var.resource_group_name + location = var.location + name = each.value + + allocation_method = "Static" + sku = "Standard" + zones = var.zones + + tags = var.tags + + lifecycle { + precondition { + condition = var.active_active ? ( + var.zones != null ? length(var.zones) == 3 : false + ) : true + error_message = "For active-active you need to configure zones" + } + } +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip +data "azurerm_public_ip" "exists" { + for_each = toset([for ip_configuration in var.ip_configuration : ip_configuration.name if !ip_configuration.create_public_ip]) + + name = each.value + resource_group_name = var.resource_group_name +} + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway resource "azurerm_virtual_network_gateway" "this" { location = var.location resource_group_name = var.resource_group_name - name = "${var.name_prefix}vgw${var.name_suffix}-${var.name}" - tags = var.tags + name = var.name - type = try(var.type, "Vpn") - vpn_type = try(var.vpn_type, "RouteBased") - sku = try(var.sku, "Basic") + type = var.type + vpn_type = var.vpn_type + sku = var.sku - active_active = try(var.active_active, false) - default_local_network_gateway_id = try(var.default_local_network_gateway_id, null) - edge_zone = try(var.edge_zone, null) - enable_bgp = try(var.enable_bgp, false) - generation = try(var.generation, null) - private_ip_address_enabled = try(var.private_ip_address_enabled, null) + active_active = var.active_active + default_local_network_gateway_id = var.default_local_network_gateway_id + edge_zone = var.edge_zone + enable_bgp = var.enable_bgp + generation = var.generation + private_ip_address_enabled = var.private_ip_address_enabled dynamic "ip_configuration" { for_each = var.ip_configuration content { name = ip_configuration.value.name - public_ip_address_id = try(ip_configuration.value.create_public_ip, false) ? azurerm_public_ip.this[ip_configuration.value.name].id : try(data.azurerm_public_ip.exists[ip_configuration.value.name].id, null) - private_ip_address_allocation = try(ip_configuration.value.private_ip_address_allocation, "Dynamic") + public_ip_address_id = ip_configuration.value.create_public_ip ? azurerm_public_ip.this[ip_configuration.value.name].id : data.azurerm_public_ip.exists[ip_configuration.value.name].id + private_ip_address_allocation = ip_configuration.value.private_ip_address_allocation subnet_id = ip_configuration.value.subnet_id } } @@ -29,58 +60,60 @@ resource "azurerm_virtual_network_gateway" "this" { dynamic "vpn_client_configuration" { for_each = var.vpn_client_configuration content { - address_space = try(vpn_client_configuration.value.address_space, null) - aad_tenant = try(vpn_client_configuration.value.aad_tenant, null) - aad_audience = try(vpn_client_configuration.value.aad_audience, null) - aad_issuer = try(vpn_client_configuration.value.aad_issuer, null) + address_space = vpn_client_configuration.value.address_space + aad_tenant = vpn_client_configuration.value.aad_tenant + aad_audience = vpn_client_configuration.value.aad_audience + aad_issuer = vpn_client_configuration.value.aad_issuer dynamic "root_certificate" { - for_each = try(vpn_client_configuration.value.root_certificate, null) != null ? { for t in vpn_client_configuration.value.root_certificate : t.name => t } : {} + for_each = coalesce({ for t in vpn_client_configuration.value.root_certificate : t.name => t }, {}) content { name = root_certificate.value.name public_cert_data = root_certificate.value.public_cert_data } } dynamic "revoked_certificate" { - for_each = try(vpn_client_configuration.value.revoked_certificate, null) != null ? { for t in vpn_client_configuration.value.revoked_certificate : t.name => t } : {} + for_each = coalesce({ for t in vpn_client_configuration.value.revoked_certificate : t.name => t }, {}) content { name = revoked_certificate.value.name thumbprint = revoked_certificate.value.thumbprint } } - radius_server_address = try(vpn_client_configuration.value.radius_server_address, null) - radius_server_secret = try(vpn_client_configuration.value.radius_server_secret, null) - vpn_client_protocols = try(vpn_client_configuration.value.vpn_client_protocols, null) - vpn_auth_types = try(vpn_client_configuration.value.vpn_auth_types, null) + radius_server_address = vpn_client_configuration.value.radius_server_address + radius_server_secret = vpn_client_configuration.value.radius_server_secret + vpn_client_protocols = vpn_client_configuration.value.vpn_client_protocols + vpn_auth_types = vpn_client_configuration.value.vpn_auth_types } } dynamic "bgp_settings" { for_each = [var.local_bgp_settings] content { - asn = try(bgp_settings.value.asn, null) + asn = bgp_settings.value.asn dynamic "peering_addresses" { - for_each = try(bgp_settings.value.peering_addresses, {}) + for_each = bgp_settings.value.peering_addresses content { - ip_configuration_name = try(peering_addresses.key, null) - apipa_addresses = [for i in try(peering_addresses.value.apipa_addresses, []) : var.azure_bgp_peers_addresses[i]] - default_addresses = try(peering_addresses.value.default_addresses, null) + ip_configuration_name = peering_addresses.key + apipa_addresses = [for i in peering_addresses.value.apipa_addresses : var.azure_bgp_peers_addresses[i]] + default_addresses = peering_addresses.value.default_addresses } } - peer_weight = try(bgp_settings.value.peer_weight, null) + peer_weight = bgp_settings.value.peer_weight } } dynamic "custom_route" { for_each = var.custom_route content { - address_prefixes = try(custom_route.value.address_prefixes, null) + address_prefixes = custom_route.value.address_prefixes } } + tags = var.tags + lifecycle { precondition { - condition = (contains(["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.sku) && coalesce(var.generation, "Generation1") == "Generation2" - ) || (contains(["Basic", "Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "VpnGw1", "VpnGw1AZ"], var.sku) && coalesce(var.generation, "Generation1") == "Generation1") + condition = (contains(["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.sku) && var.generation == "Generation2" + ) || (contains(["Basic", "Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "VpnGw1", "VpnGw1AZ"], var.sku) && var.generation == "Generation1") error_message = "Generation2 is only value for a sku larger than VpnGw2 or VpnGw2AZ" } precondition { @@ -94,46 +127,22 @@ resource "azurerm_virtual_network_gateway" "this" { } } -# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip -resource "azurerm_public_ip" "this" { - for_each = { for ip_configuration in var.ip_configuration : - ip_configuration.name => try(ip_configuration.public_ip_standard_sku, false) - if ip_configuration.create_public_ip } - - resource_group_name = var.resource_group_name - location = var.location - name = "${var.name_prefix}pip-vgw${var.name_suffix}-${each.key}" - - allocation_method = each.value ? "Static" : "Dynamic" - zones = var.enable_zones ? var.avzones : null - tags = var.tags - sku = each.value ? "Standard" : "Basic" -} - -# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip -data "azurerm_public_ip" "exists" { - for_each = { for ip_configuration in var.ip_configuration : ip_configuration.name => ip_configuration.public_ip_name if ip_configuration.public_ip_name != null } - - name = each.value - resource_group_name = var.resource_group_name -} - # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/local_network_gateway resource "azurerm_local_network_gateway" "this" { for_each = var.local_network_gateways - name = "${var.name_prefix}lgw-vgw${var.name_suffix}-${each.value.name}" + name = each.value.local_ng_name resource_group_name = var.resource_group_name location = var.location gateway_address = each.value.gateway_address - address_space = try(each.value.address_space, []) + address_space = each.value.address_space dynamic "bgp_settings" { for_each = each.value.remote_bgp_settings content { - asn = try(bgp_settings.value.asn, null) - bgp_peering_address = try(bgp_settings.value.bgp_peering_address, null) - peer_weight = try(bgp_settings.value.peer_weight, null) + asn = bgp_settings.value.asn + bgp_peering_address = bgp_settings.value.bgp_peering_address + peer_weight = bgp_settings.value.peer_weight } } @@ -144,38 +153,38 @@ resource "azurerm_local_network_gateway" "this" { resource "azurerm_virtual_network_gateway_connection" "this" { for_each = var.local_network_gateways - name = "${var.name_prefix}con-vgw${var.name_suffix}-${each.value.connection}" + name = each.value.connection_name location = var.location resource_group_name = var.resource_group_name - type = "IPsec" + type = var.connection_type virtual_network_gateway_id = azurerm_virtual_network_gateway.this.id local_network_gateway_id = azurerm_local_network_gateway.this[each.key].id - enable_bgp = try(var.enable_bgp, false) - local_azure_ip_address_enabled = try(var.private_ip_address_enabled, true) + enable_bgp = var.enable_bgp + local_azure_ip_address_enabled = var.private_ip_address_enabled shared_key = var.ipsec_shared_key dynamic "custom_bgp_addresses" { - for_each = try(each.value.custom_bgp_addresses, {}) + for_each = each.value.custom_bgp_addresses content { - primary = try(var.azure_bgp_peers_addresses[custom_bgp_addresses.value.primary], null) + primary = var.azure_bgp_peers_addresses[custom_bgp_addresses.value.primary] secondary = try(var.azure_bgp_peers_addresses[custom_bgp_addresses.value.secondary], null) } } - connection_mode = try(var.connection_mode, "Default") + connection_mode = var.connection_mode dynamic "ipsec_policy" { - for_each = try(var.ipsec_policy, {}) + for_each = var.ipsec_policies content { - dh_group = try(ipsec_policy.value.dh_group, null) - ike_encryption = try(ipsec_policy.value.ike_encryption, null) - ike_integrity = try(ipsec_policy.value.ike_integrity, null) - ipsec_encryption = try(ipsec_policy.value.ipsec_encryption, null) - ipsec_integrity = try(ipsec_policy.value.ipsec_integrity, null) - pfs_group = try(ipsec_policy.value.pfs_group, null) - sa_datasize = try(ipsec_policy.value.sa_datasize, null) - sa_lifetime = try(ipsec_policy.value.sa_lifetime, null) + dh_group = ipsec_policy.value.dh_group + ike_encryption = ipsec_policy.value.ike_encryption + ike_integrity = ipsec_policy.value.ike_integrity + ipsec_encryption = ipsec_policy.value.ipsec_encryption + ipsec_integrity = ipsec_policy.value.ipsec_integrity + pfs_group = ipsec_policy.value.pfs_group + sa_datasize = ipsec_policy.value.sa_datasize + sa_lifetime = ipsec_policy.value.sa_lifetime } } diff --git a/modules/virtual_network_gateway/variables.tf b/modules/virtual_network_gateway/variables.tf index 8592d610..7b3a2a7d 100644 --- a/modules/virtual_network_gateway/variables.tf +++ b/modules/virtual_network_gateway/variables.tf @@ -1,53 +1,31 @@ -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 -} - -variable "name_prefix" { - description = "A prefix added to all resource names created by this module" - default = "" +# Main resource +variable "name" { + description = "The name of the Virtual Network Gateway." type = string } -variable "name_suffix" { - description = "A suffix added to all resource names created by this module" - default = "" +# Common settings +variable "resource_group_name" { + description = "The name of the Resource Group to use." type = string } -variable "name" { - description = "The name of the Virtual Network Gateway. Changing this forces a new resource to be created" +variable "location" { + description = "The name of the Azure region to deploy the resources in." type = string } variable "tags" { - description = "Azure tags to apply to the created resources." + description = "The map of tags to assign to all created resources." default = {} type = map(string) } -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 -} - -variable "avzones" { - description = <<-EOF - After provider version 3.x you need to specify in which availability zone(s) you want to place IP. - ie: for zone-redundant with 3 availability zone in current region value will be: - ```["1","2","3"]``` - EOF - default = [] - type = list(string) -} - +# Virtual Network Gateway variable "type" { - description = "The type of the Virtual Network Gateway. Valid options are Vpn or ExpressRoute. Changing the type forces a new resource to be created" + description = "The type of the Virtual Network Gateway." + default = "Vpn" + nullable = false type = string validation { condition = contains(["Vpn", "ExpressRoute"], var.type) @@ -56,17 +34,25 @@ variable "type" { } variable "vpn_type" { - description = "The routing type of the Virtual Network Gateway. Valid options are RouteBased or PolicyBased. Defaults to RouteBased. Changing this forces a new resource to be created." + description = "The routing type of the Virtual Network Gateway." default = "RouteBased" + nullable = false type = string validation { - condition = contains(["RouteBased", "PolicyBased"], coalesce(var.vpn_type, "PolicyBased")) + condition = contains(["RouteBased", "PolicyBased"], var.vpn_type) error_message = "Valid options are RouteBased or PolicyBased" } } variable "sku" { - description = "Configuration of the size and capacity of the virtual network gateway. Valid options are Basic, Standard, HighPerformance, UltraPerformance, ErGw1AZ, ErGw2AZ, ErGw3AZ, VpnGw1, VpnGw2, VpnGw3, VpnGw4,VpnGw5, VpnGw1AZ, VpnGw2AZ, VpnGw3AZ,VpnGw4AZ and VpnGw5AZ and depend on the type, vpn_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway." + description = <<-EOF + Configuration of the size and capacity of the virtual network gateway. + + Valid option depends on the type, vpn_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. + Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway. + EOF + default = "Basic" + nullable = false type = string validation { condition = contains(["Basic", "Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "VpnGw1", "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw1AZ", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.sku) @@ -75,13 +61,25 @@ variable "sku" { } variable "active_active" { - description = "If true, an active-active Virtual Network Gateway will be created. An active-active gateway requires a HighPerformance or an UltraPerformance SKU. If false, an active-standby gateway will be created. Defaults to false." + description = <<-EOF + Active-active Virtual Network Gateway. + + If true, an active-active Virtual Network Gateway will be created. + An active-active gateway requires a HighPerformance or an UltraPerformance SKU. + If false, an active-standby gateway will be created. Defaults to false. + EOF default = false + nullable = false type = bool } variable "default_local_network_gateway_id" { - description = "The ID of the local network gateway through which outbound Internet traffic from the virtual network in which the gateway is created will be routed (forced tunnelling)" + description = <<-EOF + The ID of the local network gateway. + + Outbound Internet traffic from the virtual network, in which the gateway is created, + will be routed through local network gateway(forced tunnelling)" + EOF type = string } @@ -91,15 +89,17 @@ variable "edge_zone" { } variable "enable_bgp" { - description = "If true, BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. Defaults to false" + description = "Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway." default = false + nullable = false type = bool } variable "generation" { - description = "The Generation of the Virtual Network gateway. Possible values include Generation1, Generation2 or None" + description = "The Generation of the Virtual Network gateway." type = string default = "Generation1" + nullable = false validation { condition = contains(["Generation1", "Generation2", "None"], coalesce(var.generation, "Generation1")) error_message = "Valid options are Generation1, Generation2 or None" @@ -107,23 +107,47 @@ variable "generation" { } variable "private_ip_address_enabled" { - description = "Should private IP be enabled on this gateway for connections?" + description = "Controls whether the private IP is enabled on the gateway." + default = false + nullable = false type = bool } +variable "zones" { + description = <<-EOF + After provider version 3.x you need to specify in which availability zone(s) you want to place IP. + + For zone-redundant with 3 availability zones in current region value will be: + ```["1","2","3"]``` + EOF + default = null + type = list(string) + validation { + condition = var.zones == null || (var.zones != null ? ( + length(var.zones) == 3 && length(setsubtract(var.zones, ["1", "2", "3"])) == 0 + ) : true) + error_message = "No zones or all 3 zones are expected" + } +} + variable "ip_configuration" { description = <<-EOF - List of IP configurations - every object in the list contains attributes: + IP configurations. - - name - name of the IP configuration - - create_public_ip - boolean value, true if public IP needs to be created - - public_ip_name - name of the public IP resource used, when there is no need to create new one - - private_ip_address_allocation - defines how the private IP address of the gateways virtual interface is assigned. Valid options are Static or Dynamic. Defaults to Dynamic. - - public_ip_standard_sku - defaults to `false`, when set to `true` creates a Standard SKU, statically allocated public IP, otherwise it will be a Basic/Dynamic one. - - subnet_id - the ID of the gateway subnet of a virtual network in which the virtual network gateway will be created. + List of available attributes of each IP configuration. + + - `name` - (`string`, required) name of the IP configuration + - `create_public_ip` - (`bool`, required) - true if public IP needs to be created + - `public_ip_name` - (`string`, required) name of the public IP resource used, when there is no need + to create new one + - `private_ip_address_allocation` - (`string`, optional, defaults to `Dynamic`) defines how the private IP address of + the gateways virtual interface is assigned. + - `subnet_id` - (`string`, required) the ID of the gateway subnet of a virtual network in which + the virtual network gateway will be created. Example: + ```hcl ip_configuration = [ { name = "001" @@ -136,59 +160,116 @@ variable "ip_configuration" { subnet_id = "ID_for_subnet_GatewaySubnet" } ] - + ``` EOF - type = list(any) + default = [] + nullable = false + type = list(object({ + name = string + create_public_ip = bool + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") + subnet_id = string + })) + validation { + condition = length(flatten([for _, config in var.ip_configuration : config.name])) == length(distinct(flatten([for _, config in var.ip_configuration : config.name]))) + error_message = "The `name` property has to be unique among all IP configuration." + } + validation { + condition = alltrue(flatten([ + for _, config in var.ip_configuration : [ + contains(["Static", "Dynamic"], config.private_ip_address_allocation) + ]])) + error_message = "Possible values for `private_ip_address_allocation` are Static or Dynamic" + } } variable "vpn_client_configuration" { description = <<-EOF - List of VPN client configurations - every object in the list contains attributes: - - address_space - the address space out of which IP addresses for vpn clients will be taken. You can provide more than one address space, e.g. in CIDR notation. - - aad_tenant - AzureAD Tenant URL - - aad_audience - the client id of the Azure VPN application. See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values - - aad_issuer - the STS url for your tenant - - root_certificate - one or more root_certificate blocks which are defined below. These root certificates are used to sign the client certificate used by the VPN clients to connect to the gateway. - - revoked_certificate - one or more revoked_certificate blocks which are defined below. - - radius_server_address - the address of the Radius server. - - radius_server_secret - the secret used by the Radius server. - - vpn_client_protocols - list of the protocols supported by the vpn client. The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with the use of aad_tenant, aad_audience and aad_issuer. - - vpn_auth_types - list of the vpn authentication types for the virtual network gateway. The supported values are AAD, Radius and Certificate. + VPN client configurations (IPSec point-to-site connections). + + List of available attributes of each VPN client configurations: + - `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. + You can provide more than one address space, e.g. in CIDR notation. + - `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL + - `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. + See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values + - `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant + - `root_certificate` - (`object`, optional, defaults to `null`) one or more root_certificate blocks + which are defined below. These root certificates are used to sign the client + certificate used by the VPN clients to connect to the gateway. + - `revoked_certificate` - (`object`, optional, defaults to `null`) one or more revoked_certificate blocks + which are defined below. + - `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. + - `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. + - `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. + The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with + the use of aad_tenant, aad_audience and aad_issuer. + - `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for + the virtual network gateway. The supported values are AAD, Radius and Certificate. EOF - type = list(any) + type = list(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificate = optional(object({ + name = string + public_cert_data = string + })) + revoked_certificate = optional(object({ + name = string + thumbprint = string + })) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })) } variable "azure_bgp_peers_addresses" { description = <<-EOF - Map of IP addresses used on Azure side for BGP. Map is used to not to duplicate IP address and refer to keys while configuring: - - custom_bgp_addresses - - peering_addresses in local_bgp_settings + Map of IP addresses used on Azure side for BGP. + + Map is used to not to duplicate IP address and refer to keys while configuring: + - `custom_bgp_addresses` + - `peering_addresses` in `local_bgp_settings` Example: + ```hcl azure_bgp_peers_addresses = { primary_1 = "169.254.21.2" secondary_1 = "169.254.22.2" primary_2 = "169.254.21.6" secondary_2 = "169.254.22.6" } - + ``` EOF + default = {} + nullable = false type = map(string) } variable "local_bgp_settings" { description = <<-EOF - Map of BGP settings: - - asn - the Autonomous System Number (ASN) to use as part of the BGP. - - peering_addresses - a map of peering addresses, which contains 1 (for active-standby) or 2 objects (for active-active) with: - - key is the ip configuration name - - apipa_addresses is the list of keys for IP addresses defined in variable azure_bgp_peers_addresses - - peer_weight - the weight added to routes which have been learned through BGP peering. Valid values can be between 0 and 100. + BGP settings. + + Attributes: + - `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. + - `peering_addresses` - (`map`, required) a map of peering addresses, which contains 1 (for active-standby) + or 2 objects (for active-active), where key is the ip configuration name and with attributes: + - `apipa_addresses` - (`list`, required) is the list of keys for IP addresses defined in variable azure_bgp_peers_addresses + - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to + the BGP peer of the Virtual Network Gateway. + - `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes + which have been learned through BGP peering. Example: + ```hcl local_bgp_settings = { asn = "65001" peering_addresses = { @@ -200,41 +281,66 @@ variable "local_bgp_settings" { } } } - + ``` EOF - type = any + type = object({ + asn = string + peering_addresses = map(object({ + apipa_addresses = list(string) + default_addresses = optional(list(string)) + })) + peer_weight = optional(number) + }) + validation { + condition = var.local_bgp_settings.peer_weight != null ? var.local_bgp_settings.peer_weight >= 0 && var.local_bgp_settings.peer_weight <= 100 : true + error_message = "Possible values for `peer_weight` are between 0 and 100." + } } variable "custom_route" { description = <<-EOF - List of custom routes - every object in the list contains attributes: - - address_prefixes - a list of address blocks reserved for this virtual network in CIDR notation as defined below. + List of custom routes. + + Every object in the list contains attributes: + - `address_prefixes` - (`list`, optional, defaults to `null`) a list of address blocks reserved for this virtual network in CIDR notation as defined below. EOF - type = list(any) + default = [] + nullable = false + type = list(object({ + address_prefixes = optional(list(string)) + })) } +# Local network gateways variable "local_network_gateways" { description = <<-EOF - Map of local network gateways - every object in the map contains attributes: - - name - the name of the local network gateway. - - connection - the name of the virtual network gateway connection. - - remote_bgp_settings - block containing Local Network Gateway's BGP speaker settings: - - asn - the BGP speaker's ASN. - - bgp_peering_address - the BGP peering address and BGP identifier of this BGP speaker. - - peer_weight - the weight added to routes learned from this BGP speaker. - - gateway_address - the gateway IP address to connect with. - - address_space - the list of string CIDRs representing the address spaces the gateway exposes. - - custom_bgp_addresses - Border Gateway Protocol custom IP Addresses, which can only be used on IPSec / active-active connections. Object contains 2 attributes: - - primary - single IP address that is part of the azurerm_virtual_network_gateway ip_configuration (first one) - - secondary - single IP address that is part of the azurerm_virtual_network_gateway ip_configuration (second one) + Map of local network gateways. + + Every object in the map contains attributes: + - local_ng_name - (`string`, required) the name of the local network gateway. + - connection_name - (`string`, required) the name of the virtual network gateway connection. + - remote_bgp_settings - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: + - asn - (`string`, required) the BGP speaker's ASN. + - bgp_peering_address - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. + - peer_weight - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. + - gateway_address - (`string`, optional, defaults to `null`) the gateway IP address to connect with. + - address_space - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces + the gateway exposes. + - custom_bgp_addresses - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, + which can only be used on IPSec / active-active connections. Object contains 2 attributes: + - primary - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway + ip_configuration (first one) + - secondary - (`string`, optional, defaults to `null`) single IP address that is part of + the azurerm_virtual_network_gateway ip_configuration (second one) Example: + ```hcl local_network_gateways = { "lg1" = { - name = "001" - connection = "001" + local_ng_name = "001" + connection_name = "001" gateway_address = "PUBLIC_IP_1" remote_bgp_settings = [{ asn = "65002" @@ -248,8 +354,8 @@ variable "local_network_gateways" { ] } "lg2" = { - name = "002" - connection = "002" + local_ng_name = "002" + connection_name = "002" gateway_address = "PUBLIC_IP_2" remote_bgp_settings = [{ asn = "65003" @@ -263,8 +369,8 @@ variable "local_network_gateways" { ] } "lg3" = { - name = "003" - connection = "003" + local_ng_name = "003" + connection_name = "003" gateway_address = "PUBLIC_IP_3" remote_bgp_settings = [{ asn = "65002" @@ -278,8 +384,8 @@ variable "local_network_gateways" { ] } "lg4" = { - name = "004" - connection = "004" + local_ng_name = "004" + connection_name = "004" gateway_address = "PUBLIC_IP_4" remote_bgp_settings = [{ asn = "65003" @@ -293,36 +399,73 @@ variable "local_network_gateways" { ] } } - + ``` EOF - type = any + type = map(object({ + local_ng_name = string + connection_name = string + remote_bgp_settings = optional(list(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })), []) + gateway_address = optional(string) + address_space = optional(list(string), []) + custom_bgp_addresses = optional(list(object({ + primary = string + secondary = optional(string) + })), []) + })) } -variable "ipsec_shared_key" { - description = "The shared IPSec key." +# Virtual Network Gateway connection +variable "connection_type" { + description = "The type of VNG connection." + default = "IPsec" + nullable = false type = string - sensitive = true + validation { + condition = contains(["IPsec", "ExpressRoute", "Vnet2Vnet"], var.connection_type) + error_message = "Valid options are IPsec (Site-to-Site), ExpressRoute (ExpressRoute), and Vnet2Vnet (VNet-to-VNet)" + } } variable "connection_mode" { - description = "Connection mode to use. Possible values are Default, InitiatorOnly and ResponderOnly. Defaults to Default. Changing this value will force a resource to be created." + description = "The connection mode to use." + default = "Default" + nullable = false type = string + validation { + condition = contains(["Default", "InitiatorOnly", "ResponderOnly"], var.connection_mode) + error_message = "Possible values are Default, InitiatorOnly and ResponderOnly" + } } -variable "ipsec_policy" { +variable "ipsec_shared_key" { + description = "The shared IPSec key." + type = string + sensitive = true +} + +variable "ipsec_policies" { description = <<-EOF - IPsec policy used for Virtual Network Connection with attributes: - - dh_group - The DH group used in IKE phase 1 for initial SA. Valid options are DHGroup1, DHGroup14, DHGroup2, DHGroup2048, DHGroup24, ECP256, ECP384, or None. - - ike_encryption - The IKE encryption algorithm. Valid options are AES128, AES192, AES256, DES, DES3, GCMAES128, or GCMAES256. - - ike_integrity - The IKE integrity algorithm. Valid options are GCMAES128, GCMAES256, MD5, SHA1, SHA256, or SHA384. - - ipsec_encryption - The IPSec encryption algorithm. Valid options are AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES192, GCMAES256, or None. - - ipsec_integrity - The IPSec integrity algorithm. Valid options are GCMAES128, GCMAES192, GCMAES256, MD5, SHA1, or SHA256. - - pfs_group - The DH group used in IKE phase 2 for new child SA. Valid options are ECP256, ECP384, PFS1, PFS14, PFS2, PFS2048, PFS24, PFSMM, or None. - - sa_datasize - The IPSec SA payload size in KB. Must be at least 1024 KB. Defaults to 102400000 KB. - - sa_lifetime - The IPSec SA lifetime in seconds. Must be at least 300 seconds. Defaults to 27000 seconds. + IPsec policies used for Virtual Network Connection. + + Single policy contains attributes: + - `dh_group` - (`string`, required) The DH group used in IKE phase 1 for initial SA. + - `ike_encryption` - (`string`, required) The IKE encryption algorithm. + - `ike_integrity` - (`string`, required) The IKE integrity algorithm. + - `ipsec_encryption` - (`string`, required) The IPSec encryption algorithm. + - `ipsec_integrity` - (`string`, required) The IPSec integrity algorithm. + - `pfs_group` - (`string`, required) The DH group used in IKE phase 2 for new child SA. + - `sa_datasize` - (`string`, optional, defaults to `102400000`) The IPSec SA payload size in KB. + Must be at least 1024 KB. + - `sa_lifetime` - (`string`, optional, defaults to `27000`) The IPSec SA lifetime in seconds. + Must be at least 300 seconds. Example: + ```hcl ipsec_policy = [ { dh_group = "ECP384" @@ -335,7 +478,58 @@ variable "ipsec_policy" { sa_lifetime = "27000" } ] - + ``` EOF - type = any + type = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string, "102400000") + sa_lifetime = optional(string, "27000") + })) + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["DHGroup1", "DHGroup14", "DHGroup2", "DHGroup2048", "DHGroup24", "ECP256", "ECP384", "None"], ipsec_policy.dh_group) + ]])) + error_message = "Possible values for `dh_group` are DHGroup1, DHGroup14, DHGroup2, DHGroup2048, DHGroup24, ECP256, ECP384 or None" + } + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES256"], ipsec_policy.ike_encryption) + ]])) + error_message = "Possible values for `ike_encryption` are AES128, AES192, AES256, DES, DES3, GCMAES128, or GCMAES256" + } + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["GCMAES128", "GCMAES256", "MD5", "SHA1", "SHA256", "SHA384"], ipsec_policy.ike_integrity) + ]])) + error_message = "Possible values for `ike_integrity` are GCMAES128, GCMAES256, MD5, SHA1, SHA256, or SHA384" + } + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES192", "GCMAES256", "None"], ipsec_policy.ipsec_encryption) + ]])) + error_message = "Possible values for `ipsec_encryption` are AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES192, GCMAES256, or None" + } + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["GCMAES128", "GCMAES192", "GCMAES256", "MD5", "SHA1", "SHA256"], ipsec_policy.ipsec_integrity) + ]])) + error_message = "Possible values for `ipsec_integrity` are GCMAES128, GCMAES192, GCMAES256, MD5, SHA1, or SHA256" + } + validation { + condition = alltrue(flatten([ + for _, ipsec_policy in var.ipsec_policies : [ + contains(["ECP256", "ECP384", "PFS1", "PFS14", "PFS2", "PFS2048", "PFS24", "PFSMM", "None"], ipsec_policy.pfs_group) + ]])) + error_message = "Possible values for `pfs_group` are ECP256, ECP384, PFS1, PFS14, PFS2, PFS2048, PFS24, PFSMM, or None" + } } diff --git a/modules/virtual_network_gateway/versions.tf b/modules/virtual_network_gateway/versions.tf index 501042ff..8dc5c1eb 100644 --- a/modules/virtual_network_gateway/versions.tf +++ b/modules/virtual_network_gateway/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 bcf1c63e235fb61fe6561b69c866a127f03ece33 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Thu, 16 Nov 2023 13:38:27 +0100 Subject: [PATCH 10/49] refactor: vnet tfvars in examples (#359) --- examples/common_vmseries/example.tfvars | 14 +++++++------- .../common_vmseries_and_autoscale/example.tfvars | 14 +++++++------- examples/dedicated_vmseries/example.tfvars | 14 +++++++------- .../example.tfvars | 14 +++++++------- examples/standalone_panorama/example.tfvars | 6 +++--- examples/standalone_vmseries/example.tfvars | 6 +++--- examples/test_infrastructure/example.tfvars | 2 +- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 3e439335..9409ebb0 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -91,20 +91,20 @@ vnets = { "management" = { name = "mgmt-snet" address_prefixes = ["10.0.0.0/28"] - network_security_group = "management" - route_table = "management" + network_security_group_key = "management" + route_table_key = "management" enable_storage_service_endpoint = true } "private" = { name = "private-snet" address_prefixes = ["10.0.0.16/28"] - route_table = "private" + route_table_key = "private" } "public" = { - name = "public-snet" - address_prefixes = ["10.0.0.32/28"] - network_security_group = "public" - route_table = "public" + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" } "appgw" = { name = "appgw-snet" diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index a69c6b80..45795420 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -101,20 +101,20 @@ vnets = { "management" = { name = "mgmt-snet" address_prefixes = ["10.0.0.0/28"] - network_security_group = "management" - route_table = "management" + network_security_group_key = "management" + route_table_key = "management" enable_storage_service_endpoint = true } "private" = { name = "private-snet" address_prefixes = ["10.0.0.16/28"] - route_table = "private" + route_table_key = "private" } "public" = { - name = "public-snet" - address_prefixes = ["10.0.0.32/28"] - network_security_group = "public" - route_table = "public" + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" } "appgw" = { name = "appgw-snet" diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 2ac20c25..404f600d 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -91,20 +91,20 @@ vnets = { "management" = { name = "mgmt-snet" address_prefixes = ["10.0.0.0/28"] - network_security_group = "management" - route_table = "management" + network_security_group_key = "management" + route_table_key = "management" enable_storage_service_endpoint = true } "private" = { name = "private-snet" address_prefixes = ["10.0.0.16/28"] - route_table = "private" + route_table_key = "private" } "public" = { - name = "public-snet" - address_prefixes = ["10.0.0.32/28"] - network_security_group = "public" - route_table = "public" + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" } } } diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 5579d8a8..c873e3f5 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -101,20 +101,20 @@ vnets = { "management" = { name = "mgmt-snet" address_prefixes = ["10.0.0.0/28"] - network_security_group = "management" - route_table = "management" + network_security_group_key = "management" + route_table_key = "management" enable_storage_service_endpoint = true } "private" = { name = "private-snet" address_prefixes = ["10.0.0.16/28"] - route_table = "private" + route_table_key = "private" } "public" = { - name = "public-snet" - address_prefixes = ["10.0.0.32/28"] - network_security_group = "public" - route_table = "public" + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" } "appgw" = { name = "appgw-snet" diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 6726f2b9..6bee91a4 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -36,9 +36,9 @@ vnets = { } subnets = { "panorama" = { - name = "panorama-snet" - address_prefixes = ["10.1.0.0/28"] - network_security_group = "panorama" + name = "panorama-snet" + address_prefixes = ["10.1.0.0/28"] + network_security_group_key = "panorama" } } } diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index d7ceb202..73d3bdb4 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -34,9 +34,9 @@ vnets = { } subnets = { "management" = { - name = "mgmt-snet" - address_prefixes = ["10.0.0.0/28"] - network_security_group = "management" + name = "mgmt-snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group_key = "management" } } } diff --git a/examples/test_infrastructure/example.tfvars b/examples/test_infrastructure/example.tfvars index 6aa15fb9..de10de89 100644 --- a/examples/test_infrastructure/example.tfvars +++ b/examples/test_infrastructure/example.tfvars @@ -64,7 +64,7 @@ vnets = { "vms" = { name = "vms" address_prefixes = ["10.100.1.0/26"] - route_table = "nva" + route_table_key = "nva" } "bastion" = { name = "AzureBastionSubnet" From a63213f80532ba637c7d785f53d70c642a0ee59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Thu, 16 Nov 2023 13:42:41 +0100 Subject: [PATCH 11/49] refactor(module/loadbalancer): including examples (#348) --- examples/common_vmseries/example.tfvars | 11 +- examples/common_vmseries/main.tf | 46 +- examples/common_vmseries/variables.tf | 148 ++--- .../example.tfvars | 14 +- .../common_vmseries_and_autoscale/main.tf | 47 +- .../variables.tf | 142 +++-- examples/dedicated_vmseries/example.tfvars | 11 +- examples/dedicated_vmseries/main.tf | 45 +- examples/dedicated_vmseries/variables.tf | 148 ++--- .../example.tfvars | 11 +- .../dedicated_vmseries_and_autoscale/main.tf | 47 +- .../variables.tf | 142 +++-- examples/gwlb_with_vmseries/example.tfvars | 6 +- examples/gwlb_with_vmseries/main.tf | 52 +- examples/gwlb_with_vmseries/variables.tf | 148 +++-- examples/lb/.header.md | 3 + examples/lb/README.md | 290 ++++++++++ examples/lb/brownfield/main.tf | 46 ++ examples/lb/example.tfvars | 123 ++++ examples/lb/main.tf | 88 +++ examples/lb/outputs.tf | 47 ++ examples/lb/variables.tf | 192 +++++++ examples/{vnet => lb}/versions.tf | 0 examples/standalone_vmseries/main.tf | 45 +- examples/standalone_vmseries/variables.tf | 143 +++-- examples/vnet/main.tf | 45 -- modules/loadbalancer/.header.md | 83 +++ modules/loadbalancer/README.md | 522 ++++++++++++++--- modules/loadbalancer/main.tf | 167 +++--- modules/loadbalancer/outputs.tf | 4 +- modules/loadbalancer/variables.tf | 542 ++++++++++++------ modules/loadbalancer/versions.tf | 2 +- 32 files changed, 2493 insertions(+), 867 deletions(-) create mode 100644 examples/lb/.header.md create mode 100644 examples/lb/README.md create mode 100644 examples/lb/brownfield/main.tf create mode 100644 examples/lb/example.tfvars create mode 100644 examples/lb/main.tf create mode 100644 examples/lb/outputs.tf create mode 100644 examples/lb/variables.tf rename examples/{vnet => lb}/versions.tf (100%) delete mode 100644 examples/vnet/main.tf create mode 100644 modules/loadbalancer/.header.md diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 9409ebb0..94462efa 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 4a84da47..75a2cf3a 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -101,32 +101,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 @@ -135,6 +138,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 8362318e..79a98222 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -160,73 +160,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/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 45795420..19815dbb 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/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index b9c6d29e..d5509872 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -95,32 +95,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 77e07d31..babc7672 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -160,73 +160,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/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 404f600d..042f7556 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/main.tf b/examples/dedicated_vmseries/main.tf index 914f5532..bb8e58d5 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -100,32 +100,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 ee45008b..39c642a3 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -160,73 +160,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/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index c873e3f5..40ee9b2b 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/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index b9c6d29e..d5509872 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -95,32 +95,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 77e07d31..babc7672 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -160,73 +160,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/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" } } diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index f2c80215..a5cd81d0 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -247,39 +247,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 396de21e..ad16afaf 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -240,67 +240,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/.header.md b/examples/lb/.header.md new file mode 100644 index 00000000..46e6e81d --- /dev/null +++ b/examples/lb/.header.md @@ -0,0 +1,3 @@ +# LB + +Sample LB documentation. diff --git a/examples/lb/README.md b/examples/lb/README.md new file mode 100644 index 00000000..5ad6de64 --- /dev/null +++ b/examples/lb/README.md @@ -0,0 +1,290 @@ +<!-- BEGIN_TF_DOCS --> +# LB + +Sample LB documentation. + +## 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 Balancers. + + + + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.3, < 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 + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### load_balancers + +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` + + +Type: + +```hcl +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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ 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..555a2bde --- /dev/null +++ b/examples/lb/example.tfvars @@ -0,0 +1,123 @@ +# --- 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 = ["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" + } + } + frontend_ips = { + "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" = { + 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" + zones = ["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" + } + } + } + } + } +} \ No newline at end of file diff --git a/examples/lb/main.tf b/examples/lb/main.tf new file mode 100644 index 00000000..0bc194fa --- /dev/null +++ b/examples/lb/main.tf @@ -0,0 +1,88 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { + for k, v in each.value.network_security_groups : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { + for k, v in each.value.route_tables : + k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + 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 + + 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 => 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] +} diff --git a/examples/lb/outputs.tf b/examples/lb/outputs.tf new file mode 100644 index 00000000..e1ec9160 --- /dev/null +++ b/examples/lb/outputs.tf @@ -0,0 +1,47 @@ +# output "fronts" { +# value = local.fronts +# } + + + + + +# output "username" { +# description = "Initial administrative username to use for VM-Series." +# value = var.vmseries_username +# } + +# output "password" { +# description = "Initial administrative password to use for VM-Series." +# value = local.vmseries_password +# sensitive = true +# } + +# output "natgw_public_ips" { +# description = "Nat Gateways Public IP resources." +# value = length(var.natgws) > 0 ? { for k, v in module.natgw : k => { +# pip = v.natgw_pip +# pip_prefix = v.natgw_pip_prefix +# } } : null +# } + +# output "metrics_instrumentation_keys" { +# description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." +# value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null +# sensitive = true +# } + +# output "lb_frontend_ips" { +# description = "IP Addresses of the load balancers." +# value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null +# } + +# output "vmseries_mgmt_ips" { +# description = "IP addresses for the VMSeries management interface." +# value = { for k, v in module.vmseries : k => v.mgmt_ip_address } +# } + +# output "bootstrap_storage_urls" { +# value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null +# sensitive = true +# } diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf new file mode 100644 index 00000000..8a422944 --- /dev/null +++ b/examples/lb/variables.tf @@ -0,0 +1,192 @@ +### 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 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) + })), {}) + })), {}) + })) +} \ 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/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 4a84da47..2813961e 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -101,32 +101,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 e6e3f6c0..3ccc3663 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -160,71 +160,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/examples/vnet/main.tf b/examples/vnet/main.tf deleted file mode 100644 index 8f805125..00000000 --- a/examples/vnet/main.tf +++ /dev/null @@ -1,45 +0,0 @@ -# Create or source the Resource Group. -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - name = var.resource_group_name -} - -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - - network_security_groups = { - for k, v in each.value.network_security_groups : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { - for k, v in each.value.route_tables : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - tags = var.tags -} diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md new file mode 100644 index 00000000..eac7e1a6 --- /dev/null +++ b/modules/loadbalancer/.header.md @@ -0,0 +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] +> The module can create a public or a private Load Balancer +> Due to that some properties are mutually exclusive. + +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 + +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" + } + } + } + } +} +``` diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index dcf6bfa9..ed2cea1e 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -1,71 +1,465 @@ +<!-- BEGIN_TF_DOCS --> # 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. -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, 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`. +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. - -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [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 | -|------|-------------|------|---------|:--------:| -| <a name="input_frontend_ips"></a> [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. <br>Keys of the map are names of LB Frontend IP configurations.<br><br>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.<br><br>**Inbound rules.**<br><br>Here is a list of properties supported by each `in_rule`:<br><br>- `protocol` : required, communication protocol, either 'Tcp', 'Udp' or 'All'.<br>- `port` : required, communication port, this is both the front- and the backend port if `backend_port` is not given.<br>- `backend_port` : optional, this is the backend port to forward traffic to in the backend pool.<br>- `floating_ip` : optional, defaults to `true`, enables floating IP for this rule.<br>- `session_persistence` : optional, defaults to 5 tuple (Azure default), see `Session persistence/Load distribution` below for details.<br><br>Public LB<br><br>- `create_public_ip` : Optional. Set to `true` to create a public IP.<br>- `public_ip_name` : Ignored if `create_public_ip` is `true`. The existing public IP resource name to use.<br>- `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`.<br><br>Example<pre>frontend_ips = {<br> pip_existing = {<br> create_public_ip = false<br> public_ip_name = "my_ip"<br> public_ip_resource_group = "my_rg_name"<br> in_rules = {<br> HTTP = {<br> port = 80<br> protocol = "Tcp"<br> }<br> }<br> }<br>}</pre>Forward to a different port on backend pool<pre>frontend_ips = {<br> pip_existing = {<br> create_public_ip = false<br> public_ip_name = "my_ip"<br> public_ip_resource_group = "my_rg_name"<br> in_rules = {<br> HTTP = {<br> port = 80<br> backend_port = 8080<br> protocol = "Tcp"<br> }<br> }<br> }<br>}</pre>Private LB<br><br>- `subnet_id` : Identifier of an existing subnet. This also trigger creation of an internal LB.<br>- `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`.<br><br>Example<pre>frontend_ips = {<br> internal_fe = {<br> subnet_id = azurerm_subnet.this.id<br> private_ip_address = "192.168.0.10"<br> in_rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br>}</pre>Session persistence/Load distribution<br><br>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:<br><br>- `Default` : this is the 5 tuple hash - this method is also used when no property is defined<br>- `SourceIP` : a 2 tuple hash is used<br>- `SourceIPProtocol` : a 3 tuple hash is used<br><br>Example<pre>frontend_ips = {<br> rule_1 = {<br> create_public_ip = true<br> in_rules = {<br> HTTP = {<br> port = 80<br> protocol = "Tcp"<br> session_persistence = "SourceIP"<br> }<br> }<br> }<br> }</pre>**Outbound rules.**<br><br>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`.<br><br>Following properties are available:<br><br>- `protocol` : Protocol used by the rule. On of `All`, `Tcp` or `Udp` is accepted.<br>- `allocated_outbound_ports` : Number of ports allocated per instance. Defaults to `1024`.<br>- `enable_tcp_reset` : Ignored when `protocol` is set to `Udp`, defaults to `False` (Azure defaults).<br>- `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).<br><br>Example:<pre>frontend_ips = {<br> rule_1 = {<br> create_public_ip = true<br> in_rules = {<br> HTTP = {<br> port = 80<br> protocol = "Tcp"<br> session_persistence = "SourceIP"<br> }<br> }<br> out_rules = {<br> "outbound_tcp" = {<br> protocol = "Tcp"<br> allocated_outbound_ports = 2048<br> enable_tcp_reset = true<br> idle_timeout_in_minutes = 10<br> }<br> }<br> }<br>}</pre> | `any` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of a pre-existing Resource Group to place the resources in. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Region to deploy load balancer and dependencies. | `string` | n/a | yes | -| <a name="input_backend_name"></a> [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 | -| <a name="input_name"></a> [name](#input\_name) | The name of the load balancer. | `string` | n/a | yes | -| <a name="input_probe_name"></a> [probe\_name](#input\_probe\_name) | The name of the load balancer probe. | `string` | `"vmseries_probe"` | no | -| <a name="input_probe_port"></a> [probe\_port](#input\_probe\_port) | Health check port number of the load balancer probe. | `string` | `"80"` | no | -| <a name="input_network_security_allow_source_ips"></a> [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.<br>If it's empty, user is responsible for configuring a Network Security Group separately.<br>The list cannot include Azure tags like "Internet" or "Sql.EastUS". | `list(string)` | `[]` | no | -| <a name="input_network_security_resource_group_name"></a> [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 | -| <a name="input_network_security_group_name"></a> [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.<br>User is responsible to associate the NSG with the load balancer's subnet, the module only supplies the rules.<br>If empty, user is responsible for configuring an NSG separately. | `string` | `null` | no | -| <a name="input_network_security_base_priority"></a> [network\_security\_base\_priority](#input\_network\_security\_base\_priority) | The base number from which the auto-generated priorities of the NSG rules grow.<br>Ignored if `network_security_group_name` is empty or if `network_security_allow_source_ips` is empty. | `number` | `1000` | no | -| <a name="input_enable_zones"></a> [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 | -| <a name="input_tags"></a> [tags](#input\_tags) | Azure tags to apply to the created resources. | `map(string)` | `{}` | no | -| <a name="input_avzones"></a> [avzones](#input\_avzones) | Controls zones for load balancer's Fronted IP configurations. For:<br><br>* public IPs - these are regions in which the IP resource is available<br>* private IPs - this represents Zones to which Azure will deploy paths leading to this Frontend IP.<br><br>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:<pre>["1","2","3"]</pre> | `list(string)` | `[]` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_backend_pool_id"></a> [backend\_pool\_id](#output\_backend\_pool\_id) | The identifier of the backend pool. | -| <a name="output_frontend_ip_configs"></a> [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. | -| <a name="output_health_probe"></a> [health\_probe](#output\_health\_probe) | The health probe object. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +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 Azure Load Balancer. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure 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 +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`zones`](#zones) | `list` | Controls zones for Load Balancer's Fronted IP configurations. +[`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. + + + +## 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 Azure Load Balancer. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### 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 + } + } +} +``` + + +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) + 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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### health_probes + +Backend's health probe definition. + +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)` + + +Type: + +```hcl +map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + })) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### nsg_auto_rules_settings + +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 +- `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: + +```hcl +object({ + nsg_name = string + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number, 1000) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index b5768ebc..044617ee 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,148 @@ 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.gwlb_fip_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" "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 +locals { + default_http_probe_port = { + "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_rule" "in_rules" { +resource "azurerm_lb_probe" "this" { + for_each = merge(coalesce(var.health_probes, {}), local.default_probe) + + loadbalancer_id = azurerm_lb.this.id + + 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" { 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[each.value.rule.health_probe_key].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 IPs: ${join(",", var.nsg_auto_rules_settings.source_ips)}" 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/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..bb65542b 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -1,47 +1,122 @@ -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. - - 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 "name" { + description = "The name of the Azure Load Balancer." + type = string +} - **Inbound rules.** +variable "resource_group_name" { + description = "The name of the Resource Group to use." + type = string +} - Here is a list of properties supported by each `in_rule`: +variable "location" { + description = "The name of the Azure region to deploy the resources in." + type = string +} - - `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. +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} - Public LB +variable "zones" { + description = <<-EOF + Controls zones for Load Balancer's Fronted IP configurations. - - `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`. + For: - Example + - 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) - ``` - 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" - } - } - } + 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) + validation { + condition = length(var.zones) > 0 || var.zones == null + error_message = "The `var.zones` can either be a non empty list of Availability Zones or explicit `null`." } - ``` - - Forward to a different port on backend pool +} - ``` +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 @@ -50,24 +125,16 @@ variable "frontend_ips" { in_rules = { HTTP = { port = 80 - backend_port = 8080 protocol = "Tcp" + backend_port = 8080 } } } } - ``` - - 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 Load Balancer, 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 +145,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 Load Balancer, session persistance with 2 tuple hash, outbound rule defined frontend_ips = { rule_1 = { create_public_ip = true @@ -129,114 +157,286 @@ 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) + 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 "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 "health_probes" { + description = <<-EOF + Backend's health probe definition. -variable "probe_port" { - description = "Health check port number of the load balancer probe." - default = "80" - type = string -} + When this property is either: -variable "network_security_allow_source_ips" { - 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) -} + - not defined at all, or + - at least one `in_rule` has no health probe specified -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 -} + a default, TCP based probe will be created for port 80. -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. + 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)` EOF default = null - type = string + type = map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + 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 = 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 = 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 = 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 = 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 = 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 = 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 = var.health_probes == null ? true : alltrue([for k, v in var.health_probes : v.request_path != null if v.protocol != "Tcp"]) + error_message = "value" + } } -variable "network_security_base_priority" { +variable "nsg_auto_rules_settings" { 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. - 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) -} + Controls automatic creation of NSG rules for all defined inbound rules. -variable "avzones" { - description = <<-EOF - Controls zones for load balancer's Fronted IP configurations. For: + When skipped or assigned an explicit `null`, disables rules creation. - * 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 + - `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 = [] - 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) + }) + 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}(\\/[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`." + } } 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 4ded3dda32c74742cde55cf2667e01b77a083f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:48:45 +0100 Subject: [PATCH 12/49] refactor: add support for disable bgp route propagation (#367) --- .github/workflows/pre-commit-update.yml | 4 ++-- examples/common_vmseries/variables.tf | 3 ++- examples/common_vmseries_and_autoscale/variables.tf | 3 ++- examples/dedicated_vmseries/variables.tf | 3 ++- examples/dedicated_vmseries_and_autoscale/variables.tf | 3 ++- examples/gwlb_with_vmseries/example.tfvars | 2 +- examples/gwlb_with_vmseries/variables.tf | 3 ++- examples/standalone_panorama/variables.tf | 3 ++- examples/standalone_vmseries/variables.tf | 3 ++- examples/test_infrastructure/variables.tf | 3 ++- modules/vnet/README.md | 8 +++++--- modules/vnet/main.tf | 9 +++++---- modules/vnet/variables.tf | 8 +++++--- 13 files changed, 34 insertions(+), 21 deletions(-) diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index bac6b48d..00655f7a 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -22,7 +22,7 @@ jobs: uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_pre_commit.yml@v2.3 with: pre-commit-hooks: terraform_fmt terraform_docs terraform_tflint checkov - checkout-branch: pre-commit-dependencies-update + branch: pre-commit-dependencies-update comment-pr: name: Give comment on the PR if pre-commit failed @@ -31,4 +31,4 @@ jobs: uses: PaloAltoNetworks/terraform-modules-vmseries-ci-workflows/.github/workflows/_comment_pr.yml@v2.3 with: pr_number: ${{ needs.update.outputs.pr_number }} - job_result: ${{ needs.pre-commit.result }} \ No newline at end of file + job_result: ${{ needs.pre-commit.result }} diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 79a98222..286db4d2 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -97,7 +97,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index babc7672..8abc9d67 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -79,7 +79,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 39c642a3..cb3fcaae 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -79,7 +79,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index babc7672..8abc9d67 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -79,7 +79,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 4f1c507f..d431473f 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,6 +1,6 @@ # Common name_prefix = "example-" -location = "East US" +location = "North Europe" resource_group_name = "vmseries-gwlb" tags = { "CreatedBy" = "Palo Alto Networks" diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index ad16afaf..c4e77e08 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -67,7 +67,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 33b9cf5f..34dcce02 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -79,7 +79,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 3ccc3663..cfd44a09 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -79,7 +79,8 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/examples/test_infrastructure/variables.tf b/examples/test_infrastructure/variables.tf index 34f73ddd..753ac35b 100644 --- a/examples/test_infrastructure/variables.tf +++ b/examples/test_infrastructure/variables.tf @@ -74,7 +74,8 @@ variable "vnets" { hub_resource_group_name = optional(string) hub_vnet_name = optional(string) network_security_groups = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) rules = optional(map(object({ name = string priority = number diff --git a/modules/vnet/README.md b/modules/vnet/README.md index b6efe63d..f36ac1cf 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -393,8 +393,9 @@ Map of objects describing a Route Tables. List of available properties: -- `name` - (`string`, required) name of a Route Table. -- `routes` - (`map`, required) a map of Route Table entries (UDRs): +- `name` - (`string`, required) name of a Route Table. +- `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP +- `routes` - (`map`, required) a map of Route Table entries (UDRs): - `name` - (`string`, required) a name of a UDR. - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. @@ -439,7 +440,8 @@ Type: ```hcl map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool, false) routes = map(object({ name = string address_prefix = string diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index e6e612ed..f5f8f5b7 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -97,10 +97,11 @@ resource "azurerm_network_security_rule" "this" { resource "azurerm_route_table" "this" { for_each = var.route_tables - name = each.value.name - location = var.location - resource_group_name = var.resource_group_name - tags = var.tags + name = each.value.name + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags + disable_bgp_route_propagation = each.value.disable_bgp_route_propagation } locals { diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index f3ddedfc..33f77764 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -335,8 +335,9 @@ variable "route_tables" { List of available properties: - - `name` - (`string`, required) name of a Route Table. - - `routes` - (`map`, required) a map of Route Table entries (UDRs): + - `name` - (`string`, required) name of a Route Table. + - `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP + - `routes` - (`map`, required) a map of Route Table entries (UDRs): - `name` - (`string`, required) a name of a UDR. - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. @@ -379,7 +380,8 @@ variable "route_tables" { default = {} nullable = false type = map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool, false) routes = map(object({ name = string address_prefix = string From d877740ff0452b5579a67a98f6d9ba9b826ec955 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:50:47 +0100 Subject: [PATCH 13/49] refactor(module/natgw)!: module refactor and adjusted examples (#358) --- examples/common_vmseries/main.tf | 19 +- examples/common_vmseries/variables.tf | 70 ++-- .../common_vmseries_and_autoscale/main.tf | 19 +- .../variables.tf | 70 ++-- examples/dedicated_vmseries/main.tf | 19 +- examples/dedicated_vmseries/variables.tf | 70 ++-- .../example.tfvars | 14 +- .../dedicated_vmseries_and_autoscale/main.tf | 19 +- .../variables.tf | 70 ++-- examples/natgw/example.tfvars | 62 +++ examples/natgw/main.tf | 61 +++ examples/natgw/variables.tf | 176 +++++++++ examples/natgw/versions.tf | 22 ++ examples/standalone_vmseries/main.tf | 19 +- examples/standalone_vmseries/variables.tf | 70 ++-- modules/natgw/.header.md | 32 ++ modules/natgw/README.md | 361 ++++++++++++++---- modules/natgw/main.tf | 48 ++- modules/natgw/outputs.tf | 6 +- modules/natgw/variables.tf | 178 ++++++--- modules/natgw/versions.tf | 2 +- 21 files changed, 1071 insertions(+), 336 deletions(-) create mode 100644 examples/natgw/example.tfvars create mode 100644 examples/natgw/main.tf create mode 100644 examples/natgw/variables.tf create mode 100644 examples/natgw/versions.tf create mode 100644 modules/natgw/.header.md diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 75a2cf3a..643b8d43 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -69,23 +69,16 @@ module "natgw" { for_each = var.natgws - create_natgw = try(each.value.create_natgw, true) - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) - idle_timeout = try(each.value.idle_timeout, null) + idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - create_pip = try(each.value.create_pip, true) - existing_pip_name = try(each.value.existing_pip_name, null) - existing_pip_resource_group_name = try(each.value.existing_pip_resource_group_name, null) - - create_pip_prefix = try(each.value.create_pip_prefix, false) - pip_prefix_length = try(each.value.create_pip_prefix, false) ? try(each.value.pip_prefix_length, null) : null - existing_pip_prefix_name = try(each.value.existing_pip_prefix_name, null) - existing_pip_prefix_resource_group_name = try(each.value.existing_pip_prefix_resource_group_name, null) - + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) tags = var.tags depends_on = [module.vnet] diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 286db4d2..d578a5e5 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -119,41 +119,65 @@ variable "vnets" { variable "natgws" { description = <<-EOF - A map defining Nat Gateways. - - Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. + A map defining NAT Gateways. + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + Following properties are supported: - - - `name` : a name of the newly created NatGW. - - `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module. - - `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one). - - `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone. - - `idle_timeout` : connection IDLE timeout in minutes, for newly created resources - - `vnet_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to. - - `subnet_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet_name`. - - `create_pip` : (default: `true`) create a Public IP that will be attached to a NatGW - - `existing_pip_name` : when `create_pip` is set to `false`, source and attach and existing Public IP to the NatGW - - `existing_pip_resource_group_name` : when `create_pip` is set to `false`, name of the Resource Group hosting the existing Public IP - - `create_pip_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW. - - `pip_prefix_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription. - - `existing_pip_prefix_name` : when `create_pip_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW - - `existing_pip_prefix_resource_group_name` : when `create_pip_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit-vnet" - subnet_keys = ["public"] - zone = 1 + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } } } ``` EOF default = {} - type = any + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index d5509872..452b7c35 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -62,23 +62,16 @@ module "natgw" { for_each = var.natgws - create_natgw = try(each.value.create_natgw, true) - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) - idle_timeout = try(each.value.idle_timeout, null) + idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - create_pip = try(each.value.create_pip, true) - existing_pip_name = try(each.value.existing_pip_name, null) - existing_pip_resource_group_name = try(each.value.existing_pip_resource_group_name, null) - - create_pip_prefix = try(each.value.create_pip_prefix, false) - pip_prefix_length = try(each.value.create_pip_prefix, false) ? try(each.value.pip_prefix_length, null) : null - existing_pip_prefix_name = try(each.value.existing_pip_prefix_name, null) - existing_pip_prefix_resource_group_name = try(each.value.existing_pip_prefix_resource_group_name, null) - + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) tags = var.tags depends_on = [module.vnet] diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 8abc9d67..3e98896e 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -119,41 +119,65 @@ variable "vnets" { variable "natgws" { description = <<-EOF - A map defining Nat Gateways. - - Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. + A map defining NAT Gateways. + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + Following properties are supported: - - - `name` : a name of the newly created NatGW. - - `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module. - - `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one). - - `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone. - - `idle_timeout` : connection IDLE timeout in minutes, for newly created resources - - `vnet_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to. - - `subnet_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet_name`. - - `create_pip` : (default: `true`) create a Public IP that will be attached to a NatGW - - `existing_pip_name` : when `create_pip` is set to `false`, source and attach and existing Public IP to the NatGW - - `existing_pip_resource_group_name` : when `create_pip` is set to `false`, name of the Resource Group hosting the existing Public IP - - `create_pip_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW. - - `pip_prefix_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription. - - `existing_pip_prefix_name` : when `create_pip_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW - - `existing_pip_prefix_resource_group_name` : when `create_pip_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit-vnet" - subnet_keys = ["public"] - zone = 1 + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } } } ``` EOF default = {} - type = any + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) } diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index bb8e58d5..ff2cb081 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -68,23 +68,16 @@ module "natgw" { for_each = var.natgws - create_natgw = try(each.value.create_natgw, true) - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) - idle_timeout = try(each.value.idle_timeout, null) + idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - create_pip = try(each.value.create_pip, true) - existing_pip_name = try(each.value.existing_pip_name, null) - existing_pip_resource_group_name = try(each.value.existing_pip_resource_group_name, null) - - create_pip_prefix = try(each.value.create_pip_prefix, false) - pip_prefix_length = try(each.value.create_pip_prefix, false) ? try(each.value.pip_prefix_length, null) : null - existing_pip_prefix_name = try(each.value.existing_pip_prefix_name, null) - existing_pip_prefix_resource_group_name = try(each.value.existing_pip_prefix_resource_group_name, null) - + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) tags = var.tags depends_on = [module.vnet] diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index cb3fcaae..ee6c1e3a 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -119,41 +119,65 @@ variable "vnets" { variable "natgws" { description = <<-EOF - A map defining Nat Gateways. - - Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. + A map defining NAT Gateways. + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + Following properties are supported: - - - `name` : a name of the newly created NatGW. - - `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module. - - `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one). - - `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone. - - `idle_timeout` : connection IDLE timeout in minutes, for newly created resources - - `vnet_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to. - - `subnet_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet_name`. - - `create_pip` : (default: `true`) create a Public IP that will be attached to a NatGW - - `existing_pip_name` : when `create_pip` is set to `false`, source and attach and existing Public IP to the NatGW - - `existing_pip_resource_group_name` : when `create_pip` is set to `false`, name of the Resource Group hosting the existing Public IP - - `create_pip_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW. - - `pip_prefix_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription. - - `existing_pip_prefix_name` : when `create_pip_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW - - `existing_pip_prefix_resource_group_name` : when `create_pip_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit-vnet" - subnet_keys = ["public"] - zone = 1 + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } } } ``` EOF default = {} - type = any + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) } diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 40ee9b2b..8b7cd387 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -126,12 +126,14 @@ vnets = { natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit" - subnet_keys = ["public", "management"] - create_pip = false - create_pip_prefix = true - pip_prefix_length = 29 + name = "public-natgw" + vnet_key = "transit" + subnet_keys = ["public", "management"] + public_ip_prefix = { + create = true + name = "public-natgw-ippre" + length = 29 + } } } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index d5509872..452b7c35 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -62,23 +62,16 @@ module "natgw" { for_each = var.natgws - create_natgw = try(each.value.create_natgw, true) - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) - idle_timeout = try(each.value.idle_timeout, null) + idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - create_pip = try(each.value.create_pip, true) - existing_pip_name = try(each.value.existing_pip_name, null) - existing_pip_resource_group_name = try(each.value.existing_pip_resource_group_name, null) - - create_pip_prefix = try(each.value.create_pip_prefix, false) - pip_prefix_length = try(each.value.create_pip_prefix, false) ? try(each.value.pip_prefix_length, null) : null - existing_pip_prefix_name = try(each.value.existing_pip_prefix_name, null) - existing_pip_prefix_resource_group_name = try(each.value.existing_pip_prefix_resource_group_name, null) - + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) tags = var.tags depends_on = [module.vnet] diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 8abc9d67..3e98896e 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -119,41 +119,65 @@ variable "vnets" { variable "natgws" { description = <<-EOF - A map defining Nat Gateways. - - Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. + A map defining NAT Gateways. + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + Following properties are supported: - - - `name` : a name of the newly created NatGW. - - `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module. - - `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one). - - `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone. - - `idle_timeout` : connection IDLE timeout in minutes, for newly created resources - - `vnet_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to. - - `subnet_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet_name`. - - `create_pip` : (default: `true`) create a Public IP that will be attached to a NatGW - - `existing_pip_name` : when `create_pip` is set to `false`, source and attach and existing Public IP to the NatGW - - `existing_pip_resource_group_name` : when `create_pip` is set to `false`, name of the Resource Group hosting the existing Public IP - - `create_pip_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW. - - `pip_prefix_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription. - - `existing_pip_prefix_name` : when `create_pip_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW - - `existing_pip_prefix_resource_group_name` : when `create_pip_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit-vnet" - subnet_keys = ["public"] - zone = 1 + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } } } ``` EOF default = {} - type = any + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) } diff --git a/examples/natgw/example.tfvars b/examples/natgw/example.tfvars new file mode 100644 index 00000000..d3d6a122 --- /dev/null +++ b/examples/natgw/example.tfvars @@ -0,0 +1,62 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "transit-vnet-common" +name_prefix = "ac-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + + +# --- VNET PART --- # +vnets = { + "transit" = { + name = "transit" + address_space = ["10.0.0.0/25"] + network_security_groups = { + "management" = { + name = "mgmt-nsg" + rules = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["0.0.0.0/0"] # 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"] + } + } + } + } + subnets = { + "management" = { + name = "mgmt-snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group_key = "management" + } + } + } +} + + +# --- NATGW PART --- # +natgws = { + "natgw" = { + create_natgw = true + name = "natgw" + vnet_key = "transit" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + public_ip_prefix = { + create = true + name = "natgw-pip-prefix" + length = 31 + } + } +} \ No newline at end of file diff --git a/examples/natgw/main.tf b/examples/natgw/main.tf new file mode 100644 index 00000000..6adf2680 --- /dev/null +++ b/examples/natgw/main.tf @@ -0,0 +1,61 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} + +module "natgw" { + source = "../../modules/natgw" + + for_each = var.natgws + + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + zone = try(each.value.zone, null) + idle_timeout = each.value.idle_timeout + subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } + + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + + tags = var.tags + depends_on = [module.vnet] +} \ No newline at end of file diff --git a/examples/natgw/variables.tf b/examples/natgw/variables.tf new file mode 100644 index 00000000..e5478074 --- /dev/null +++ b/examples/natgw/variables.tf @@ -0,0 +1,176 @@ +### 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 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 its 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +} + + +### NATGW +variable "natgws" { + description = <<-EOF + A map defining NAT Gateways. + + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + + Following properties are supported: + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + + Example: + ``` + natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } + } + ``` + EOF + default = {} + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +} \ No newline at end of file diff --git a/examples/natgw/versions.tf b/examples/natgw/versions.tf new file mode 100644 index 00000000..50ff584a --- /dev/null +++ b/examples/natgw/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.3, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 2813961e..19dd23cc 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -69,23 +69,16 @@ module "natgw" { for_each = var.natgws - create_natgw = try(each.value.create_natgw, true) - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) - idle_timeout = try(each.value.idle_timeout, null) + idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - create_pip = try(each.value.create_pip, true) - existing_pip_name = try(each.value.existing_pip_name, null) - existing_pip_resource_group_name = try(each.value.existing_pip_resource_group_name, null) - - create_pip_prefix = try(each.value.create_pip_prefix, false) - pip_prefix_length = try(each.value.create_pip_prefix, false) ? try(each.value.pip_prefix_length, null) : null - existing_pip_prefix_name = try(each.value.existing_pip_prefix_name, null) - existing_pip_prefix_resource_group_name = try(each.value.existing_pip_prefix_resource_group_name, null) - + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) tags = var.tags depends_on = [module.vnet] diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index cfd44a09..3ff0c377 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -119,41 +119,65 @@ variable "vnets" { variable "natgws" { description = <<-EOF - A map defining Nat Gateways. - - Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. + A map defining NAT Gateways. + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + Following properties are supported: - - - `name` : a name of the newly created NatGW. - - `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module. - - `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one). - - `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone. - - `idle_timeout` : connection IDLE timeout in minutes, for newly created resources - - `vnet_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to. - - `subnet_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet_name`. - - `create_pip` : (default: `true`) create a Public IP that will be attached to a NatGW - - `existing_pip_name` : when `create_pip` is set to `false`, source and attach and existing Public IP to the NatGW - - `existing_pip_resource_group_name` : when `create_pip` is set to `false`, name of the Resource Group hosting the existing Public IP - - `create_pip_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW. - - `pip_prefix_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription. - - `existing_pip_prefix_name` : when `create_pip_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW - - `existing_pip_prefix_resource_group_name` : when `create_pip_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` natgws = { "natgw" = { - name = "public-natgw" - vnet_key = "transit-vnet" - subnet_keys = ["public"] - zone = 1 + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } } } ``` EOF default = {} - type = any + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) } diff --git a/modules/natgw/.header.md b/modules/natgw/.header.md new file mode 100644 index 00000000..9ddbc225 --- /dev/null +++ b/modules/natgw/.header.md @@ -0,0 +1,32 @@ +# NAT Gateway module + +## Purpose + +Terraform module used to deploy Azure NAT Gateway. For limitations and +zone-resiliency considerations please refer to [Microsoft +documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-overview). + +This module can be used to either create a new NAT Gateway or to connect +an existing one with subnets deployed using (for example) the [VNET +module](../vnet/README.md). + +## Usage + +To deploy this resource in it's minimum configuration following code +snippet can be used (assuming that the VNET module is used to deploy VNET +and Subnets): + +```hcl +module "natgw" { + source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/natgw" + + name = "NATGW_name" + resource_group_name = "resource_group_name" + location = "region_name" + subnet_ids = { "a_subnet_name" = +module.vnet.subnet_ids["a_subnet_name"] } +} +``` + +This will create a NAT Gateway in with a single Public IP in a zone chosen +by Azure. \ No newline at end of file diff --git a/modules/natgw/README.md b/modules/natgw/README.md index b85842f8..3b5aae7d 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -1,85 +1,310 @@ +<!-- BEGIN_TF_DOCS --> # NAT Gateway module ## Purpose +Terraform module used to deploy Azure NAT Gateway. For limitations and +zone-resiliency considerations please refer to [Microsoft +documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-overview). -Terraform module used to deploy Azure NAT Gateway. For limitations and zone-resiliency considerations please refer to [Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-overview). - -This module can be used to either create a new NAT Gateway or to connect an existing one with subnets deployed using (for example) the [VNET module](../vnet/README.md). +This module can be used to either create a new NAT Gateway or to connect +an existing one with subnets deployed using (for example) the [VNET +module](../vnet/README.md). ## Usage -To deploy this resource in it's minimum configuration following code snippet can be used (assuming that the VNET module is used to deploy VNET and Subnets): +To deploy this resource in it's minimum configuration following code +snippet can be used (assuming that the VNET module is used to deploy VNET +and Subnets): -```terraform +```hcl module "natgw" { source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/natgw" name = "NATGW_name" resource_group_name = "resource_group_name" location = "region_name" - subnet_ids = { "a_subnet_name" = module.vnet.subnet_ids["a_subnet_name"] } + subnet_ids = { "a_subnet_name" = +module.vnet.subnet_ids["a_subnet_name"] } +} +``` + +This will create a NAT Gateway in with a single Public IP in a zone chosen +by Azure. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | Name of a NAT Gateway. +[`resource_group_name`](#resource_group_name) | `string` | Name of a Resource Group hosting the NAT Gateway (either the existing one or the one that will be created). +[`location`](#location) | `string` | Azure region. +[`subnet_ids`](#subnet_ids) | `map` | A map of subnet IDs what will be bound with this NAT Gateway. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | A map of tags that will be assigned to resources created by this module. +[`create_natgw`](#create_natgw) | `bool` | Triggers creation of a NAT Gateway when set to `true`. +[`zone`](#zone) | `string` | Controls whether the NAT Gateway will be bound to a specific zone or not. +[`idle_timeout`](#idle_timeout) | `number` | Connection IDLE timeout in minutes (up to 120, by default 4). +[`public_ip`](#public_ip) | `object` | A map defining a Public IP resource. +[`public_ip_prefix`](#public_ip_prefix) | `object` | A map defining a Public IP Prefix resource. + + + +## Module's Outputs + +Name | Description +--- | --- +`natgw_pip` | Public IP associated with the NAT Gateway. +`natgw_pip_prefix` | Public IP Prefix associated with the NAT Gateway. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.25 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.25 + + + + +Resources used in this module: + +- `nat_gateway` (managed) +- `nat_gateway_public_ip_association` (managed) +- `nat_gateway_public_ip_prefix_association` (managed) +- `public_ip` (managed) +- `public_ip_prefix` (managed) +- `subnet_nat_gateway_association` (managed) +- `nat_gateway` (data) +- `public_ip` (data) +- `public_ip_prefix` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +Name of a NAT Gateway. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +Name of a Resource Group hosting the NAT Gateway (either the existing one or the one that will be created). + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +Azure region. Only for newly created resources. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +#### subnet_ids + +A map of subnet IDs what will be bound with this NAT Gateway. + +Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name. + + +Type: map(string) + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +### Optional Inputs + + + + + +#### tags + +A map of tags that will be assigned to resources created by this module. Only for newly created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### create_natgw + +Triggers creation of a NAT Gateway when set to `true`. + +Set it to `false` to source an existing resource. In this 'mode' the module will only bind an existing NAT Gateway to specified +subnets. + + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### zone + +Controls whether the NAT Gateway will be bound to a specific zone or not. This is a string with the zone number or `null`. Only +for newly created resources. + +NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to +decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. Keep in mind +that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But +if that zone becomes unavailable, resources in other zones will lose internet connectivity. + +For design considerations, limitation and examples of zone-resiliency architecture please refer to [Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-availability-zones). + + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### idle_timeout + +Connection IDLE timeout in minutes (up to 120, by default 4). Only for newly created resources. + +Type: number + +Default value: `4` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### public_ip + +A map defining a Public IP resource. + +List of available properties: + +- `create` - (`bool`, required) controls whether a Public IP is created, sourced, or not used at all. +- `name` - (`string`, required) name of a created or sourced Public IP. +- `resource_group_name` - (`string`, optional) name of a resource group hosting the sourced Public IP resource, ignored when + `create = true`. + +The module operates in 3 modes, depending on combination of `create` and `name` properties: + +`create` | `name` | operation +--- | --- | --- +`true` | `!null` | a Public IP resource is created in a resource group of the NAT Gateway +`false` | `!null` | a Public IP resource is sourced from a resource group of the NAT Gateway, the resource group can be + overridden with `resource_group_name` property +`false` | `null` | a Public IP resource will not be created or sourced at all + +Example: + +```hcl +# create a new Public IP +public_ip = { + create = true + name = "new-public-ip-name" +} + +# source an existing Public IP from an external resource group +public_ip = { + create = false + name = "existing-public-ip-name" + resource_group_name = "external-rg-name" } ``` -This will create a NAT Gateway in with a single Public IP in a zone chosen by Azure. - -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_nat_gateway.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway) | resource | -| [azurerm_nat_gateway_public_ip_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_association) | resource | -| [azurerm_nat_gateway_public_ip_prefix_association.nat_ips](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_prefix_association) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_public_ip_prefix.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip_prefix) | resource | -| [azurerm_subnet_nat_gateway_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association) | resource | -| [azurerm_nat_gateway.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/nat_gateway) | data source | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip) | data source | -| [azurerm_public_ip_prefix.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip_prefix) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name"></a> [name](#input\_name) | Name of a NAT Gateway. | `string` | n/a | yes | -| <a name="input_create_natgw"></a> [create\_natgw](#input\_create\_natgw) | Triggers creation of a NAT Gateway when set to `true`.<br><br>Set it to `false` to source an existing resource. In this 'mode' the module will only bind an existing NAT Gateway to specified subnets. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of a Resource Group hosting the NAT Gateway (either the existing one or the one that will be created). | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Azure region. Only for newly created resources. | `string` | n/a | yes | -| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags that will be assigned to resources created by this module. Only for newly created resources. | `map(string)` | `{}` | no | -| <a name="input_zone"></a> [zone](#input\_zone) | Controls if the NAT Gateway will be bound to a specific zone or not. This is a string with the zone number or `null`. Only for newly created resources.<br><br>NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. <br>Keep in mind that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But if that zone becomes unavailable resources in other zones will loose internet connectivity. <br><br>For design considerations, limitation and examples of zone-resiliency architecture please refer to [Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-availability-zones). | `string` | `null` | no | -| <a name="input_idle_timeout"></a> [idle\_timeout](#input\_idle\_timeout) | Connection IDLE timeout in minutes. Only for newly created resources. | `number` | `null` | no | -| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | A map of subnet IDs what will be bound with this NAT Gateway. Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name. | `map(string)` | n/a | yes | -| <a name="input_create_pip"></a> [create\_pip](#input\_create\_pip) | Set `true` to create a Public IP resource that will be connected to newly created NAT Gateway. Not used when NAT Gateway is only sourced.<br><br>Setting this property to `false` has two meanings:<br>* when `existing_pip_name` is `null` simply no Public IP will be created<br>* when `existing_pip_name` is set to a name of an exiting Public IP resource it will be sourced and associated to this NAT Gateway. | `bool` | `true` | no | -| <a name="input_existing_pip_name"></a> [existing\_pip\_name](#input\_existing\_pip\_name) | Name of an existing Public IP resource to associate with the NAT Gateway. Only for newly created resources. | `string` | `null` | no | -| <a name="input_existing_pip_resource_group_name"></a> [existing\_pip\_resource\_group\_name](#input\_existing\_pip\_resource\_group\_name) | Name of a resource group hosting the Public IP resource specified in `existing_pip_name`. When omitted Resource Group specified in `resource_group_name` will be used. | `string` | `null` | no | -| <a name="input_create_pip_prefix"></a> [create\_pip\_prefix](#input\_create\_pip\_prefix) | Set `true` to create a Public IP Prefix resource that will be connected to newly created NAT Gateway. Not used when NAT Gateway is only sourced.<br><br>Setting this property to `false` has two meanings:<br>* when `existing_pip_prefix_name` is `null` simply no Public IP Prefix will be created<br>* when `existing_pip_prefix_name` is set to a name of an exiting Public IP Prefix resource it will be sourced and associated to this NAT Gateway. | `bool` | `false` | no | -| <a name="input_pip_prefix_length"></a> [pip\_prefix\_length](#input\_pip\_prefix\_length) | Number of bits of the Public IP Prefix. This basically specifies how many IP addresses are reserved. Azure default is `/28`.<br><br>This value can be between `0` and `31` but can be limited by limits set on Subscription level. | `number` | `null` | no | -| <a name="input_existing_pip_prefix_name"></a> [existing\_pip\_prefix\_name](#input\_existing\_pip\_prefix\_name) | Name of an existing Public IP Prefix resource to associate with the NAT Gateway. Only for newly created resources. | `string` | `null` | no | -| <a name="input_existing_pip_prefix_resource_group_name"></a> [existing\_pip\_prefix\_resource\_group\_name](#input\_existing\_pip\_prefix\_resource\_group\_name) | Name of a resource group hosting the Public IP Prefix resource specified in `existing_pip_name`. When omitted Resource Group specified in `resource_group_name` will be used. | `string` | `null` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_natgw_pip"></a> [natgw\_pip](#output\_natgw\_pip) | n/a | -| <a name="output_natgw_pip_prefix"></a> [natgw\_pip\_prefix](#output\_natgw\_pip\_prefix) | n/a | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> + +Type: + +```hcl +object({ + create = bool + name = string + resource_group_name = optional(string) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### public_ip_prefix + +A map defining a Public IP Prefix resource. + +List of available properties: + +- `create` - (`bool`, required) controls whether a Public IP Prefix is created, sourced, or not used at all. +- `name` - (`string`, required) name of a created or sourced Public IP Prefix. +- `resource_group_name` - (`string`, optional) name of a resource group hosting the sourced Public IP Prefix resource, ignored + when `create = true`. +- `length` - (`number`, optional, defaults to `28`) number of bits of the Public IP Prefix, this value can be + between `0` and `31` but can be limited on subscription level (Azure default is `/28`). + +The module operates in 3 modes, depending on combination of `create` and `name` properties: + +`create` | `name` | operation +--- | --- | --- +`true` | `!null` | a Public IP Prefix resource is created in a resource group of the NAT Gateway +`false` | `!null` | a Public IP Prefix resource is sourced from a resource group of the NAT Gateway, the resource group can be + overridden with `resource_group_name` property +`false` | `null` | a Public IP Prefix resource will not be created or sourced at all + +Example: + +```hcl +# create a new Public IP Prefix, default prefix length is `/28` +public_ip_prefix = { + create = true + name = "new-public-ip-prefix-name" +} + +# source an existing Public IP Prefix from an external resource group +public_ip = { + create = false + name = "existing-public-ip-prefix-name" + resource_group_name = "external-rg-name" +} +``` + + +Type: + +```hcl +object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number, 28) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/main.tf b/modules/natgw/main.tf index 06d94ab6..f57e39fc 100644 --- a/modules/natgw/main.tf +++ b/modules/natgw/main.tf @@ -1,7 +1,8 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - count = (var.create_natgw && var.create_pip) ? 1 : 0 + count = try(var.create_natgw && var.public_ip.create, false) ? 1 : 0 - name = "${var.name}-pip" + name = var.public_ip.name resource_group_name = var.resource_group_name location = var.location allocation_method = "Static" @@ -11,34 +12,38 @@ resource "azurerm_public_ip" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "this" { - count = (var.create_natgw && !var.create_pip && var.existing_pip_name != null) ? 1 : 0 + count = try(var.create_natgw && !var.public_ip.create && var.public_ip.name != null, false) ? 1 : 0 - name = var.existing_pip_name - resource_group_name = var.existing_pip_resource_group_name == null ? var.resource_group_name : var.existing_pip_resource_group_name + name = var.public_ip.name + resource_group_name = coalesce(var.public_ip.resource_group_name, var.resource_group_name) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip_prefix resource "azurerm_public_ip_prefix" "this" { - count = (var.create_natgw && var.create_pip_prefix) ? 1 : 0 + count = try(var.create_natgw && var.public_ip_prefix.create, false) ? 1 : 0 - name = "${var.name}-pip-prefix" + name = var.public_ip_prefix.name resource_group_name = var.resource_group_name location = var.location ip_version = "IPv4" - prefix_length = var.pip_prefix_length + prefix_length = var.public_ip_prefix.length sku = "Standard" zones = var.zone != null ? [var.zone] : null tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip_prefix data "azurerm_public_ip_prefix" "this" { - count = (var.create_natgw && !var.create_pip_prefix && var.existing_pip_prefix_name != null) ? 1 : 0 + count = try(var.create_natgw && !var.public_ip_prefix.create && var.public_ip_prefix.name != null, false) ? 1 : 0 - name = var.existing_pip_prefix_name - resource_group_name = coalesce(var.existing_pip_prefix_resource_group_name, var.resource_group_name) + name = var.public_ip_prefix.name + resource_group_name = coalesce(var.public_ip_prefix.resource_group_name, var.resource_group_name) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway resource "azurerm_nat_gateway" "this" { count = var.create_natgw ? 1 : 0 @@ -52,6 +57,7 @@ resource "azurerm_nat_gateway" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/nat_gateway data "azurerm_nat_gateway" "this" { count = var.create_natgw ? 0 : 1 @@ -59,33 +65,39 @@ data "azurerm_nat_gateway" "this" { resource_group_name = var.resource_group_name } - locals { natgw_id = var.create_natgw ? azurerm_nat_gateway.this[0].id : data.azurerm_nat_gateway.this[0].id - pip = var.create_natgw ? ( - var.create_pip ? azurerm_public_ip.this[0] : try(data.azurerm_public_ip.this[0], null) + pip = try(azurerm_public_ip.this[0], data.azurerm_public_ip.this[0], null) + + pip_prefix = try(azurerm_public_ip_prefix.this[0], data.azurerm_public_ip_prefix.this[0], null) + + /* pip = var.create_natgw ? ( + try(var.public_ip.create, false) ? azurerm_public_ip.this[0] : try(data.azurerm_public_ip.this[0], null) ) : null pip_prefix = var.create_natgw ? ( - var.create_pip_prefix ? azurerm_public_ip_prefix.this[0] : try(data.azurerm_public_ip_prefix.this[0], null) - ) : null + try(var.public_ip_prefix.create, false) ? azurerm_public_ip_prefix.this[0] : try(data.azurerm_public_ip_prefix.this[0], null) + ) : null */ } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_association resource "azurerm_nat_gateway_public_ip_association" "this" { - count = var.create_natgw && local.pip != null ? 1 : 0 + count = var.create_natgw && var.public_ip != null ? 1 : 0 nat_gateway_id = local.natgw_id public_ip_address_id = local.pip.id } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_prefix_association resource "azurerm_nat_gateway_public_ip_prefix_association" "nat_ips" { - count = var.create_natgw && local.pip_prefix != null ? 1 : 0 + count = var.create_natgw && var.public_ip_prefix != null ? 1 : 0 nat_gateway_id = local.natgw_id public_ip_prefix_id = local.pip_prefix.id } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association resource "azurerm_subnet_nat_gateway_association" "this" { for_each = var.subnet_ids diff --git a/modules/natgw/outputs.tf b/modules/natgw/outputs.tf index 95641fe0..e217910b 100644 --- a/modules/natgw/outputs.tf +++ b/modules/natgw/outputs.tf @@ -1,7 +1,9 @@ output "natgw_pip" { - value = try(local.pip.ip_address, null) + description = "Public IP associated with the NAT Gateway." + value = try(local.pip.ip_address, null) } output "natgw_pip_prefix" { - value = try(local.pip_prefix.ip_prefix, null) + description = "Public IP Prefix associated with the NAT Gateway." + value = try(local.pip_prefix.ip_prefix, null) } diff --git a/modules/natgw/variables.tf b/modules/natgw/variables.tf index 6a1e0bac..8643de5a 100644 --- a/modules/natgw/variables.tf +++ b/modules/natgw/variables.tf @@ -3,16 +3,6 @@ variable "name" { type = string } -variable "create_natgw" { - description = <<-EOF - Triggers creation of a NAT Gateway when set to `true`. - - Set it to `false` to source an existing resource. In this 'mode' the module will only bind an existing NAT Gateway to specified subnets. - EOF - default = true - type = bool -} - variable "resource_group_name" { description = "Name of a Resource Group hosting the NAT Gateway (either the existing one or the one that will be created)." type = string @@ -29,84 +19,150 @@ variable "tags" { type = map(string) } +variable "create_natgw" { + description = <<-EOF + Triggers creation of a NAT Gateway when set to `true`. + + Set it to `false` to source an existing resource. In this 'mode' the module will only bind an existing NAT Gateway to specified + subnets. + EOF + default = true + type = bool +} + variable "zone" { description = <<-EOF - Controls if the NAT Gateway will be bound to a specific zone or not. This is a string with the zone number or `null`. Only for newly created resources. + Controls whether the NAT Gateway will be bound to a specific zone or not. This is a string with the zone number or `null`. Only + for newly created resources. - NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. - Keep in mind that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But if that zone becomes unavailable resources in other zones will loose internet connectivity. + NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to + decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. Keep in mind + that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But + if that zone becomes unavailable, resources in other zones will lose internet connectivity. For design considerations, limitation and examples of zone-resiliency architecture please refer to [Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-availability-zones). EOF default = null type = string + validation { + condition = (var.zone == null || can(regex("^[1-3]$", var.zone))) + error_message = "The `zone` variable should have value of either: \"1\", \"2\" or \"3\"." + } } variable "idle_timeout" { - description = "Connection IDLE timeout in minutes. Only for newly created resources." - default = null + description = "Connection IDLE timeout in minutes (up to 120, by default 4). Only for newly created resources." + default = 4 type = number + validation { + condition = (var.idle_timeout >= 1 && var.idle_timeout <= 120) + error_message = "The `idle_timeout` variable should be a number between 1 and 120." + } } variable "subnet_ids" { - description = "A map of subnet IDs what will be bound with this NAT Gateway. Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name." + description = <<-EOF + A map of subnet IDs what will be bound with this NAT Gateway. + + Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name. + EOF type = map(string) } -variable "create_pip" { +variable "public_ip" { description = <<-EOF - Set `true` to create a Public IP resource that will be connected to newly created NAT Gateway. Not used when NAT Gateway is only sourced. - - Setting this property to `false` has two meanings: - * when `existing_pip_name` is `null` simply no Public IP will be created - * when `existing_pip_name` is set to a name of an exiting Public IP resource it will be sourced and associated to this NAT Gateway. - EOF - default = true - type = bool -} + A map defining a Public IP resource. -variable "existing_pip_name" { - description = "Name of an existing Public IP resource to associate with the NAT Gateway. Only for newly created resources." - default = null - type = string -} + List of available properties: -variable "existing_pip_resource_group_name" { - description = "Name of a resource group hosting the Public IP resource specified in `existing_pip_name`. When omitted Resource Group specified in `resource_group_name` will be used." - default = null - type = string -} + - `create` - (`bool`, required) controls whether a Public IP is created, sourced, or not used at all. + - `name` - (`string`, required) name of a created or sourced Public IP. + - `resource_group_name` - (`string`, optional) name of a resource group hosting the sourced Public IP resource, ignored when + `create = true`. -variable "create_pip_prefix" { - description = <<-EOF - Set `true` to create a Public IP Prefix resource that will be connected to newly created NAT Gateway. Not used when NAT Gateway is only sourced. + The module operates in 3 modes, depending on combination of `create` and `name` properties: - Setting this property to `false` has two meanings: - * when `existing_pip_prefix_name` is `null` simply no Public IP Prefix will be created - * when `existing_pip_prefix_name` is set to a name of an exiting Public IP Prefix resource it will be sourced and associated to this NAT Gateway. + `create` | `name` | operation + --- | --- | --- + `true` | `!null` | a Public IP resource is created in a resource group of the NAT Gateway + `false` | `!null` | a Public IP resource is sourced from a resource group of the NAT Gateway, the resource group can be + overridden with `resource_group_name` property + `false` | `null` | a Public IP resource will not be created or sourced at all + + Example: + + ```hcl + # create a new Public IP + public_ip = { + create = true + name = "new-public-ip-name" + } + + # source an existing Public IP from an external resource group + public_ip = { + create = false + name = "existing-public-ip-name" + resource_group_name = "external-rg-name" + } + ``` EOF - default = false - type = bool + default = null + type = object({ + create = bool + name = string + resource_group_name = optional(string) + }) } -variable "pip_prefix_length" { +variable "public_ip_prefix" { description = <<-EOF - Number of bits of the Public IP Prefix. This basically specifies how many IP addresses are reserved. Azure default is `/28`. - - This value can be between `0` and `31` but can be limited by limits set on Subscription level. + A map defining a Public IP Prefix resource. + + List of available properties: + + - `create` - (`bool`, required) controls whether a Public IP Prefix is created, sourced, or not used at all. + - `name` - (`string`, required) name of a created or sourced Public IP Prefix. + - `resource_group_name` - (`string`, optional) name of a resource group hosting the sourced Public IP Prefix resource, ignored + when `create = true`. + - `length` - (`number`, optional, defaults to `28`) number of bits of the Public IP Prefix, this value can be + between `0` and `31` but can be limited on subscription level (Azure default is `/28`). + + The module operates in 3 modes, depending on combination of `create` and `name` properties: + + `create` | `name` | operation + --- | --- | --- + `true` | `!null` | a Public IP Prefix resource is created in a resource group of the NAT Gateway + `false` | `!null` | a Public IP Prefix resource is sourced from a resource group of the NAT Gateway, the resource group can be + overridden with `resource_group_name` property + `false` | `null` | a Public IP Prefix resource will not be created or sourced at all + + Example: + + ```hcl + # create a new Public IP Prefix, default prefix length is `/28` + public_ip_prefix = { + create = true + name = "new-public-ip-prefix-name" + } + + # source an existing Public IP Prefix from an external resource group + public_ip = { + create = false + name = "existing-public-ip-prefix-name" + resource_group_name = "external-rg-name" + } + ``` EOF default = null - type = number -} - -variable "existing_pip_prefix_name" { - description = "Name of an existing Public IP Prefix resource to associate with the NAT Gateway. Only for newly created resources." - default = null - type = string -} - -variable "existing_pip_prefix_resource_group_name" { - description = "Name of a resource group hosting the Public IP Prefix resource specified in `existing_pip_name`. When omitted Resource Group specified in `resource_group_name` will be used." - default = null - type = string + type = object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number, 28) + }) + validation { + condition = (var.public_ip_prefix == null || + (try(var.public_ip_prefix.length, -1) >= 0 && try(var.public_ip_prefix.length, 32) <= 31)) + error_message = "The `length` property should be a number between 0 and 31." + } } diff --git a/modules/natgw/versions.tf b/modules/natgw/versions.tf index 501042ff..2c796798 100644 --- a/modules/natgw/versions.tf +++ b/modules/natgw/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" From f24fbea77982bfa091dbbb617444860c663b0074 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Tue, 12 Dec 2023 11:06:03 +0100 Subject: [PATCH 14/49] refactor(module/appgw): Refactor module Application Gateway and adjust examples (#345) --- .gitignore | 2 +- .terraform-docs.yml | 6 +- examples/appgw/.header.md | 5 + examples/appgw/README.md | 389 ++++ examples/appgw/example.tfvars | 694 +++++++ examples/appgw/main.tf | 85 + examples/appgw/main_test.go | 62 + examples/appgw/outputs.tf | 0 examples/appgw/variables.tf | 290 +++ examples/appgw/versions.tf | 22 + examples/common_vmseries/example.tfvars | 49 +- examples/common_vmseries/main.tf | 49 +- examples/common_vmseries/variables.tf | 168 +- .../example.tfvars | 39 +- .../common_vmseries_and_autoscale/main.tf | 35 +- .../variables.tf | 170 +- examples/dedicated_vmseries/example.tfvars | 51 + examples/dedicated_vmseries/main.tf | 47 +- examples/dedicated_vmseries/variables.tf | 168 +- .../example.tfvars | 40 +- .../dedicated_vmseries_and_autoscale/main.tf | 35 +- .../variables.tf | 170 +- examples/standalone_vmseries/main.tf | 47 +- examples/standalone_vmseries/variables.tf | 168 +- examples/virtual_network_gateway/.header.md | 4 +- examples/virtual_network_gateway/README.md | 4 +- examples/virtual_network_gateway/main_test.go | 62 + modules/appgw/.header.md | 799 ++++++++ modules/appgw/README.md | 1717 +++++++++++++---- modules/appgw/main.tf | 263 +-- modules/appgw/outputs.tf | 4 +- modules/appgw/variables.tf | 691 ++++++- modules/appgw/versions.tf | 2 +- 33 files changed, 5497 insertions(+), 840 deletions(-) create mode 100644 examples/appgw/.header.md create mode 100644 examples/appgw/README.md create mode 100644 examples/appgw/example.tfvars create mode 100644 examples/appgw/main.tf create mode 100644 examples/appgw/main_test.go create mode 100644 examples/appgw/outputs.tf create mode 100644 examples/appgw/variables.tf create mode 100644 examples/appgw/versions.tf create mode 100644 examples/virtual_network_gateway/main_test.go create mode 100644 modules/appgw/.header.md diff --git a/.gitignore b/.gitignore index e2fcf46c..41853f5f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,4 @@ terraform.tfvars.json *.tfplan # **/ *bootstrap.xml - +examples/appgw/files/* \ No newline at end of file diff --git a/.terraform-docs.yml b/.terraform-docs.yml index fcbe8715..7816f7e6 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -90,16 +90,16 @@ content: |- {{ range .Module.Inputs }} {{ if .Required -}} #### {{ .Name }} - + {{ .Description }} - + Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} ```hcl {{ .Type }} ``` {{ end }} - + <sup>[back to list](#modules-required-inputs)</sup> {{ end }} {{- end }} diff --git a/examples/appgw/.header.md b/examples/appgw/.header.md new file mode 100644 index 00000000..97cfc566 --- /dev/null +++ b/examples/appgw/.header.md @@ -0,0 +1,5 @@ +# APP GW module sample + +A sample of using a APP GW module with the new variables layout and usage of `optional` keyword. + +The `README` is also in new, document-style format. diff --git a/examples/appgw/README.md b/examples/appgw/README.md new file mode 100644 index 00000000..008b3abd --- /dev/null +++ b/examples/appgw/README.md @@ -0,0 +1,389 @@ +<!-- BEGIN_TF_DOCS --> +# APP GW module sample + +A sample of using a APP GW 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + +## 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. +`appgw` | - | ../../modules/appgw | Create Application Gateay + + +Resources used in this module: + +- `public_ip` (managed) +- `resource_group` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +Following properties are supported: +- `name` - (`string`, required) name of the Application Gateway. +- `public_ip` - (`string`, required) public IP address. +- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. +- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. + This has to be a subnet dedicated to Application Gateways v2. +- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, + which Application Gateway uses to retrieve certificates from Key Vault. +- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported +- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway +- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - + this property is used by both: the Application Gateway and the Public IP created + in front of the AppGW. +- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name +- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address + will be used in backend pool +- `listeners` - (`map`, required) map of listeners (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `backend_pool` - (`object`, optional) backend pool (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `backends` - (`map`, optional) map of backends (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `probes` - (`map`, optional) map of probes (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `rewrites` - (`map`, optional) map of rewrites (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `rules` - (`map`, required) map of rules (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `redirects` - (`map`, optional) map of redirects (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `url_path_maps` - (`map`, optional) map of URL path maps (refer to + [module documentation](../../modules/appgw/README.md) for details) +- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` +- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, + for `ssl_policy_type` set to `Custom` +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, + for `ssl_policy_type` set to `Custom` +- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS + listeners by providing a name of the profile in the `ssl_profile_name` property + + +Type: + +```hcl +map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars new file mode 100644 index 00000000..96b37a39 --- /dev/null +++ b/examples/appgw/example.tfvars @@ -0,0 +1,694 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "appgw-example" +name_prefix = "sczech-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + + +# --- VNET PART --- # +vnets = { + transit = { + name = "transit" + address_space = ["10.0.0.0/24"] + network_security_groups = {} + route_tables = { + "rt" = { + name = "rt" + routes = { + "udr" = { + name = "udr" + address_prefix = "10.0.0.0/8" + next_hop_type = "None" + } + } + } + } + subnets = { + "appgw" = { + name = "appgw" + address_prefixes = ["10.0.0.0/25"] + route_table_key = "rt" + } + } + } +} + +# --- APPGW PART --- # + +appgws = { + "public-http-minimum" = { + name = "appgw-http-minimum" + public_ip = { + name = "pip-http-minimum" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { + "xff-strip-port" = { + name = "minimum-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } + } + "public-http-existing" = { + name = "appgw-http-existing" + public_ip = { + name = "pip-existing" + create = false + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + backends = { + existing = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + existing = { + name = "existing-listener" + port = 80 + } + } + rewrites = { + existing = { + name = "existing-set" + rules = { + "xff-strip-port" = { + name = "existing-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + existing = { + name = "existing-rule" + priority = 1 + backend = "existing" + listener = "existing" + rewrite = "existing" + } + } + } + "public-http-autoscale" = { + name = "appgw-http-autoscale" + public_ip = { + name = "pip-http-autoscale" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + autoscale = { + min = 2 + max = 20 + } + } + backends = { + http = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + http = { + name = "http-listener" + port = 80 + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + } + } + } + "public-waf" = { + name = "appgw-waf" + public_ip = { + name = "pip-waf" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + waf = { + prevention_mode = true + rule_set_type = "OWASP" + rule_set_version = "3.2" + } + backends = { + waf = { + name = "waf-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + waf = { + name = "waf-listener" + port = 80 + } + } + rewrites = { + waf = { + name = "waf-set" + rules = { + "xff-strip-port" = { + name = "waf-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "waf-rule" + priority = 1 + backend = "waf" + listener = "waf" + rewrite = "waf" + } + } + } + # If you test example for Application Gateway with SSL, + # you need to created directory files and create keys and certs using commands: + # 1. Create CA private key and certificate: + # openssl genrsa 2048 > ca-key1.pem + # openssl req -new -x509 -nodes -days 365000 -key ca-key1.pem -out ca-cert1.pem + # openssl genrsa 2048 > ca-key2.pem + # openssl req -new -x509 -nodes -days 365000 -key ca-key2.pem -out ca-cert2.pem + # 2. Create server certificate: + # openssl req -newkey rsa:2048 -nodes -keyout test1.key -x509 -days 365 -CA ca-cert1.pem -CAkey ca-key1.pem -out test1.crt + # openssl req -newkey rsa:2048 -nodes -keyout test2.key -x509 -days 365 -CA ca-cert2.pem -CAkey ca-key2.pem -out test2.crt + # 3. Create PFX file with key and certificate: + # openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx + # openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx + "public-ssl-custom" = { + name = "appgw-ssl-custom" + public_ip = { + name = "pip-ssl-custom" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Custom" + ssl_policy_min_protocol_version = "TLSv1_0" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_min_protocol_version = "TLSv1_1" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] + } + profile2 = { + name = "appgw-ssl-profile2" + ssl_policy_min_protocol_version = "TLSv1_2" + ssl_policy_cipher_suites = ["TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + http = { + name = "http-listener" + port = 80 + } + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile2" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + redirect_listener = { + name = "redirect-listener-listener" + port = 521 + } + redirect_url = { + name = "redirect-url-listener" + port = 522 + } + path_based_backend = { + name = "path-backend-listener" + port = 641 + } + path_based_redirect_listener = { + name = "path-redirect-listener-listener" + port = 642 + } + path_based_redirect_url = { + name = "path-redirect-rul-listener" + port = 643 + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + http = { + name = "http-settings" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + probe = "http" + } + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + probe = "https1" + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + probe = "https2" + } + } + probes = { + http = { + name = "http-probe" + path = "/" + protocol = "Http" + timeout = 10 + host = "127.0.0.1" + } + https1 = { + name = "https-probe1" + path = "/" + protocol = "Https" + timeout = 10 + } + https2 = { + name = "https-probe2" + path = "/" + protocol = "Https" + timeout = 10 + } + } + rewrites = { + http = { + name = "http-set" + rules = { + "xff-strip-port" = { + name = "http-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + rewrite = "http" + } + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + redirect_listener = { + name = "redirect-listener-rule" + priority = 4 + listener = "redirect_listener" + redirect = "redirect_listener" + } + redirect_url = { + name = "redirect-url-rule" + priority = 5 + listener = "redirect_url" + redirect = "redirect_url" + } + path_based_backend = { + name = "path-based-backend-rule" + priority = 6 + listener = "path_based_backend" + url_path_map = "path_based_backend" + } + path_based_redirect_listener = { + name = "path-redirect-listener-rule" + priority = 7 + listener = "path_based_redirect_listener" + url_path_map = "path_based_redirect_listener" + } + path_based_redirect_url = { + name = "path-redirect-rul-rule" + priority = 8 + listener = "path_based_redirect_url" + url_path_map = "path_based_redirect_url" + } + } + redirects = { + redirect_listener = { + name = "listener-redirect" + type = "Permanent" + target_listener = "http" + include_path = true + include_query_string = true + } + redirect_url = { + name = "url-redirect" + type = "Temporary" + target_url = "http://example.com" + include_path = true + include_query_string = true + } + } + url_path_maps = { + path_based_backend = { + name = "backend-map" + backend = "http" + path_rules = { + http = { + paths = ["/plaintext"] + backend = "http" + } + https = { + paths = ["/secure"] + backend = "https1" + } + } + } + path_based_redirect_listener = { + name = "redirect-listener-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_listener" + } + } + } + path_based_redirect_url = { + name = "redirect-url-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_url" + } + } + } + } + } + "public-ssl-predefined" = { + name = "appgw-ssl-predefined" + public_ip = { + name = "pip-ssl-predefined" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Predefined" + ssl_policy_name = "AppGwSslPolicy20170401" + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_name = "AppGwSslPolicy20170401S" + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + } + } + rewrites = { + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + } + } +} diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf new file mode 100644 index 00000000..c6fcf8fa --- /dev/null +++ b/examples/appgw/main.tf @@ -0,0 +1,85 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Create public IP in order to reuse it in 1 of the application gateways +resource "azurerm_public_ip" "this" { + name = "pip-existing" + resource_group_name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + sku = "Standard" + allocation_method = "Static" + zones = ["1", "2", "3"] + tags = var.tags +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + network_security_groups = each.value.network_security_groups + route_tables = each.value.route_tables + + tags = var.tags +} + +# Create Application Gateay +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = each.value.name + public_ip = each.value.public_ip + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = each.value.backend_pool + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles + + tags = var.tags + depends_on = [module.vnet, azurerm_public_ip.this] +} \ No newline at end of file diff --git a/examples/appgw/main_test.go b/examples/appgw/main_test.go new file mode 100644 index 00000000..0669ff80 --- /dev/null +++ b/examples/appgw/main_test.go @@ -0,0 +1,62 @@ +package appgw + +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/appgw/outputs.tf b/examples/appgw/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf new file mode 100644 index 00000000..e239a86d --- /dev/null +++ b/examples/appgw/variables.tf @@ -0,0 +1,290 @@ +### 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) + })), {}) + })) +} + +### Application Gateway +variable "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. + + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + Following properties are supported: + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. + This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, + which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - + this property is used by both: the Application Gateway and the Public IP created + in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address + will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to + [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, + for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, + for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS + listeners by providing a name of the profile in the `ssl_profile_name` property + EOF + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +} diff --git a/examples/appgw/versions.tf b/examples/appgw/versions.tf new file mode 100644 index 00000000..95b07f02 --- /dev/null +++ b/examples/appgw/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 94462efa..6fd79705 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -159,7 +159,7 @@ load_balancers = { -# # --- VMSERIES PART --- # +# --- VMSERIES PART --- # vmseries_version = "10.2.3" vmseries_vm_size = "Standard_DS3_v2" vmseries = { @@ -186,6 +186,7 @@ vmseries = { create_pip = true } ] + add_to_appgw_backend = true } "fw-2" = { name = "firewall02" @@ -210,27 +211,36 @@ vmseries = { create_pip = true } ] + add_to_appgw_backend = true } } -# # --- APPLICATION GATEWAYs --- # +# --- APPLICATION GATEWAYs --- # appgws = { "public" = { - name = "public-appgw" - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = 2 - vmseries_public_nic_name = "public" - rules = { - "minimum" = { - priority = 1 - listener = { - port = 80 - } - rewrite_sets = { + name = "appgw" + public_ip = { + name = "pip" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { "xff-strip-port" = { + name = "minimum-xff-strip-port" sequence = 100 request_headers = { "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" @@ -239,5 +249,14 @@ appgws = { } } } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } } } diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 643b8d43..58cf1f2c 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -316,31 +316,38 @@ module "appgw" { for_each = var.appgws - name = "${var.name_prefix}${each.value.name}" + name = each.value.name + public_ip = each.value.public_ip resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = try(each.value.managed_identities, null) - waf_enabled = try(each.value.waf_enabled, false) - capacity = try(each.value.capacity, null) - capacity_min = try(each.value.capacity_min, null) - capacity_max = try(each.value.capacity_max, null) - enable_http2 = try(each.value.enable_http2, null) - zones = try(each.value.zones, null) - - vmseries_ips = [for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" - ].private_ip_address if try(v.add_to_appgw_backend, false)] - - rules = each.value.rules + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = { + name = "vmseries" + vmseries_ips = [ + for k, v in var.vmseries : module.vmseries[k].interfaces[ + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + ].private_ip_address if try(v.add_to_appgw_backend, false) + ] + } + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps - ssl_policy_type = try(each.value.ssl_policy_type, null) - ssl_policy_name = try(each.value.ssl_policy_name, null) - ssl_policy_min_protocol_version = try(each.value.ssl_policy_min_protocol_version, null) - ssl_policy_cipher_suites = try(each.value.ssl_policy_cipher_suites, []) - ssl_profiles = try(each.value.ssl_profiles, {}) + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles tags = var.tags - depends_on = [module.vmseries] -} + depends_on = [module.vnet] +} \ No newline at end of file diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index d578a5e5..c31961b9 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -452,31 +452,159 @@ variable "vmseries" { EOF } -# Application Gateway +### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). Following properties are supported: - - `name` : name of the Application Gateway. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`) - - `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity - - `capacity_max` : (optional) maximum capacity for autoscaling - - `enable_http2` : enable HTTP2 support on the Application Gateway - - `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false` - - `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property. - - `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault - - `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF - default = {} + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) } diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 19815dbb..148e8ee8 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -167,21 +167,33 @@ load_balancers = { } } + + +# --- APPLICATION GATEWAYs --- # appgws = { "public" = { - name = "public-appgw" + name = "appgw" + public_ip = { + name = "pip" + } vnet_key = "transit" subnet_key = "appgw" zones = ["1", "2", "3"] - capacity = 2 - rules = { - "minimum" = { - priority = 1 - listener = { - port = 80 - } - rewrite_sets = { + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { "xff-strip-port" = { + name = "minimum-xff-strip-port" sequence = 100 request_headers = { "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" @@ -190,6 +202,15 @@ appgws = { } } } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } } } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 452b7c35..df4d9a3f 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -147,26 +147,29 @@ module "appgw" { for_each = var.appgws - name = "${var.name_prefix}${each.value.name}" + name = each.value.name + public_ip = each.value.public_ip resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = try(each.value.managed_identities, null) - waf_enabled = try(each.value.waf_enabled, false) - capacity = try(each.value.capacity, null) - capacity_min = try(each.value.capacity_min, null) - capacity_max = try(each.value.capacity_max, null) - enable_http2 = try(each.value.enable_http2, null) - zones = try(each.value.zones, null) - - rules = each.value.rules - - ssl_policy_type = try(each.value.ssl_policy_type, null) - ssl_policy_name = try(each.value.ssl_policy_name, null) - ssl_policy_min_protocol_version = try(each.value.ssl_policy_min_protocol_version, null) - ssl_policy_cipher_suites = try(each.value.ssl_policy_cipher_suites, []) - ssl_profiles = try(each.value.ssl_profiles, {}) + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles tags = var.tags depends_on = [module.vnet] diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 3e98896e..4649dd1e 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -423,33 +423,159 @@ variable "vmss" { -# Application Gateway +### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). Following properties are supported: - - `name` : name of the Application Gateway. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`) - - `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity - - `capacity_max` : (optional) maximum capacity for autoscaling - - `enable_http2` : enable HTTP2 support on the Application Gateway - - `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false` - - `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property. - - `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault - - `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF - default = {} + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) } diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 042f7556..194932a8 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -106,6 +106,10 @@ vnets = { network_security_group_key = "public" route_table_key = "public" } + "appgw" = { + name = "appgw-snet" + address_prefixes = ["10.0.0.48/28"] + } } } } @@ -155,6 +159,53 @@ load_balancers = { +# --- APPLICATION GATEWAYs --- # +appgws = { + "public" = { + name = "appgw" + public_ip = { + name = "pip" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { + "xff-strip-port" = { + name = "minimum-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } + } +} + + + # --- VMSERIES PART --- # bootstrap_storage = { diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index ff2cb081..e4b64dc7 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -314,31 +314,38 @@ module "appgw" { for_each = var.appgws - name = "${var.name_prefix}${each.value.name}" + name = each.value.name + public_ip = each.value.public_ip resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = try(each.value.managed_identities, null) - waf_enabled = try(each.value.waf_enabled, false) - capacity = try(each.value.capacity, null) - capacity_min = try(each.value.capacity_min, null) - capacity_max = try(each.value.capacity_max, null) - enable_http2 = try(each.value.enable_http2, null) - zones = try(each.value.zones, null) - - vmseries_ips = [for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" - ].private_ip_address if try(v.add_to_appgw_backend, false)] - - rules = each.value.rules + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = { + name = "vmseries" + vmseries_ips = [ + for k, v in var.vmseries : module.vmseries[k].interfaces[ + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + ].private_ip_address if try(v.add_to_appgw_backend, false) + ] + } + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps - ssl_policy_type = try(each.value.ssl_policy_type, null) - ssl_policy_name = try(each.value.ssl_policy_name, null) - ssl_policy_min_protocol_version = try(each.value.ssl_policy_min_protocol_version, null) - ssl_policy_cipher_suites = try(each.value.ssl_policy_cipher_suites, []) - ssl_profiles = try(each.value.ssl_profiles, {}) + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles tags = var.tags - depends_on = [module.vmseries] + depends_on = [module.vnet] } diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index ee6c1e3a..b6028de7 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -453,31 +453,159 @@ variable "vmseries" { EOF } -# Application Gateway +### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). Following properties are supported: - - `name` : name of the Application Gateway. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`) - - `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity - - `capacity_max` : (optional) maximum capacity for autoscaling - - `enable_http2` : enable HTTP2 support on the Application Gateway - - `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false` - - `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property. - - `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault - - `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF - default = {} + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) } diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 8b7cd387..1f0c2986 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -183,20 +183,33 @@ load_balancers = { } } + + +# --- APPLICATION GATEWAYs --- # appgws = { "public" = { - name = "public-appgw" + name = "appgw" + public_ip = { + name = "pip" + } vnet_key = "transit" subnet_key = "appgw" - capacity = 2 - rules = { - "minimum" = { - priority = 1 - listener = { - port = 80 - } - rewrite_sets = { + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { "xff-strip-port" = { + name = "minimum-xff-strip-port" sequence = 100 request_headers = { "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" @@ -205,6 +218,15 @@ appgws = { } } } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } } } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 452b7c35..df4d9a3f 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -147,26 +147,29 @@ module "appgw" { for_each = var.appgws - name = "${var.name_prefix}${each.value.name}" + name = each.value.name + public_ip = each.value.public_ip resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = try(each.value.managed_identities, null) - waf_enabled = try(each.value.waf_enabled, false) - capacity = try(each.value.capacity, null) - capacity_min = try(each.value.capacity_min, null) - capacity_max = try(each.value.capacity_max, null) - enable_http2 = try(each.value.enable_http2, null) - zones = try(each.value.zones, null) - - rules = each.value.rules - - ssl_policy_type = try(each.value.ssl_policy_type, null) - ssl_policy_name = try(each.value.ssl_policy_name, null) - ssl_policy_min_protocol_version = try(each.value.ssl_policy_min_protocol_version, null) - ssl_policy_cipher_suites = try(each.value.ssl_policy_cipher_suites, []) - ssl_profiles = try(each.value.ssl_profiles, {}) + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles tags = var.tags depends_on = [module.vnet] diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 3e98896e..4649dd1e 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -423,33 +423,159 @@ variable "vmss" { -# Application Gateway +### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). Following properties are supported: - - `name` : name of the Application Gateway. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`) - - `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity - - `capacity_max` : (optional) maximum capacity for autoscaling - - `enable_http2` : enable HTTP2 support on the Application Gateway - - `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false` - - `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property. - - `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault - - `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF - default = {} + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) } diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 19dd23cc..958edc36 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -315,31 +315,38 @@ module "appgw" { for_each = var.appgws - name = "${var.name_prefix}${each.value.name}" + name = each.value.name + public_ip = each.value.public_ip resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = try(each.value.managed_identities, null) - waf_enabled = try(each.value.waf_enabled, false) - capacity = try(each.value.capacity, null) - capacity_min = try(each.value.capacity_min, null) - capacity_max = try(each.value.capacity_max, null) - enable_http2 = try(each.value.enable_http2, null) - zones = try(each.value.zones, null) - - vmseries_ips = [for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" - ].private_ip_address if try(v.add_to_appgw_backend, false)] - - rules = each.value.rules + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = { + name = "vmseries" + vmseries_ips = [ + for k, v in var.vmseries : module.vmseries[k].interfaces[ + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + ].private_ip_address if try(v.add_to_appgw_backend, false) + ] + } + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps - ssl_policy_type = try(each.value.ssl_policy_type, null) - ssl_policy_name = try(each.value.ssl_policy_name, null) - ssl_policy_min_protocol_version = try(each.value.ssl_policy_min_protocol_version, null) - ssl_policy_cipher_suites = try(each.value.ssl_policy_cipher_suites, []) - ssl_profiles = try(each.value.ssl_profiles, {}) + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles tags = var.tags - depends_on = [module.vmseries] + depends_on = [module.vnet] } diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 3ff0c377..2c49cee8 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -449,31 +449,161 @@ variable "vmseries" { EOF } -# Application Gateway +### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). Following properties are supported: - - `name` : name of the Application Gateway. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. - - `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`) - - `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity - - `capacity_max` : (optional) maximum capacity for autoscaling - - `enable_http2` : enable HTTP2 support on the Application Gateway - - `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false` - - `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property. - - `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault - - `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF default = {} + nullable = false + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) } diff --git a/examples/virtual_network_gateway/.header.md b/examples/virtual_network_gateway/.header.md index 24dfd8ae..190b9594 100644 --- a/examples/virtual_network_gateway/.header.md +++ b/examples/virtual_network_gateway/.header.md @@ -1,5 +1,5 @@ -# VNET module sample +# VNG module sample -A sample of using a VNET module with the new variables layout and usage of `optional` keyword. +A sample of using a VNG module with the new variables layout and usage of `optional` keyword. The `README` is also in new, document-style format. diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index dd36f8cb..586f1605 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -1,7 +1,7 @@ <!-- BEGIN_TF_DOCS --> -# VNET module sample +# VNG module sample -A sample of using a VNET module with the new variables layout and usage of `optional` keyword. +A sample of using a VNG module with the new variables layout and usage of `optional` keyword. The `README` is also in new, document-style format. diff --git a/examples/virtual_network_gateway/main_test.go b/examples/virtual_network_gateway/main_test.go new file mode 100644 index 00000000..97d2facd --- /dev/null +++ b/examples/virtual_network_gateway/main_test.go @@ -0,0 +1,62 @@ +package vng + +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/modules/appgw/.header.md b/modules/appgw/.header.md new file mode 100644 index 00000000..102577ff --- /dev/null +++ b/modules/appgw/.header.md @@ -0,0 +1,799 @@ +# Palo Alto Networks Application Gateway Module for Azure + +A terraform module for deploying a Application Gateway v2 and its components required for the VM-Series firewalls in Azure. + +## Usage + +In order to use module `appgw`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +Then you can use below code as an example of calling module to create Application Gateway: + +```hcl +# Create Application Gateay +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = each.value.name + public_ip = each.value.public_ip + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = each.value.backend_pool + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles + + tags = var.tags + depends_on = [module.vnet] +} +``` + +Every application gateway is defined by basic attributes for name, VNet, subnet or capacity. +For applications there is a need to set `listeners`, `backends`, sometimes `rewrites`, `redirects` and / or `url_path_maps`. +Then `rules` property connects the other component using it's keys. + +The examples below are meant to show most common use cases and to serve as a base for more complex +application gateways definitions. + +### Example 1 + +Application Gateway with: +* new public IP +* HTTP listener +* static capacity +* rewriting HTTP headers + +```hcl +appgws = { + "public-http-minimum" = { + name = "appgw-http-minimum" + public_ip = { + name = "pip-http-minimum" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { + "xff-strip-port" = { + name = "minimum-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } + } + } +} +``` + +### Example 2 + +Application Gateway with: +* existing public IP +* HTTP listener +* static capacity +* rewriting HTTP headers + +```hcl +appgws = { + "public-http-existing" = { + name = "appgw-http-existing" + public_ip = { + name = "pip-existing" + create = false + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + backends = { + existing = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + existing = { + name = "existing-listener" + port = 80 + } + } + rewrites = { + existing = { + name = "existing-set" + rules = { + "xff-strip-port" = { + name = "existing-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + existing = { + name = "existing-rule" + priority = 1 + backend = "existing" + listener = "existing" + rewrite = "existing" + } + } + } +} +``` + +### Example 3 + +Application Gateway with: +* new public IP +* HTTP listener +* autoscaling + +```hcl +appgws = { + "public-http-autoscale" = { + name = "appgw-http-autoscale" + public_ip = { + name = "pip-http-autoscale" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + autoscale = { + min = 2 + max = 20 + } + } + backends = { + http = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + http = { + name = "http-listener" + port = 80 + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + } + } + } +} +``` + +### Example 4 + +Application Gateway with: +* new public IP +* WAF enabled +* HTTP listener +* static capacity +* rewriting HTTP headers + +```hcl +appgws = { + "public-waf" = { + name = "appgw-waf" + public_ip = { + name = "pip-waf" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + waf = { + prevention_mode = true + rule_set_type = "OWASP" + rule_set_version = "3.2" + } + backends = { + waf = { + name = "waf-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + waf = { + name = "waf-listener" + port = 80 + } + } + rewrites = { + waf = { + name = "waf-set" + rules = { + "xff-strip-port" = { + name = "waf-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "waf-rule" + priority = 1 + backend = "waf" + listener = "waf" + rewrite = "waf" + } + } + } +} +``` + +### Prerequisites for example 5 and 6 + +If you need to test example for Application Gateway with SSL, you need to created directory files +and create keys and certs using commands: + +1. Create CA private key and certificate: +```bash + openssl genrsa 2048 > ca-key1.pem + openssl req -new -x509 -nodes -days 365000 -key ca-key1.pem -out ca-cert1.pem + openssl genrsa 2048 > ca-key2.pem + openssl req -new -x509 -nodes -days 365000 -key ca-key2.pem -out ca-cert2.pem +``` +2. Create server certificate: +```bash + openssl req -newkey rsa:2048 -nodes -keyout test1.key -x509 -days 365 -CA ca-cert1.pem -CAkey ca-key1.pem -out test1.crt + openssl req -newkey rsa:2048 -nodes -keyout test2.key -x509 -days 365 -CA ca-cert2.pem -CAkey ca-key2.pem -out test2.crt +``` +3. Create PFX file with key and certificate: +```bash + openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx + openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx +``` + +### Example 5 + +Application Gateway with: +* new public IP +* multi site HTTPS listener (many host names on port 443) +* static capacity +* rewriting HTTPS headers + +```hcl +appgws = { + "public-ssl-predefined" = { + name = "appgw-ssl-predefined" + public_ip = { + name = "pip-ssl-predefined" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Predefined" + ssl_policy_name = "AppGwSslPolicy20170401" + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_name = "AppGwSslPolicy20170401S" + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + } + } + rewrites = { + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + } + } +} +``` + +### Example 6 + +Application Gateway with: +* new public IP +* multiple listener: + * HTTP + * multi site HTTPS (many host names on port 443) + * redirect + * path based +* static capacity +* rewriting HTTP and HTTPS headers +* custom SSL profiles and policies +* custom health probes +* rewrites + +```hcl +appgws = { + "public-ssl-custom" = { + name = "appgw-ssl-custom" + public_ip = { + name = "pip-ssl-custom" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Custom" + ssl_policy_min_protocol_version = "TLSv1_0" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_min_protocol_version = "TLSv1_1" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] + } + profile2 = { + name = "appgw-ssl-profile2" + ssl_policy_min_protocol_version = "TLSv1_2" + ssl_policy_cipher_suites = ["TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + http = { + name = "http-listener" + port = 80 + } + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile2" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + redirect_listener = { + name = "redirect-listener-listener" + port = 521 + } + redirect_url = { + name = "redirect-url-listener" + port = 522 + } + path_based_backend = { + name = "path-backend-listener" + port = 641 + } + path_based_redirect_listener = { + name = "path-redirect-listener-listener" + port = 642 + } + path_based_redirect_url = { + name = "path-redirect-rul-listener" + port = 643 + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + http = { + name = "http-settings" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + probe = "http" + } + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + probe = "https1" + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + probe = "https2" + } + } + probes = { + http = { + name = "http-probe" + path = "/" + protocol = "Http" + timeout = 10 + host = "127.0.0.1" + } + https1 = { + name = "https-probe1" + path = "/" + protocol = "Https" + timeout = 10 + } + https2 = { + name = "https-probe2" + path = "/" + protocol = "Https" + timeout = 10 + } + } + rewrites = { + http = { + name = "http-set" + rules = { + "xff-strip-port" = { + name = "http-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + rewrite = "http" + } + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + redirect_listener = { + name = "redirect-listener-rule" + priority = 4 + listener = "redirect_listener" + redirect = "redirect_listener" + } + redirect_url = { + name = "redirect-url-rule" + priority = 5 + listener = "redirect_url" + redirect = "redirect_url" + } + path_based_backend = { + name = "path-based-backend-rule" + priority = 6 + listener = "path_based_backend" + url_path_map = "path_based_backend" + } + path_based_redirect_listener = { + name = "path-redirect-listener-rule" + priority = 7 + listener = "path_based_redirect_listener" + url_path_map = "path_based_redirect_listener" + } + path_based_redirect_url = { + name = "path-redirect-rul-rule" + priority = 8 + listener = "path_based_redirect_url" + url_path_map = "path_based_redirect_url" + } + } + redirects = { + redirect_listener = { + name = "listener-redirect" + type = "Permanent" + target_listener = "http" + include_path = true + include_query_string = true + } + redirect_url = { + name = "url-redirect" + type = "Temporary" + target_url = "http://example.com" + include_path = true + include_query_string = true + } + } + url_path_maps = { + path_based_backend = { + name = "backend-map" + backend = "http" + path_rules = { + http = { + paths = ["/plaintext"] + backend = "http" + } + https = { + paths = ["/secure"] + backend = "https1" + } + } + } + path_based_redirect_listener = { + name = "redirect-listener-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_listener" + } + } + } + path_based_redirect_url = { + name = "redirect-url-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_url" + } + } + } + } + } +} +``` diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 4e605503..8db7ab58 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -1,514 +1,1449 @@ -# Palo Alto Networks VNet Module for Azure +<!-- BEGIN_TF_DOCS --> +# Palo Alto Networks Application Gateway Module for Azure -A terraform module for deploying an Application Gateway v2. The module is dedicated to work with the Next Generation Firewalls, hence it supports only one backend. It supports only v2 and WAF v2 Gateways. +A terraform module for deploying a Application Gateway v2 and its components required for the VM-Series firewalls in Azure. -In the center of module's configuration is the `rules` property. See the the [rules property explained](#rules-property-explained) and [`rules` property examples](#rules-property-examples) topics for more details. +## Usage + +In order to use module `appgw`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +Then you can use below code as an example of calling module to create Application Gateway: + +```hcl +# Create Application Gateay +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = each.value.name + public_ip = each.value.public_ip + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = each.value.backend_pool + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles + + tags = var.tags + depends_on = [module.vnet] +} +``` -## Rules property explained +Every application gateway is defined by basic attributes for name, VNet, subnet or capacity. +For applications there is a need to set `listeners`, `backends`, sometimes `rewrites`, `redirects` and / or `url_path_maps`. +Then `rules` property connects the other component using it's keys. -The `rules` property combines configuration for several Application Gateway components and groups them by a logical application. In other words an application defines a listener, http settings, health check probe, redirect rules, rewrite rule sets or url path maps (some fo them are mutually exclusive, check details on each of them below). Those are always unique for an application, meaning that you cannot share them between application definitions. Most of settings are optional and depend on a use case. The only one that is required is the listener port and the priority of the rule. +The examples below are meant to show most common use cases and to serve as a base for more complex +application gateways definitions. -In general `rules` property is a map where key is the logical application name and value is a set of properties, like below: +### Example 1 + +Application Gateway with: +* new public IP +* HTTP listener +* static capacity +* rewriting HTTP headers ```hcl -rules = { - "redirect_2_app_1 = { - priority = 1 - listener = { - port = 80 - } - redirect = { - type = "Temporary" - target_listener_name = "application_1" - include_path = true - include_query_string = true +appgws = { + "public-http-minimum" = { + name = "appgw-http-minimum" + public_ip = { + name = "pip-http-minimum" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + listeners = { + minimum = { + name = "minimum-listener" + port = 80 + } + } + rewrites = { + minimum = { + name = "minimum-set" + rules = { + "xff-strip-port" = { + name = "minimum-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "minimum-rule" + priority = 1 + backend = "minimum" + listener = "minimum" + rewrite = "minimum" + } } } - "application_1" = { - priority = 2 - listener = { - port = 443 - protocol = "Https" - ssl_certificate_path = "/path/to/cert" - ssl_certificate_pass = "cert_password" +} +``` + +### Example 2 + +Application Gateway with: +* existing public IP +* HTTP listener +* static capacity +* rewriting HTTP headers + +```hcl +appgws = { + "public-http-existing" = { + name = "appgw-http-existing" + public_ip = { + name = "pip-existing" + create = false + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + backends = { + existing = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + existing = { + name = "existing-listener" + port = 80 + } + } + rewrites = { + existing = { + name = "existing-set" + rules = { + "xff-strip-port" = { + name = "existing-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + existing = { + name = "existing-rule" + priority = 1 + backend = "existing" + listener = "existing" + rewrite = "existing" + } } } } ``` -The example above is a setup where the Application Gateway serves only as a reverse proxy terminating SSL connections (by default all traffic sent to the backend pool is sent to port 80, plain text). It also redirects all http communication sent to listener port 80 to https on port 443. +### Example 3 + +Application Gateway with: +* new public IP +* HTTP listener +* autoscaling + +```hcl +appgws = { + "public-http-autoscale" = { + name = "appgw-http-autoscale" + public_ip = { + name = "pip-http-autoscale" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + autoscale = { + min = 2 + max = 20 + } + } + backends = { + http = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + http = { + name = "http-listener" + port = 80 + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + } + } + } +} +``` -As you can see in the `target_listener_name` property, all Application Gateway component created for an application are equal to the application name (so the key value). +### Example 4 -For each application one can configure the following properties: +Application Gateway with: +* new public IP +* WAF enabled +* HTTP listener +* static capacity +* rewriting HTTP headers -* `priority` - rule's priority -* [`listener`](#property-listener) - provides general listener settings like port, protocol, error pages, etc -* [`backend`](#property-backend) - (optional) complete backend http settings configuration -* [`probe`](#property-probe) - (optional) backend health check probe configuration -* [`redirect`](#property-redirect) - (optional) mutually exclusive with `backend` and `probe`, creates a redirect rule -* [`rewrite_sets`](#property-rewrite-sets) - (optional) a set of rewrite rules used to modify response and request headers. -* [`url_path_maps`](#property-urlpathmaps) - (optional) a map of URL paths with their routing configuration - creates a rule of `PathBasedRouting` type (if not specified the rule is of `Basic` type) +```hcl +appgws = { + "public-waf" = { + name = "appgw-waf" + public_ip = { + name = "pip-waf" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + waf = { + prevention_mode = true + rule_set_type = "OWASP" + rule_set_version = "3.2" + } + backends = { + waf = { + name = "waf-backend" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + } + } + listeners = { + waf = { + name = "waf-listener" + port = 80 + } + } + rewrites = { + waf = { + name = "waf-set" + rules = { + "xff-strip-port" = { + name = "waf-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + minimum = { + name = "waf-rule" + priority = 1 + backend = "waf" + listener = "waf" + rewrite = "waf" + } + } + } +} +``` -For details on each of them (except for `priority`) check the topics below. +### Prerequisites for example 5 and 6 -### property: listener +If you need to test example for Application Gateway with SSL, you need to created directory files +and create keys and certs using commands: -Configures the listener, frontend port and (optionally) the SSL Certificate component that will be used by the listener (required for `https` listeners). The following properties are available: +1. Create CA private key and certificate: +```bash + openssl genrsa 2048 > ca-key1.pem + openssl req -new -x509 -nodes -days 365000 -key ca-key1.pem -out ca-cert1.pem + openssl genrsa 2048 > ca-key2.pem + openssl req -new -x509 -nodes -days 365000 -key ca-key2.pem -out ca-cert2.pem +``` +2. Create server certificate: +```bash + openssl req -newkey rsa:2048 -nodes -keyout test1.key -x509 -days 365 -CA ca-cert1.pem -CAkey ca-key1.pem -out test1.crt + openssl req -newkey rsa:2048 -nodes -keyout test2.key -x509 -days 365 -CA ca-cert2.pem -CAkey ca-key2.pem -out test2.crt +``` +3. Create PFX file with key and certificate: +```bash + openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx + openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx +``` -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `port` | a port number | `number` | n/a | yes | -| `protocol` | either `Http` or `Https` (case sensitive) | `string` | `"Http"` | no | -| `host_names` | host header values this rule should react on, this creates a Multi-Site listener | `list(string)` | `null` | no | -| `ssl_profile_name` | a name (key) of an SSL Profile defined in `ssl_profiles` property | `string` | `null` | no | -| `ssl_certificate_path` | a path to a certificate in `.pfx` format | `string` | `null` | yes if `protocol == "Https"`, mutually exclusive with `ssl_certificate_vault_id` | -| `ssl_certificate_pass` | a password matching the certificate specified in `ssl_certificate_path` | `string` | `null` | yes if `protocol == "Https"`, mutually exclusive with `ssl_certificate_vault_id` | -| `ssl_certificate_vault_id` | an ID of a certificate stored in an Azure Key Vault, requires `managed_identities` property, the identity(-ties) used have to have at least `GET` access to Key Vault's secrets | `string` | `null` | yes if `protocol == "Https"`, mutually exclusive with `ssl_certificate_path` | -| `custom_error_pages` | a map that contains ULRs for custom error pages, for more information see below | `map` | `null` | no | +### Example 5 -The `custom_error_pages` map has the following format: +Application Gateway with: +* new public IP +* multi site HTTPS listener (many host names on port 443) +* static capacity +* rewriting HTTPS headers ```hcl -custom_error_pages = { - HttpStatus403 = "http://error.com/403/page.html", - HttpStatus502 = "http://error.com/502/page.html" +appgws = { + "public-ssl-predefined" = { + name = "appgw-ssl-predefined" + public_ip = { + name = "pip-ssl-predefined" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Predefined" + ssl_policy_name = "AppGwSslPolicy20170401" + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_name = "AppGwSslPolicy20170401S" + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + } + } + rewrites = { + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + } + } } ``` -Keys can have values of `HttpStatus403` and `HttpStatus502` only. Both are optional. Only the error page path is customizable and it has to point to an HTML file. +### Example 6 + +Application Gateway with: +* new public IP +* multiple listener: + * HTTP + * multi site HTTPS (many host names on port 443) + * redirect + * path based +* static capacity +* rewriting HTTP and HTTPS headers +* custom SSL profiles and policies +* custom health probes +* rewrites + +```hcl +appgws = { + "public-ssl-custom" = { + name = "appgw-ssl-custom" + public_ip = { + name = "pip-ssl-custom" + } + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1", "2", "3"] + capacity = { + static = 2 + } + ssl_global = { + ssl_policy_type = "Custom" + ssl_policy_min_protocol_version = "TLSv1_0" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + ssl_profiles = { + profile1 = { + name = "appgw-ssl-profile1" + ssl_policy_min_protocol_version = "TLSv1_1" + ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] + } + profile2 = { + name = "appgw-ssl-profile2" + ssl_policy_min_protocol_version = "TLSv1_2" + ssl_policy_cipher_suites = ["TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384"] + } + } + frontend_ip_configuration_name = "public_ipconfig" + listeners = { + http = { + name = "http-listener" + port = 80 + } + https1 = { + name = "https1-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile1" + ssl_certificate_path = "./files/test1.pfx" + ssl_certificate_pass = "" + host_names = ["test1.appgw.local"] + } + https2 = { + name = "https2-listener" + port = 443 + protocol = "Https" + ssl_profile_name = "appgw-ssl-profile2" + ssl_certificate_path = "./files/test2.pfx" + ssl_certificate_pass = "" + host_names = ["test2.appgw.local"] + } + redirect_listener = { + name = "redirect-listener-listener" + port = 521 + } + redirect_url = { + name = "redirect-url-listener" + port = 522 + } + path_based_backend = { + name = "path-backend-listener" + port = 641 + } + path_based_redirect_listener = { + name = "path-redirect-listener-listener" + port = 642 + } + path_based_redirect_url = { + name = "path-redirect-rul-listener" + port = 643 + } + } + backend_pool = { + name = "vmseries-pool" + } + backends = { + http = { + name = "http-settings" + port = 80 + protocol = "Http" + timeout = 60 + cookie_based_affinity = "Enabled" + probe = "http" + } + https1 = { + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test1.appgw.local" + root_certs = { + test = { + name = "https-application-test1" + path = "./files/ca-cert1.pem" + } + } + probe = "https1" + } + https2 = { + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + cookie_based_affinity = "Enabled" + hostname_from_backend = false + hostname = "test2.appgw.local" + root_certs = { + test = { + name = "https-application-test2" + path = "./files/ca-cert2.pem" + } + } + probe = "https2" + } + } + probes = { + http = { + name = "http-probe" + path = "/" + protocol = "Http" + timeout = 10 + host = "127.0.0.1" + } + https1 = { + name = "https-probe1" + path = "/" + protocol = "Https" + timeout = 10 + } + https2 = { + name = "https-probe2" + path = "/" + protocol = "Https" + timeout = 10 + } + } + rewrites = { + http = { + name = "http-set" + rules = { + "xff-strip-port" = { + name = "http-xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + https1 = { + name = "https1-set" + rules = { + "xff-strip-port" = { + name = "https1-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + https2 = { + name = "https2-set" + rules = { + "xff-strip-port" = { + name = "https2-xff-strip-port" + sequence = 100 + conditions = { + "http_resp_X-Forwarded-Proto" = { + pattern = "https" + ignore_case = true + negate = true + } + } + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + "X-Forwarded-Proto" = "https" + } + } + } + } + } + rules = { + http = { + name = "http-rule" + priority = 1 + backend = "http" + listener = "http" + rewrite = "http" + } + https1 = { + name = "https1-rule" + priority = 2 + backend = "https1" + listener = "https1" + rewrite = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend = "https2" + listener = "https2" + rewrite = "https2" + } + redirect_listener = { + name = "redirect-listener-rule" + priority = 4 + listener = "redirect_listener" + redirect = "redirect_listener" + } + redirect_url = { + name = "redirect-url-rule" + priority = 5 + listener = "redirect_url" + redirect = "redirect_url" + } + path_based_backend = { + name = "path-based-backend-rule" + priority = 6 + listener = "path_based_backend" + url_path_map = "path_based_backend" + } + path_based_redirect_listener = { + name = "path-redirect-listener-rule" + priority = 7 + listener = "path_based_redirect_listener" + url_path_map = "path_based_redirect_listener" + } + path_based_redirect_url = { + name = "path-redirect-rul-rule" + priority = 8 + listener = "path_based_redirect_url" + url_path_map = "path_based_redirect_url" + } + } + redirects = { + redirect_listener = { + name = "listener-redirect" + type = "Permanent" + target_listener = "http" + include_path = true + include_query_string = true + } + redirect_url = { + name = "url-redirect" + type = "Temporary" + target_url = "http://example.com" + include_path = true + include_query_string = true + } + } + url_path_maps = { + path_based_backend = { + name = "backend-map" + backend = "http" + path_rules = { + http = { + paths = ["/plaintext"] + backend = "http" + } + https = { + paths = ["/secure"] + backend = "https1" + } + } + } + path_based_redirect_listener = { + name = "redirect-listener-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_listener" + } + } + } + path_based_redirect_url = { + name = "redirect-url-map" + backend = "http" + path_rules = { + http = { + paths = ["/redirect"] + redirect = "redirect_url" + } + } + } + } + } +} +``` + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Application Gateway. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`public_ip`](#public_ip) | `object` | Public IP address. +[`subnet_id`](#subnet_id) | `string` | An ID of a subnet that will host the Application Gateway. +[`ssl_profiles`](#ssl_profiles) | `map` | A map of SSL profiles. +[`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. +[`probes`](#probes) | `map` | A map of probes for the Application Gateway. +[`rewrites`](#rewrites) | `map` | A map of rewrites for the Application Gateway. +[`rules`](#rules) | `map` | A map of rules for the Application Gateway. +[`redirects`](#redirects) | `map` | A map of redirects for the Application Gateway. +[`url_path_maps`](#url_path_maps) | `map` | A map of URL path maps for the Application Gateway. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`zones`](#zones) | `list` | A list of zones the Application Gateway should be available in. +[`domain_name_label`](#domain_name_label) | `string` | Label for the Domain Name. +[`enable_http2`](#enable_http2) | `bool` | Enable HTTP2 on the Application Gateway. +[`waf`](#waf) | `object` | Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. +[`capacity`](#capacity) | `object` | Capacity configuration for Application Gateway. +[`managed_identities`](#managed_identities) | `list` | A list of existing User-Assigned Managed Identities. +[`ssl_global`](#ssl_global) | `object` | Global SSL settings. +[`frontend_ip_configuration_name`](#frontend_ip_configuration_name) | `string` | Frontend IP configuration name. +[`backend_pool`](#backend_pool) | `object` | Backend pool. +[`backends`](#backends) | `map` | A map of backend settings for the Application Gateway. + + + +## Module's Outputs + +Name | Description +--- | --- +`public_ip` | A public IP assigned to the Application Gateway. +`public_domain_name` | Public domain name assigned to the Application Gateway. +`backend_pool_id` | The identifier of the Application Gateway backend address pool. + +## 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: + +- `application_gateway` (managed) +- `public_ip` (managed) +- `public_ip` (data) + +## Inputs/Outpus details -### property: backend +### Required Inputs -Configures the backend's http settings, so port and protocol properties for a connection between an Application Gateway and the actual Firewall. Following properties are available: -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `port` | port on which the backend is listening | `number` | `80` | no | -| `protocol` | protocol for the backend service, this can be `Http` or `Https` | `string` | `"Http"` | no | -| `hostname_from_backend` | override request host header with backend's host name | `bool` | `false` | no, mutually exclusive with `hostname` | -| `hostname` | override request host header with a custom host name | `string` | `null` | no, mutually exclusive with `hostname_from_backend` | -| `path` | path prefix, in case we need to shift the url path for the backend | `string` | `null` | no | -| `timeout` | timeout for backend's response in seconds | `number` | `60` | no | -| `cookie_based_affinity` | cookie based routing | `string` | `"Enabled"` | no | -| `affinity_cookie_name` | name of the affinity cookie, when skipped defaults to Azure's default name | `string` | `null` | no | -| `root_certs` | for https traffic only, a map of custom root certificates used to sign backend's certificate (see below) | `map` | `null` | no | +#### name -When `hostname_from_backend` nor `hostname` is not set the request's host header is not changed. This requires that the health check probe's (if used) `host` property is set (Application Gateway limitation). However, if one of this properties is set you can skip probe's `host` property - the host header will be inherited from the backend's http settings. +The name of the Application Gateway. -The `root_certs` map has the following format: +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### public_ip + +Public IP address. + +Type: ```hcl -root_certs = { - root_cert_name = "./files/ca.crt" -} +object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) ``` -### property: probe -Configures a health check probe. A probe is fully customizable, meaning that one decides what should be probed, the FW or an application behind it. +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + +#### subnet_id + +An ID of a subnet that will host the Application Gateway. + +Keep in mind that this subnet can contain only AppGWs and only of the same type. + -One can decide on the port used by the probe but the protocol is always aligned to the one set in http settings (Application Gateway limitation). +Type: string -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `path` | url for the health check endpoint, this property controls if the custom probe is created or not; if this is not set, http settings will have the property `Use custom probe` set to `No` | `string` | `null` | yes to enable a probe | -| `host` | host header for the health check probe, when omitted sets the `Pick host name from backend HTTP settings` to `Yes`, cannot be skipped when `backend.hostname` or `backend.hostname_from_backend` are not set | `string` | `null` | no | -| `port` | (v2 only) port for the health check, defaults to default protocol port | `number` | n/a | no | -| `interval` | probe interval in seconds | `nubmer` | `5` | no | -| `timeout` | probe timeout in seconds | `nubmer` | `30` | no | -| `threshold` | number of failed probes until the backend is marked as down | `nubmer` | `2` | no | -| `match_code` | a list of acceptable http response codes, this property controls the custom match condition for a health probe, if not set, it disables them | `list(nubmer)` | `null` | no | -| `match_body` | a snippet of the backend response that can be matched for health check conditions | `string` | `null` | no | +<sup>[back to list](#modules-required-inputs)</sup> -### property: redirect -Configures a rule that only redirects traffic (traffic matched by this rules never reaches the Firewalls). Hence it is mutually exclusive with `backend` and `probe` properties. +#### ssl_profiles -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `type` | this property triggers creation of a redirect rule, possible values are: `Permanent`, `Temporary`, `Found` and `SeeOther` | `string` | `null` | no | -| `target_listener_name` | a name of an existing listener to which traffic will be redirected, this is basically a name of a rule | `string` | `null` | no, mutually exclusive with `target_url` | -| `target_url` | a URL to which traffic will be redirected | `string` | `null` | no, mutually exclusive with `target_listener_name` | -| `include_path` | decides whether to include the path in the redirected Url | `bool` | `false` | no | -| `include_query_string` | decides whether to include the query string in the redirected Url | `bool` | `false` | no | +A map of SSL profiles. -### property: rewrite_sets +SSL profiles can be later on referenced in HTTPS listeners by providing a name of the profile in the `name` property. +For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` +variables as SSL profile is a named SSL policy - same properties apply. +The only difference is that you cannot name an SSL policy inside an SSL profile. -Creates rewrite rules used to modify the HTTP response and request headers. A set of rewrite rules cannot be shared between applications. For details on building the rules refer to [Microsoft's documentation](https://docs.microsoft.com/azure/application-gateway/rewrite-http-headers). +Every SSL profile contains attributes: +- `name` - (`string`, required) name of the SSL profile +- `ssl_policy_name` - (`string`, optional) name of predefined policy +- `ssl_policy_min_protocol_version` - (`string`, optional) the minimal TLS version. +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. -The whole property is a map, where key is the rule name and value is a map of rule's properties. Example of a rule that strips a port number from the `X-Forwarded-For` header: + +Type: ```hcl -rewrite_sets = { - "xff-strip-port" = { - sequence = 100 - request_header = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } -} +map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) ``` -Properties for a rule are described below. -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `sequence` | a rule priority | `number` | n/a | yes | -| `conditions` | a map of pre-conditions for a rule, for details see [property: rewrite_sets.conditions](#property-rewritesetsconditions) | `map` | `null` | no | -| `request_headers` | a key-value map of request headers to modify, where a key is the header name and the value is the new value (to delete a header set the value to an empty string) | `map` | `null` | no | -| `response_headers` | a key-value map of response headers to modify, where a key is the header name and the value is the new value (to delete a header set the value to an empty string) | `map` | `null` | no | +<sup>[back to list](#modules-required-inputs)</sup> -#### property: rewrite_sets.conditions -This is a map where the key is a variable that will be checked and value is a set of properties describing the actual condition. +#### listeners -For details on the variables see [Microsoft's documentation](https://docs.microsoft.com/azure/application-gateway/rewrite-http-headers#server-variables). But generally value of this variable brakes into 3 scenarios controlled by a prefix: +A map of listeners for the Application Gateway. -* `var_` - the condition is based on a server variable, the variable name follows the prefix -* `http_req_` - a request header condition, the header name follows the prefix -* `http_resp` - a response header condition, the header name follows the prefix. +Every listener contains attributes: +- `name` - (`string`, required) The name for this Frontend Port. +- `port` - (`string`, required) The port used for this Frontend Port. +- `protocol` - (`string`, optional) The Protocol to use for this HTTP Listener. +- `host_names` - (`list`, optional) A list of Hostname(s) should be used for this HTTP Listener. + It allows special wildcard characters. +- `ssl_profile_name` - (`string`, optional) The name of the associated SSL Profile which should be used + for this HTTP Listener. +- `ssl_certificate_path` - (`string`, optional) Path to the file with tThe base64-encoded PFX certificate data. +- `ssl_certificate_pass` - (`string`, optional) Password for the pfx file specified in data. +- `ssl_certificate_vault_id` - (`string`, optional) Secret Id of (base-64 encoded unencrypted pfx) Secret + or Certificate object stored in Azure KeyVault. +- `custom_error_pages` - (`map`, optional) Map of string, where key is HTTP status code and value is + error page URL of the application gateway customer error. -Example: + +Type: ```hcl -conditions = { - "var_client_ip" = { - pattern = "1.1.1.1" - ignore_case = true - } - "http_req_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } -} +map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) ``` -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `conditions.pattern` | a fix string or a regular expression to evaluate the condition | `string` | `null` | yes | -| `conditions.ignore_case` | case in-sensitive comparison | `bool` | `false` | no | -| `conditions.negate` | negate the condition | `bool` | `false` | no | -### property: url_path_maps +<sup>[back to list](#modules-required-inputs)</sup> -Triggers creation of a `PathBasedRouting` rule for an application. It's a map where key is a name of a routing configuration for a specific path and value contains the actual configuration. -| Name | Description | Type | Default | Required | -| --- | --- | --- | --- | --- | -| `path` | a URL path that will be matched for this configuration | `string` | n/a | yes | -| `backend` | a [backend](#property-backend) configuration like specified above | `map` | `null` | no, mutually exclusive with `redirect` | -| `probe` | a [probe](#property-probe) configuration like specified above | `map` | `null` | no, mutually exclusive with `redirect` | -| `redirect` | a [redirect](#property-redirect) configuration like specified above | `map` | `null` | no, mutually exclusive with `backend` and `probe` | -As one can see the only specific setting is `path`. The rest of configuration is similar to a regular application configuration. For each path a pair backend settings and probe or a redirect configuration is created. +#### probes -## Usage +A map of probes for the Application Gateway. -### General +Every probe contains attributes: +- `name` - (`string`, required) The name used for this Probe +- `path` - (`string`, required) The path used for this Probe +- `host` - (`string`, optional) The hostname used for this Probe +- `port` - (`number`, optional) Custom port which will be used for probing the backend servers. +- `protocol` - (`string`, optional, defaults `Http`) The protocol which should be used. +- `interval` - (`number`, optional, defaults `5`) The interval between two consecutive probes in seconds. +- `timeout` - (`number`, optional, defaults `30`) The timeout used for this Probe, + which indicates when a probe becomes unhealthy. +- `threshold` - (`number`, optional, defaults `2`) The unhealthy Threshold for this Probe, which indicates + the amount of retries which should be attempted before a node is deemed unhealthy. +- `match_code` - (`list`, optional) The list of allowed status codes for this Health Probe. +- `match_body` - (`string`, optional) A snippet from the Response Body which must be present in the Response. -Module requires that Firewalls and a dedicated subnet are set up already. -An example invocation (assuming usage of other Palo Alto's Azure modules) with a minium set of rules (at least one rule is required): +Type: ```hcl -module "Application Gateway" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/appgw" +map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })) +``` - name = "Application Gateway" - resource_group_name = azurerm_resource_group.this.name - location = var.location - subnet_id = module.security_vnet.subnet_ids["subnet-Application Gateway"] - capacity = 2 - vmseries_ips = [for k, v in module.vmseries : v.interfaces[1].private_ip_address] +<sup>[back to list](#modules-required-inputs)</sup> - rules = { - "minimum" = { - priority = 1 - listener = { - port = 8080 - } - } - } -} +#### rewrites + +A map of rewrites for the Application Gateway. + +Every rewrite contains attributes: +- `name` - (`string`) Rewrite Rule Set name +- `rules` - (`object`, optional) Rewrite Rule Set defined with attributes: + - `name` - (`string`, required) Rewrite Rule name. + - `sequence` - (`number`, required) Rule sequence of the rewrite rule that determines + the order of execution in a set. + - `conditions` - (`map`, optional) One or more condition blocks as defined below: + - `pattern` - (`string`, required) The pattern, either fixed string or regular expression, + that evaluates the truthfulness of the condition. + - `ignore_case` - (`string`, optional, defaults to `false`) Perform a case in-sensitive comparison. + - `negate` - (`bool`, optional, defaults to `false`) Negate the result of the condition evaluation. + - `request_headers` - (`map`, optional) Map of request header, where header name is the key, + header value is the value of the object in the map. + - `response_headers`- (`map`, optional) Map of response header, where header name is the key, + header value is the value of the object in the map. + + +Type: + +```hcl +map(object({ + name = string + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })) ``` -### `rules` property examples -The `rules` property is quite flexible, there are several limitations though. Their origin comes from the Application Gateway rather than the code itself. They are: +<sup>[back to list](#modules-required-inputs)</sup> + +#### rules -* `priority` property is required since 2021-08-01 AzureRM API update -* `listener.port` has to be specified at minimum to create a valid rule -* `listener.port` has to be unique between rules unless `listener.host_names` is used (all rules sharing a port have to have `listener.host_names` specified) -* a health check probe has to have a host header specified, this is done by either setting the header directly in `probe.host` property, or by inheriting it from http backend settings (one of `backend.hostname_from_backend` or `backend.hostname` has to be set) -* when creating a redirect rule `backend` and `probe` cannot be set -* the probe has to use the same protocol as the associated http backend settings, different port can be used though +A map of rules for the Application Gateway. -The examples below are meant to show most common use cases and to serve as a base for more complex rules. +A rule combines, backend, listener, rewrites and redirects configurations. +A key is an application name that is used to prefix all components inside Application Gateway +that are created for this application. -* [SSL termination with a redirect from HTTP to HTTPS](#ssl-termination-with-a-redirect-from-http-to-https) -* [Multiple websites hosted on a single port](#multiple-websites-hosted-on-a-single-port) -* [Probing a Firewall availability in an HA pair](#probing-a-firewall-availability-in-an-ha-pair) -* [Rewriting HTTP headers](#rewriting-http-headers) -* [Path based configuration](#path-based-configuration) +Every rule contains attributes: +- `name` - (`string`, required) Rule name. +- `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value + from 1 to 20000 with 1 being the highest priority and 20000 being the lowest priority. +- `backend` - (`string`, optional) Backend settings` key +- `listener` - (`string`, required) Listener's key +- `rewrite` - (`string`, optional) Rewrite's key +- `url_path_map` - (`string`, optional) URL Path Map's key +- `redirect` - (`string`, optional) Redirect's key -#### SSL termination with a redirect from HTTP to HTTPS -This rule redirects all `http` traffic to a `https` listener. The ssl certificate is taken from an Azure Key Vault service. +Type: ```hcl -rules = { - "http-2-https" = { - priority = 1 +map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) +``` - listener = { - port = 80 - } - redirect = { - type = "Permanent" - target_listener_name = "https" - include_path = true - include_query_string = true - } - } - "https" = { - priority = 2 +<sup>[back to list](#modules-required-inputs)</sup> - listener = { - port = 443 - protocol = "Https" - ssl_certificate_vault_id = "https://kv.vault.azure.net/secrets/cert/bb1391bba15042a59adaea584a8208e8" - } - } -} +#### redirects + +A map of redirects for the Application Gateway. + +Every redirect contains attributes: +- `name` - (`string`, required) The name of redirect. +- `type` - (`string`, required) The type of redirect. + Possible values are Permanent, Temporary, Found and SeeOther +- `target_listener` - (`string`, optional) The name of the listener to redirect to. +- `target_url` - (`string`, optional) The URL to redirect the request to. +- `include_path` - (`bool`, optional) Whether or not to include the path in the redirected URL. +- `include_query_string` - (`bool`, optional) Whether or not to include the query string in the redirected URL. + + +Type: + +```hcl +map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })) ``` -#### Multiple websites hosted on a single port -This rule demonstrates how to split hostname based traffic to different ports on a Firewall. For simplicity `http` traffic is configured only. +<sup>[back to list](#modules-required-inputs)</sup> + +#### url_path_maps + +A map of URL path maps for the Application Gateway. + +Every URL path map contains attributes: +- `name` - (`string`, required) The name of redirect. +- `backend` - (`string`, required) The default backend for redirect. +- `path_rules` - (`map`, optional) The map of rules, where every object has attributes: + - `paths` - (`list`, required) List of paths + - `backend` - (`string`, optional) Backend's key + - `redirect` - (`string`, optional) Redirect's key + + +Type: ```hcl -rules = { - "application-1" = { - priority = 1 +map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })) +``` - listener = { - port = 80 - host_names = ["www.app_1.com"] - } - backend = { - port = 8080 - } - } - "application-2" = { - priority = 2 +<sup>[back to list](#modules-required-inputs)</sup> - listener = { - port = 80 - host_names = ["www.app_2.com"] - } - backend = { - port = 8081 - } - } -} + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### zones + +A list of zones the Application Gateway should be available in. + +NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, +pinned to a single zone or zone-redundant (so available in all zones in a region). +Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, +but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during +next `terraform apply` as there will be difference between the state and the actual configuration. + +For details on zones currently available in a region of your choice refer to +[Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). + +Example: +``` +zones = ["1","2","3"] ``` -#### Probing a Firewall availability in an HA pair -In a typical HA scenario the probe is set to check the Management Service exposed on a public interface. The example below shows how to achieve that. +Type: list(string) + +Default value: `[1 2 3]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### domain_name_label + +Label for the Domain Name. + +Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created +for the public IP in the Microsoft Azure DNS system." + + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### enable_http2 + +Enable HTTP2 on the Application Gateway. + +Type: bool + +Default value: `false` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### waf + +Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. + +This module does not support WAF rules configuration and advanced WAF settings. +Only below attributes are allowed: +- `prevention_mode` - (`bool`, required) `true` if WAF mode is Prevention, `false` for Detection mode +- `rule_set_type` - (`string`, optional, defaults to `OWASP`) The Type of the Rule Set used for this Web Application Firewall +- `rule_set_version` - (`string`, optional) The Version of the Rule Set used for this Web Application Firewall + + +Type: ```hcl -rules = { - "application-1" = { - priority = 1 +object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + }) +``` - listener = { - port = 80 - } - backend = { - port = 8080 - } +Default value: `&{}` - probe = { - path = "/php/login.php" - port = 80 - host = "127.0.0.1" - } - } -} +<sup>[back to list](#modules-optional-inputs)</sup> + +#### capacity + +Capacity configuration for Application Gateway. + +Object defines static or autoscale configuration using attributes: +- `static` - (`number`, optional) A static number of Application Gateway instances. A value bewteen 1 and 125 + or null, if autoscale configuration is provided +- `autoscale` - (`object`, optional) Autoscaling configuration (used only, if static is null) with attributes: + - `min` - (`number`, optional) Minimum capacity for autoscaling. + - `max` - (`number`, optional) Maximum capacity for autoscaling. + + +Type: + +```hcl +object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) ``` -#### Rewriting HTTP headers -This is a simple rule used to terminate SSL traffic. However the application behind the Firewall has two limitations: +Default value: `map[static:2]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### managed_identities + +A list of existing User-Assigned Managed Identities. + +Application Gateway uses Managed Identities to retrieve certificates from Key Vault. +These identities have to have at least `GET` access to Key Vault's secrets. +Otherwise Application Gateway will not be able to use certificates stored in the Vault. + + +Type: list(string) -1. it expects the protocol to be still HTTPS, to achieve that we set the `X-Forwarded-Proto` header -1. it expects that the `X-Forwarded-For` does not include ports (which is default for an Application Gateway). +Default value: `&{}` -We also use an SSL certificate stored in a file instead of an Azure Key Vault. +<sup>[back to list](#modules-optional-inputs)</sup> -NOTICE, there are some defaults used in this config: -* `backend` has no `port` or `protocol` specified - this means `80` and `Http` are used respectively. -* `probe` has no `port` or `host` specified - this means port `80` is used (default port for protocol, which is inherited from backend's protocol) and host headers are inherited from backen's host headers. +#### ssl_global + +Global SSL settings. + +SSL settings are defined by attributes: +- `ssl_policy_type` - (`string`, required) type of an SSL policy. Possible values are `Predefined` + or `Custom` or `CustomV2`. If the value is `Custom` the following values are mandatory: + `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. +- `ssl_policy_name` - (`string`, optional) name of an SSL policy. + Supported only for `ssl_policy_type` set to `Predefined`. + Normally you can set it also for `Custom` policies but the name is discarded + on Azure side causing an update to Application Gateway each time terraform code is run. + Therefore this property is omitted in the code for `Custom` policies. + For the `Predefined` policies, check the Microsoft documentation + https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview + for possible values as they tend to change over time. + The default value is currently (Q1 2023) a Microsoft's default. +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy. + Required only for `ssl_policy_type` set to `Custom`. +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. + Required only for `ssl_policy_type` set to `Custom`. + For possible values see documentation: + https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites + + +Type: ```hcl -rules = { - "application-1" = { - priority = 1 +object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }) +``` - listener = { - port = 443 - protocol = "Https" - ssl_certificate_path = "./files/certificate.pfx" - ssl_certificate_pass = "password" - } - backend = { - hostname_from_backend = true - } +Default value: `map[ssl_policy_cipher_suites:[] ssl_policy_min_protocol_version:<nil> ssl_policy_name:AppGwSslPolicy20220101S ssl_policy_type:Predefined]` - probe = { - path = "/php/login.php" - } +<sup>[back to list](#modules-optional-inputs)</sup> - rewrite_sets = { - "xff-strip-port" = { - sequence = 100 - conditions = { - "http_resp_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } - } - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - "X-Forwarded-Proto" = "https" - } - } - } - } -} + +#### frontend_ip_configuration_name + +Frontend IP configuration name + +Type: string + +Default value: `public_ipconfig` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### backend_pool + +Backend pool. + +Object contains attributes: +- `name` - (`string`, required) name of the backend pool. +- `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VMSeries' interfaces that will serve as backends + for the Application Gateway. + + +Type: + +```hcl +object({ + name = string + vmseries_ips = optional(list(string), []) + }) ``` -#### Path based configuration -Here we show a configuration for a 'complex' application. This means that under different paths we have different applications. The host header remains the same. Each application is served on the Firewall under a different port. We use path based routing to split the traffic on an Application Gateway. +Default value: `map[name:vmseries]` + +<sup>[back to list](#modules-optional-inputs)</sup> -Notice, that the general backend configuration serves as a 'catch-all' rule, while each path has it's own dedicated backend, probe or redirect configuration block. +#### backends + +A map of backend settings for the Application Gateway. + +Every backend contains attributes: +- `name` - (`string`, optional) The name of the backend settings +- `path` - (`string`, optional) The Path which should be used as a prefix for all HTTP requests. +- `hostname_from_backend` - (`bool`, optional) Whether host header should be picked from the host name of the backend server. +- `hostname` - (`string`, optional) Host header to be sent to the backend servers. +- `port` - (`number`, optional) The port which should be used for this Backend HTTP Settings Collection. +- `protocol` - (`string`, optional) The Protocol which should be used. Possible values are Http and Https. +- `timeout` - (`number`, optional) The request timeout in seconds, which must be between 1 and 86400 seconds. +- `cookie_based_affinity` - (`string`, optional) Is Cookie-Based Affinity enabled? Possible values are Enabled and Disabled. +- `affinity_cookie_name` - (`string`, optional) The name of the affinity cookie. +- `probe` - (`string`, optional) Probe's key. +- `root_certs` - (`map`, optional) A list of trusted_root_certificate names. + + +Type: ```hcl -rules = { - "complex-application" = { - priority = 1 - listener = { - port = 80 - host_names = ["www.complex.app"] - } - backend = { - port = 8080 - } - probe = { - path = "/healthcheck" - host = "127.0.0.1" - } - url_path_maps = { - "menu" = { - path = "/api/menu/" - backend = { - port = 8081 - } - probe = { - path = "/api/menu/healthcheck" - host = "127.0.0.1" - } - } - "header" = { - path = "/api/header/" - backend = { - port = 8082 - } - probe = { - path = "/api/header/healthcheck" - host = "127.0.0.1" - } - } - "old_url_fix" = { - path = "/old/app/path/" - redirect = { - type = "Permanent" - target_url = "https://www.complex.app" - } - } - } - } -} +map(object({ + name = optional(string) + path = optional(string) + hostname_from_backend = optional(bool, false) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + })) ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_application_gateway.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of an existing resource group. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Location to place the Application Gateway in. | `string` | n/a | yes | -| <a name="input_zones"></a> [zones](#input\_zones) | A list of zones the Application Gateway should be available in.<br><br>NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to a single zone or zone-redundant (so available in all zones in a region). <br>Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during next `terraform apply` as there will be difference between the state and the actual configuration.<br><br>For details on zones currently available in a region of your choice refer to [Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region).<br><br>Example:<pre>zones = ["1","2","3"]</pre> | `list(string)` | `null` | no | -| <a name="input_name"></a> [name](#input\_name) | Name of the Application Gateway. | `string` | n/a | yes | -| <a name="input_domain_name_label"></a> [domain\_name\_label](#input\_domain\_name\_label) | Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system. | `string` | `null` | no | -| <a name="input_managed_identities"></a> [managed\_identities](#input\_managed\_identities) | A list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault.<br><br>These identities have to have at least `GET` access to Key Vault's secrets. Otherwise Application Gateway will not be able to use certificates stored in the Vault. | `list(string)` | `null` | no | -| <a name="input_waf_enabled"></a> [waf\_enabled](#input\_waf\_enabled) | Enables WAF Application Gateway. This only sets the SKU. This module does not support WAF rules configuration. | `bool` | `"false"` | no | -| <a name="input_capacity"></a> [capacity](#input\_capacity) | A number of Application Gateway instances. A value bewteen 1 and 125.<br><br>This property is not used when autoscaling is enabled. | `number` | `2` | no | -| <a name="input_capacity_min"></a> [capacity\_min](#input\_capacity\_min) | When set enables autoscaling and becomes the minimum capacity. | `number` | `null` | no | -| <a name="input_capacity_max"></a> [capacity\_max](#input\_capacity\_max) | Optional, maximum capacity for autoscaling. | `number` | `null` | no | -| <a name="input_enable_http2"></a> [enable\_http2](#input\_enable\_http2) | Enable HTTP2 on the Application Gateway. | `bool` | `false` | no | -| <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id) | An ID of a subnet that will host the Application Gateway. Keep in mind that this subnet can contain only AppGWs and only of the same type. | `string` | n/a | yes | -| <a name="input_vmseries_ips"></a> [vmseries\_ips](#input\_vmseries\_ips) | IP addresses of VMSeries' interfaces that will serve as backends for the Application Gateway. | `list(string)` | `[]` | no | -| <a name="input_rules"></a> [rules](#input\_rules) | A map of rules for the Application Gateway. A rule combines listener, http settings and health check configuration. <br>A key is an application name that is used to prefix all components inside Application Gateway that are created for this application. <br><br>Details on configuration can be found [here](#rules-property-explained). | `any` | n/a | yes | -| <a name="input_ssl_policy_type"></a> [ssl\_policy\_type](#input\_ssl\_policy\_type) | Type of an SSL policy. Possible values are `Predefined` or `Custom`.<br>If the value is `Custom` the following values are mandatory: `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. | `string` | `"Predefined"` | no | -| <a name="input_ssl_policy_name"></a> [ssl\_policy\_name](#input\_ssl\_policy\_name) | Name of an SSL policy. Supported only for `ssl_policy_type` set to `Predefined`. Normally you can set it also for `Custom` policies but the name is discarded on Azure side causing an update to Application Gateway each time terraform code is run. Therefore this property is omitted in the code for `Custom` policies. <br><br>For the `Predefined` polcies, check the [Microsoft documentation](https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview) for possible values as they tend to change over time. The default value is currently (Q1 2022) a Microsoft's default. | `string` | `"AppGwSslPolicy20220101S"` | no | -| <a name="input_ssl_policy_min_protocol_version"></a> [ssl\_policy\_min\_protocol\_version](#input\_ssl\_policy\_min\_protocol\_version) | Minimum version of the TLS protocol for SSL Policy. Required only for `ssl_policy_type` set to `Custom`. <br><br>Possible values are: `TLSv1_0`, `TLSv1_1`, `TLSv1_2` or `null` (only to be used with a `Predefined` policy). | `string` | `"TLSv1_2"` | no | -| <a name="input_ssl_policy_cipher_suites"></a> [ssl\_policy\_cipher\_suites](#input\_ssl\_policy\_cipher\_suites) | A list of accepted cipher suites. Required only for `ssl_policy_type` set to `Custom`. <br>For possible values see [documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites). | `list(string)` | <pre>[<br> "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",<br> "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",<br> "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",<br> "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"<br>]</pre> | no | -| <a name="input_ssl_profiles"></a> [ssl\_profiles](#input\_ssl\_profiles) | A map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property. <br><br>The structure of the map is as follows:<pre>{<br> profile_name = {<br> ssl_policy_type = string<br> ssl_policy_min_protocol_version = string<br> ssl_policy_cipher_suites = list<br> }<br>}</pre>For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` variables as SSL profile is a named SSL policy - same properties apply. The only difference is that you cannot name an SSL policy inside an SSL profile. | `map(any)` | `{}` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | Azure tags to apply to the created resources. | `map(string)` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_public_ip"></a> [public\_ip](#output\_public\_ip) | A public IP assigned to the Application Gateway. | -| <a name="output_public_domain_name"></a> [public\_domain\_name](#output\_public\_domain\_name) | Public domain name assigned to the Application Gateway. | -| <a name="output_backend_pool_id"></a> [backend\_pool\_id](#output\_backend\_pool\_id) | The identifier of the Application Gateway backend address pool. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> + +Default value: `map[minimum:map[name:minimum]]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index f92ce8ba..268c68f6 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -1,40 +1,30 @@ locals { # Calculate a map of unique frontend ports based on the `listener.port` values defined in `rules` map. - # A unque set of ports will be created upfront and then referenced in the listener's config. - front_ports_list = distinct([for k, v in var.rules : v.listener.port]) + # A unique set of ports will be created upfront and then referenced in the listener's config. + front_ports_list = distinct([for k, v in var.listeners : v.port]) front_ports_map = { for v in local.front_ports_list : v => v } # Calculate a flat map of all backend's trusted root certificates. # Root certs are created upfront and then referenced in a single list in the http setting's config. root_certs_flat_list = flatten([ - for k, v in var.rules : [ - for name, path in v.backend.root_certs : { - name = "${k}-${name}" - path = path - } - ] if can(v.backend.root_certs) + for k, v in var.backends : [ + for key, root_cert in v.root_certs : root_cert + ] ]) - root_certs_map = { for v in local.root_certs_flat_list : v.name => v.path } +} - # Create a flat map of all path_rules for a path based rule. - # This will be used to create `url_path_map` object. - url_path_maps_flattened = flatten( - [for k, v in var.rules : [ - for map_k, map_v in v.url_path_maps : merge( - { "rule_name" = map_k }, - { "app_name" = k }, - map_v - ) - ] if can(v.url_path_maps)] - ) - url_path_maps_settings = { for v in local.url_path_maps_flattened : - "${v.app_name}-${v.rule_name}" => v - } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip +data "azurerm_public_ip" "this" { + count = var.public_ip.create ? 0 : 1 + name = var.public_ip.name + resource_group_name = coalesce(var.public_ip.resource_group, var.resource_group_name) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - name = "${var.name}-pip" + count = var.public_ip.create ? 1 : 0 + name = var.public_ip.name resource_group_name = var.resource_group_name location = var.location @@ -45,6 +35,7 @@ resource "azurerm_public_ip" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway resource "azurerm_application_gateway" "this" { name = var.name resource_group_name = var.resource_group_name @@ -54,17 +45,28 @@ resource "azurerm_application_gateway" "this" { tags = var.tags sku { - name = var.waf_enabled ? "WAF_v2" : "Standard_v2" - tier = var.waf_enabled ? "WAF_v2" : "Standard_v2" - capacity = var.capacity_min == null ? var.capacity : null + name = var.waf != null ? "WAF_v2" : "Standard_v2" + tier = var.waf != null ? "WAF_v2" : "Standard_v2" + capacity = var.capacity.static != null ? var.capacity.static : null } dynamic "autoscale_configuration" { - for_each = try(var.capacity_min, null) != null ? [1] : [] + for_each = var.capacity.autoscale != null ? [1] : [] content { - min_capacity = var.capacity_min - max_capacity = try(var.capacity_max, null) + min_capacity = var.capacity.autoscale.min + max_capacity = var.capacity.autoscale.max + } + } + + dynamic "waf_configuration" { + for_each = var.waf != null ? [1] : [] + + content { + enabled = var.waf != null + firewall_mode = var.waf.prevention_mode ? "Prevention" : "Detection" + rule_set_type = var.waf.rule_set_type + rule_set_version = var.waf.rule_set_version } } @@ -83,21 +85,21 @@ resource "azurerm_application_gateway" "this" { } frontend_ip_configuration { - name = "public_ipconfig" - public_ip_address_id = azurerm_public_ip.this.id + name = var.frontend_ip_configuration_name + public_ip_address_id = var.public_ip.create ? azurerm_public_ip.this[0].id : data.azurerm_public_ip.this[0].id } # There is only a single backend - the VMSeries private IPs assigned to untrusted NICs backend_address_pool { - name = "vmseries" - ip_addresses = var.vmseries_ips + name = var.backend_pool.name + ip_addresses = var.backend_pool.vmseries_ips } ssl_policy { - policy_name = var.ssl_policy_type == "Predefined" ? var.ssl_policy_name : null - policy_type = var.ssl_policy_type - min_protocol_version = var.ssl_policy_min_protocol_version - cipher_suites = var.ssl_policy_cipher_suites + policy_name = var.ssl_global.ssl_policy_type == "Predefined" ? var.ssl_global.ssl_policy_name : null + policy_type = var.ssl_global.ssl_policy_type + min_protocol_version = var.ssl_global.ssl_policy_min_protocol_version + cipher_suites = var.ssl_global.ssl_policy_cipher_suites } # The following block is supported only in v2 Application Gateways. @@ -105,12 +107,12 @@ resource "azurerm_application_gateway" "this" { for_each = var.ssl_profiles content { - name = ssl_profile.key + name = ssl_profile.value.name ssl_policy { - policy_name = var.ssl_policy_type == "Predefined" ? var.ssl_policy_name : null - policy_type = ssl_profile.value.ssl_policy_type - min_protocol_version = try(ssl_profile.value.ssl_policy_min_protocol_version, null) - cipher_suites = try(ssl_profile.value.ssl_policy_cipher_suites, null) + policy_name = var.ssl_global.ssl_policy_type == "Predefined" ? ssl_profile.value.ssl_policy_name : null + policy_type = var.ssl_global.ssl_policy_type + min_protocol_version = ssl_profile.value.ssl_policy_min_protocol_version + cipher_suites = ssl_profile.value.ssl_policy_cipher_suites } } } @@ -124,25 +126,25 @@ resource "azurerm_application_gateway" "this" { } dynamic "probe" { - for_each = { for k, v in merge(var.rules, local.url_path_maps_settings) : k => v if can(v.probe.path) } + for_each = var.probes content { - name = probe.key - path = probe.value.probe.path - protocol = try(probe.value.backend.protocol, "Http") - host = try(probe.value.probe.host, null) - pick_host_name_from_backend_http_settings = !can(probe.value.probe.host) - port = try(probe.value.probe.port, null) - interval = try(probe.value.probe.interval, 5) - timeout = try(probe.value.probe.timeout, 30) - unhealthy_threshold = try(probe.value.probe.threshold, 2) + name = probe.value.name + path = probe.value.path + protocol = probe.value.protocol + host = probe.value.host + pick_host_name_from_backend_http_settings = probe.value.host == null + port = probe.value.port + interval = probe.value.interval + timeout = probe.value.timeout + unhealthy_threshold = probe.value.threshold dynamic "match" { - for_each = can(probe.value.probe.match_code) ? [1] : [] + for_each = probe.value.match_code != null ? [1] : [] content { - status_code = probe.value.probe.match_code - body = try(probe.value.probe.match_body, null) + status_code = probe.value.match_code + body = probe.value.match_body } } } @@ -159,49 +161,50 @@ resource "azurerm_application_gateway" "this" { } dynamic "backend_http_settings" { - for_each = { for k, v in merge(var.rules, local.url_path_maps_settings) : k => v if !can(v.redirect.type) } + for_each = var.backends content { - name = backend_http_settings.key - port = try(backend_http_settings.value.backend.port, 80) - protocol = try(backend_http_settings.value.backend.protocol, "Http") - pick_host_name_from_backend_address = try(backend_http_settings.value.backend.hostname_from_backend, null) - host_name = try(backend_http_settings.value.backend.hostname, null) - path = try(backend_http_settings.value.backend.path, null) - request_timeout = try(backend_http_settings.value.backend.timeout, 60) - probe_name = can(backend_http_settings.value.probe.path) ? backend_http_settings.key : null - cookie_based_affinity = try(backend_http_settings.value.backend.cookie_based_affinity, "Enabled") - affinity_cookie_name = try(backend_http_settings.value.backend.affinity_cookie_name, null) - trusted_root_certificate_names = can(backend_http_settings.value.backend.root_certs) ? ( - [for k, v in backend_http_settings.value.backend.root_certs : "${backend_http_settings.key}-${k}"]) : null + name = backend_http_settings.value.name + port = backend_http_settings.value.port + protocol = backend_http_settings.value.protocol + pick_host_name_from_backend_address = backend_http_settings.value.hostname_from_backend + host_name = backend_http_settings.value.hostname + path = backend_http_settings.value.path + request_timeout = backend_http_settings.value.timeout + probe_name = (backend_http_settings.value.probe != null && var.probes != null ? + var.probes[backend_http_settings.value.probe].name : null) + cookie_based_affinity = backend_http_settings.value.cookie_based_affinity + affinity_cookie_name = backend_http_settings.value.affinity_cookie_name + trusted_root_certificate_names = [for k, v in backend_http_settings.value.root_certs : v.name] } } dynamic "ssl_certificate" { - for_each = { for k, v in var.rules : k => v if try(v.listener.ssl_certificate_path, v.listener.ssl_certificate_vault_id, null) != null } + for_each = { for k, v in var.listeners : k => v if try(v.ssl_certificate_path, v.ssl_certificate_vault_id, null) != null } content { name = ssl_certificate.key - data = try(filebase64(ssl_certificate.value.listener.ssl_certificate_path), null) - password = try(ssl_certificate.value.listener.ssl_certificate_pass, null) - key_vault_secret_id = try(ssl_certificate.value.listener.ssl_certificate_vault_id, null) + data = filebase64(ssl_certificate.value.ssl_certificate_path) + password = ssl_certificate.value.ssl_certificate_pass + key_vault_secret_id = ssl_certificate.value.ssl_certificate_vault_id } } dynamic "http_listener" { - for_each = var.rules + for_each = var.listeners content { - name = http_listener.key - frontend_ip_configuration_name = "public_ipconfig" - frontend_port_name = http_listener.value.listener.port - protocol = try(http_listener.value.listener.protocol, "Http") - host_names = try(http_listener.value.listener.host_names, null) - ssl_certificate_name = try(http_listener.value.listener.ssl_certificate_path, http_listener.value.listener.ssl_certificate_vault_id, null) != null ? http_listener.key : null - ssl_profile_name = try(http_listener.value.listener.ssl_profile_name, null) + name = http_listener.value.name + frontend_ip_configuration_name = var.frontend_ip_configuration_name + frontend_port_name = http_listener.value.port + protocol = http_listener.value.protocol + host_names = http_listener.value.host_names + ssl_certificate_name = (try(http_listener.value.ssl_certificate_path, + http_listener.value.ssl_certificate_vault_id, null) != null ? http_listener.key : null) + ssl_profile_name = http_listener.value.ssl_profile_name dynamic "custom_error_configuration" { - for_each = try(http_listener.value.listener.custom_error_pages, {}) + for_each = http_listener.value.custom_error_pages content { status_code = custom_error_configuration.key @@ -212,43 +215,44 @@ resource "azurerm_application_gateway" "this" { } dynamic "redirect_configuration" { - for_each = { for k, v in merge(var.rules, local.url_path_maps_settings) : k => v if can(v.redirect.type) } + for_each = var.redirects content { - name = redirect_configuration.key - redirect_type = redirect_configuration.value.redirect.type - target_listener_name = try(redirect_configuration.value.redirect.target_listener_name, null) - target_url = try(redirect_configuration.value.redirect.target_url, null) - include_path = try(redirect_configuration.value.redirect.include_path, null) - include_query_string = try(redirect_configuration.value.redirect.include_query_string, null) + name = redirect_configuration.value.name + redirect_type = redirect_configuration.value.type + target_listener_name = (redirect_configuration.value.target_listener != null ? + var.listeners[redirect_configuration.value.target_listener].name : null) + target_url = redirect_configuration.value.target_url + include_path = redirect_configuration.value.include_path + include_query_string = redirect_configuration.value.include_query_string } } dynamic "rewrite_rule_set" { - for_each = { for k, v in var.rules : k => v if can(v.rewrite_sets) } + for_each = var.rewrites content { - name = rewrite_rule_set.key + name = rewrite_rule_set.value.name dynamic "rewrite_rule" { - for_each = rewrite_rule_set.value.rewrite_sets + for_each = rewrite_rule_set.value.rules content { - name = rewrite_rule.key + name = rewrite_rule.value.name rule_sequence = rewrite_rule.value.sequence dynamic "condition" { - for_each = try(rewrite_rule.value.conditions, []) + for_each = rewrite_rule.value.conditions content { variable = condition.key pattern = condition.value.pattern - ignore_case = try(condition.value.ignore_case, null) - negate = try(condition.value.negate, null) + ignore_case = condition.value.ignore_case + negate = condition.value.negate } } dynamic "request_header_configuration" { - for_each = try(rewrite_rule.value.request_headers, []) + for_each = rewrite_rule.value.request_headers content { header_name = request_header_configuration.key header_value = request_header_configuration.value @@ -256,7 +260,7 @@ resource "azurerm_application_gateway" "this" { } dynamic "response_header_configuration" { - for_each = try(rewrite_rule.value.response_headers, []) + for_each = rewrite_rule.value.response_headers content { header_name = response_header_configuration.key header_value = response_header_configuration.value @@ -268,22 +272,22 @@ resource "azurerm_application_gateway" "this" { } dynamic "url_path_map" { - for_each = { for k, v in var.rules : k => v if can(v.url_path_maps) } + for_each = var.url_path_maps content { - name = url_path_map.key - default_backend_address_pool_name = "vmseries" - default_backend_http_settings_name = url_path_map.key + name = url_path_map.value.name + default_backend_address_pool_name = var.backend_pool.name + default_backend_http_settings_name = var.backends[url_path_map.value.backend].name dynamic "path_rule" { - for_each = url_path_map.value.url_path_maps + for_each = url_path_map.value.path_rules content { name = path_rule.key - paths = [path_rule.value.path] - backend_address_pool_name = can(path_rule.value.redirect.type) ? null : "vmseries" - backend_http_settings_name = can(path_rule.value.redirect.type) ? null : "${url_path_map.key}-${path_rule.key}" - redirect_configuration_name = can(path_rule.value.redirect.type) ? "${url_path_map.key}-${path_rule.key}" : null + paths = path_rule.value.paths + backend_address_pool_name = path_rule.value.backend != null ? var.backend_pool.name : null + backend_http_settings_name = path_rule.value.backend != null ? var.backends[path_rule.value.backend].name : null + redirect_configuration_name = path_rule.value.redirect != null ? var.redirects[path_rule.value.redirect].name : null } } } @@ -293,21 +297,40 @@ resource "azurerm_application_gateway" "this" { for_each = var.rules content { - name = request_routing_rule.key - rule_type = can(request_routing_rule.value.url_path_maps) ? "PathBasedRouting" : "Basic" - priority = try(request_routing_rule.value.priority, null) + name = request_routing_rule.value.name + rule_type = request_routing_rule.value.url_path_map != null ? "PathBasedRouting" : "Basic" + priority = request_routing_rule.value.priority - http_listener_name = request_routing_rule.key - - backend_address_pool_name = can(request_routing_rule.value.redirect.type) || can(request_routing_rule.value.url_path_maps) ? null : "vmseries" - backend_http_settings_name = can(request_routing_rule.value.redirect.type) || can(request_routing_rule.value.url_path_maps) ? null : request_routing_rule.key - - redirect_configuration_name = can(request_routing_rule.value.redirect.type) ? request_routing_rule.key : null - - rewrite_rule_set_name = can(request_routing_rule.value.rewrite_sets) ? request_routing_rule.key : null - - url_path_map_name = can(request_routing_rule.value.url_path_maps) ? request_routing_rule.key : null + http_listener_name = var.listeners[request_routing_rule.value.listener].name + backend_address_pool_name = ( + request_routing_rule.value.backend != null ? var.backend_pool.name : null + ) + backend_http_settings_name = ( + request_routing_rule.value.backend != null ? var.backends[request_routing_rule.value.backend].name : null + ) + redirect_configuration_name = ( + request_routing_rule.value.redirect != null ? var.redirects[request_routing_rule.value.redirect].name : null + ) + rewrite_rule_set_name = ( + request_routing_rule.value.rewrite != null ? var.rewrites[request_routing_rule.value.rewrite].name : null + ) + url_path_map_name = ( + request_routing_rule.value.url_path_map != null ? var.url_path_maps[request_routing_rule.value.url_path_map].name : null + ) } } + lifecycle { + precondition { + condition = var.probes != null ? alltrue(flatten([ + for k, probe in var.probes : probe.host != null || alltrue(flatten([ + for b, backend in var.backends : backend.probe == k ? backend.hostname != null || backend.hostname_from_backend : true + ])) + ])) : true + error_message = <<EOF +Custom health probes needs to have defined host or backend settings needs to +contain overriden host name or enabled option to pick host name from backend target. + EOF + } + } } diff --git a/modules/appgw/outputs.tf b/modules/appgw/outputs.tf index 7708d2eb..7bbe8f5a 100644 --- a/modules/appgw/outputs.tf +++ b/modules/appgw/outputs.tf @@ -1,11 +1,11 @@ output "public_ip" { description = "A public IP assigned to the Application Gateway." - value = azurerm_public_ip.this.ip_address + value = var.public_ip.create ? azurerm_public_ip.this[0].ip_address : data.azurerm_public_ip.this[0].ip_address } output "public_domain_name" { description = "Public domain name assigned to the Application Gateway." - value = azurerm_public_ip.this.fqdn + value = var.public_ip.create ? azurerm_public_ip.this[0].fqdn : data.azurerm_public_ip.this[0].fqdn } output "backend_pool_id" { diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index 74d449d3..9b79ea10 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -1,169 +1,682 @@ +# Main resource +variable "name" { + description = "The name of the Application Gateway." + type = string +} + +# Common settings variable "resource_group_name" { - description = "Name of an existing resource group." + description = "The name of the Resource Group to use." type = string } variable "location" { - description = "Location to place the Application Gateway in." + description = "The name of the Azure region to deploy the resources in." type = string } +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} + +# Application Gateway variable "zones" { description = <<-EOF A list of zones the Application Gateway should be available in. - NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to a single zone or zone-redundant (so available in all zones in a region). - Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during next `terraform apply` as there will be difference between the state and the actual configuration. + NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, + pinned to a single zone or zone-redundant (so available in all zones in a region). + Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, + but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during + next `terraform apply` as there will be difference between the state and the actual configuration. - For details on zones currently available in a region of your choice refer to [Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). + For details on zones currently available in a region of your choice refer to + [Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). Example: ``` zones = ["1","2","3"] ``` EOF - default = null + default = ["1", "2", "3"] type = list(string) + validation { + condition = var.zones == null || length(setsubtract(var.zones, ["1", "2", "3"])) == 0 + error_message = "The `var.zones` can either bea non empty list of Availability Zones or explicit `null`." + } } -variable "name" { - description = "Name of the Application Gateway." - type = string +variable "public_ip" { + description = "Public IP address." + type = object({ + name = string + resource_group = optional(string) + create = optional(bool, true) + }) } variable "domain_name_label" { - description = "Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." + description = <<-EOF + Label for the Domain Name. + + Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created + for the public IP in the Microsoft Azure DNS system." + EOF default = null type = string } -variable "managed_identities" { +variable "enable_http2" { + description = "Enable HTTP2 on the Application Gateway." + default = false + nullable = false + type = bool +} + +variable "waf" { description = <<-EOF - A list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. - These identities have to have at least `GET` access to Key Vault's secrets. Otherwise Application Gateway will not be able to use certificates stored in the Vault. + This module does not support WAF rules configuration and advanced WAF settings. + Only below attributes are allowed: + - `prevention_mode` - (`bool`, required) `true` if WAF mode is Prevention, `false` for Detection mode + - `rule_set_type` - (`string`, optional, defaults to `OWASP`) The Type of the Rule Set used for this Web Application Firewall + - `rule_set_version` - (`string`, optional) The Version of the Rule Set used for this Web Application Firewall EOF default = null - type = list(string) -} - -variable "waf_enabled" { - description = "Enables WAF Application Gateway. This only sets the SKU. This module does not support WAF rules configuration." - default = "false" - type = bool + type = object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + }) + validation { + condition = var.waf != null ? contains(["OWASP", "Microsoft_BotManagerRuleSet"], var.waf.rule_set_type) : true + error_message = "For `rule_set_type` possible values are OWASP and Microsoft_BotManagerRuleSet" + } + validation { + condition = var.waf != null ? contains( + ["0.1", "1.0", "2.2.9", "3.0", "3.1", "3.2"], coalesce(var.waf.rule_set_version, "3.2")) : true + error_message = "For `rule_set_version` possible values are 0.1, 1.0, 2.2.9, 3.0, 3.1 and 3.2" + } } variable "capacity" { description = <<-EOF - A number of Application Gateway instances. A value bewteen 1 and 125. + Capacity configuration for Application Gateway. - This property is not used when autoscaling is enabled. + Object defines static or autoscale configuration using attributes: + - `static` - (`number`, optional) A static number of Application Gateway instances. A value bewteen 1 and 125 + or null, if autoscale configuration is provided + - `autoscale` - (`object`, optional) Autoscaling configuration (used only, if static is null) with attributes: + - `min` - (`number`, optional) Minimum capacity for autoscaling. + - `max` - (`number`, optional) Maximum capacity for autoscaling. EOF - default = 2 - type = number + default = { + static = 2 + } + nullable = false + type = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + validation { + condition = coalesce(var.capacity.static, 1) >= 1 && coalesce(var.capacity.static, 125) <= 125 + error_message = "Static number of Application Gateway instances must be between 1 to 125." + } + validation { + condition = (var.capacity.static != null && var.capacity.autoscale == null + || var.capacity.static == null && var.capacity.autoscale != null) + error_message = "Only 1 capacity configuration can be used - static or autoscale." + } } -variable "capacity_min" { - description = "When set enables autoscaling and becomes the minimum capacity." +variable "managed_identities" { + description = <<-EOF + A list of existing User-Assigned Managed Identities. + + Application Gateway uses Managed Identities to retrieve certificates from Key Vault. + These identities have to have at least `GET` access to Key Vault's secrets. + Otherwise Application Gateway will not be able to use certificates stored in the Vault. + EOF default = null - type = number + type = list(string) } -variable "capacity_max" { - description = "Optional, maximum capacity for autoscaling." - default = null - type = number +variable "subnet_id" { + description = <<-EOF + An ID of a subnet that will host the Application Gateway. + + Keep in mind that this subnet can contain only AppGWs and only of the same type. + EOF + type = string } -variable "enable_http2" { - description = "Enable HTTP2 on the Application Gateway." - default = false - type = bool +variable "ssl_global" { + description = <<-EOF + Global SSL settings. + + SSL settings are defined by attributes: + - `ssl_policy_type` - (`string`, required) type of an SSL policy. Possible values are `Predefined` + or `Custom` or `CustomV2`. If the value is `Custom` the following values are mandatory: + `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. + - `ssl_policy_name` - (`string`, optional) name of an SSL policy. + Supported only for `ssl_policy_type` set to `Predefined`. + Normally you can set it also for `Custom` policies but the name is discarded + on Azure side causing an update to Application Gateway each time terraform code is run. + Therefore this property is omitted in the code for `Custom` policies. + For the `Predefined` policies, check the Microsoft documentation + https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview + for possible values as they tend to change over time. + The default value is currently (Q1 2023) a Microsoft's default. + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy. + Required only for `ssl_policy_type` set to `Custom`. + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. + Required only for `ssl_policy_type` set to `Custom`. + For possible values see documentation: + https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites + EOF + default = { + ssl_policy_type = "Predefined" + ssl_policy_name = "AppGwSslPolicy20220101S" + ssl_policy_min_protocol_version = null + ssl_policy_cipher_suites = [] + } + nullable = false + type = object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }) + validation { + condition = contains(["Predefined", "Custom", "CustomV2"], var.ssl_global.ssl_policy_type) + error_message = "For global SSL settings possible types are Predefined, Custom and CustomV2." + } + validation { + condition = contains(["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], + coalesce(var.ssl_global.ssl_policy_min_protocol_version, "TLSv1_3")) + error_message = "For global SSL settings possible min protocol versions are TLSv1_0, TLSv1_1, TLSv1_2 and TLSv1_3." + } + validation { + condition = length(setsubtract(coalesce(var.ssl_global.ssl_policy_cipher_suites, []), + ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384"])) == 0 + error_message = <<-EOF + For global SSL settings possible cipher suites are: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA256 and TLS_RSA_WITH_AES_256_GCM_SHA384." + EOF + } } -variable "subnet_id" { - description = "An ID of a subnet that will host the Application Gateway. Keep in mind that this subnet can contain only AppGWs and only of the same type." - type = string +variable "ssl_profiles" { + description = <<-EOF + A map of SSL profiles. + + SSL profiles can be later on referenced in HTTPS listeners by providing a name of the profile in the `name` property. + For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` + variables as SSL profile is a named SSL policy - same properties apply. + The only difference is that you cannot name an SSL policy inside an SSL profile. + + Every SSL profile contains attributes: + - `name` - (`string`, required) name of the SSL profile + - `ssl_policy_name` - (`string`, optional) name of predefined policy + - `ssl_policy_min_protocol_version` - (`string`, optional) the minimal TLS version. + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. + EOF + type = map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + validation { + condition = alltrue(flatten([ + for _, ssl_profile in var.ssl_profiles : [ + contains(["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], coalesce(ssl_profile.ssl_policy_min_protocol_version, "TLSv1_3")) + ]])) + error_message = "Possible values for `ssl_policy_min_protocol_version` are TLSv1_0, TLSv1_1, TLSv1_2 and TLSv1_3." + } + validation { + condition = alltrue(flatten([ + for _, ssl_profile in var.ssl_profiles : [ + length(setsubtract(coalesce(ssl_profile.ssl_policy_cipher_suites, []), + ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384"] + )) == 0 + ]])) + error_message = <<-EOF + Possible values for `ssl_policy_cipher_suites` are TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_3DES_EDE_CBC_SHA, + TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, + TLS_RSA_WITH_AES_256_CBC_SHA256 and TLS_RSA_WITH_AES_256_GCM_SHA384. + EOF + } + validation { + condition = (length(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])) == + length(distinct(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])))) + error_message = "The `name` property has to be unique among all SSL profiles." + } } -variable "vmseries_ips" { - description = "IP addresses of VMSeries' interfaces that will serve as backends for the Application Gateway." - default = [] - type = list(string) +variable "frontend_ip_configuration_name" { + description = "Frontend IP configuration name" + default = "public_ipconfig" + nullable = false + type = string } -variable "rules" { +variable "listeners" { description = <<-EOF - A map of rules for the Application Gateway. A rule combines listener, http settings and health check configuration. - A key is an application name that is used to prefix all components inside Application Gateway that are created for this application. + A map of listeners for the Application Gateway. - Details on configuration can be found [here](#rules-property-explained). + Every listener contains attributes: + - `name` - (`string`, required) The name for this Frontend Port. + - `port` - (`string`, required) The port used for this Frontend Port. + - `protocol` - (`string`, optional) The Protocol to use for this HTTP Listener. + - `host_names` - (`list`, optional) A list of Hostname(s) should be used for this HTTP Listener. + It allows special wildcard characters. + - `ssl_profile_name` - (`string`, optional) The name of the associated SSL Profile which should be used + for this HTTP Listener. + - `ssl_certificate_path` - (`string`, optional) Path to the file with tThe base64-encoded PFX certificate data. + - `ssl_certificate_pass` - (`string`, optional) Password for the pfx file specified in data. + - `ssl_certificate_vault_id` - (`string`, optional) Secret Id of (base-64 encoded unencrypted pfx) Secret + or Certificate object stored in Azure KeyVault. + - `custom_error_pages` - (`map`, optional) Map of string, where key is HTTP status code and value is + error page URL of the application gateway customer error. EOF - type = any + type = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + validation { + condition = alltrue(flatten([ + for _, listener in var.listeners : [ + contains(["Http", "Https"], listener.protocol) + ]])) + error_message = "Possible values for `protocol` are `Http` and `Https`." + } + validation { + condition = alltrue(flatten([ + for _, listener in var.listeners : (listener.port >= 1 && listener.port <= 65535) + ])) + error_message = "The listener `port` should be a valid TCP port number from 1 to 65535." + } + validation { + condition = alltrue(flatten([ + for _, listener in var.listeners : (listener.protocol == "Https" ? + try(length(coalesce(listener.ssl_certificate_vault_id, listener.ssl_certificate_path)), -1) > 0 + : true) + ])) + error_message = "If `Https` protocol is used, then SSL certificate (from file or Azure Key Vault) is required" + } + validation { + condition = alltrue(flatten([ + for _, listener in var.listeners : (listener.protocol == "Https" ? + try(length(listener.ssl_certificate_pass), -1) >= 0 + : true) + ])) + error_message = "If `Https` protocol is used, then SSL certificate password is required" + } + validation { + condition = (length(flatten([for _, listener in var.listeners : listener.name])) == + length(distinct(flatten([for _, listener in var.listeners : listener.name])))) + error_message = "The `name` property has to be unique among all listeners." + } } -variable "ssl_policy_type" { +variable "backend_pool" { description = <<-EOF - Type of an SSL policy. Possible values are `Predefined` or `Custom`. - If the value is `Custom` the following values are mandatory: `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. + Backend pool. + + Object contains attributes: + - `name` - (`string`, required) name of the backend pool. + - `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VMSeries' interfaces that will serve as backends + for the Application Gateway. EOF - default = "Predefined" - type = string - nullable = false + default = { + name = "vmseries" + } + nullable = false + type = object({ + name = string + vmseries_ips = optional(list(string), []) + }) } -variable "ssl_policy_name" { +variable "backends" { description = <<-EOF - Name of an SSL policy. Supported only for `ssl_policy_type` set to `Predefined`. Normally you can set it also for `Custom` policies but the name is discarded on Azure side causing an update to Application Gateway each time terraform code is run. Therefore this property is omitted in the code for `Custom` policies. - - For the `Predefined` polcies, check the [Microsoft documentation](https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview) for possible values as they tend to change over time. The default value is currently (Q1 2022) a Microsoft's default. + A map of backend settings for the Application Gateway. + + Every backend contains attributes: + - `name` - (`string`, optional) The name of the backend settings + - `path` - (`string`, optional) The Path which should be used as a prefix for all HTTP requests. + - `hostname_from_backend` - (`bool`, optional) Whether host header should be picked from the host name of the backend server. + - `hostname` - (`string`, optional) Host header to be sent to the backend servers. + - `port` - (`number`, optional) The port which should be used for this Backend HTTP Settings Collection. + - `protocol` - (`string`, optional) The Protocol which should be used. Possible values are Http and Https. + - `timeout` - (`number`, optional) The request timeout in seconds, which must be between 1 and 86400 seconds. + - `cookie_based_affinity` - (`string`, optional) Is Cookie-Based Affinity enabled? Possible values are Enabled and Disabled. + - `affinity_cookie_name` - (`string`, optional) The name of the affinity cookie. + - `probe` - (`string`, optional) Probe's key. + - `root_certs` - (`map`, optional) A list of trusted_root_certificate names. EOF - default = "AppGwSslPolicy20220101S" - type = string - nullable = false + default = { + "minimum" = { + name = "minimum" + } + } + nullable = false + type = map(object({ + name = optional(string) + path = optional(string) + hostname_from_backend = optional(bool, false) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + })) + validation { + condition = alltrue(flatten([ + for _, backend in var.backends : [ + contains(["Http", "Https"], backend.protocol) + ]])) + error_message = "Possible values for `protocol` are `Http` and `Https`." + } + validation { + condition = alltrue(flatten([ + for _, backend in var.backends : [ + contains(["Enabled", "Disabled"], backend.cookie_based_affinity) + ]])) + error_message = "Possible values for `cookie_based_affinity` are Enabled and Disabled." + } + validation { + condition = alltrue(flatten([ + for _, backend in var.backends : (backend.port >= 1 && backend.port <= 65535) + ])) + error_message = "The backend `port` should be a valid TCP port number from 1 to 65535." + } + validation { + condition = alltrue(flatten([ + for _, backend in var.backends : (backend.timeout != null ? backend.timeout >= 1 && backend.timeout <= 86400 : true) + ])) + error_message = "The backend `timeout` property should can take values between 1 and 86400 (seconds)." + } + validation { + condition = (length(flatten([for _, backend in var.backends : backend.name])) == + length(distinct(flatten([for _, backend in var.backends : backend.name])))) + error_message = "The `name` property has to be unique among all backends." + } } -variable "ssl_policy_min_protocol_version" { +variable "probes" { description = <<-EOF - Minimum version of the TLS protocol for SSL Policy. Required only for `ssl_policy_type` set to `Custom`. + A map of probes for the Application Gateway. - Possible values are: `TLSv1_0`, `TLSv1_1`, `TLSv1_2` or `null` (only to be used with a `Predefined` policy). + Every probe contains attributes: + - `name` - (`string`, required) The name used for this Probe + - `path` - (`string`, required) The path used for this Probe + - `host` - (`string`, optional) The hostname used for this Probe + - `port` - (`number`, optional) Custom port which will be used for probing the backend servers. + - `protocol` - (`string`, optional, defaults `Http`) The protocol which should be used. + - `interval` - (`number`, optional, defaults `5`) The interval between two consecutive probes in seconds. + - `timeout` - (`number`, optional, defaults `30`) The timeout used for this Probe, + which indicates when a probe becomes unhealthy. + - `threshold` - (`number`, optional, defaults `2`) The unhealthy Threshold for this Probe, which indicates + the amount of retries which should be attempted before a node is deemed unhealthy. + - `match_code` - (`list`, optional) The list of allowed status codes for this Health Probe. + - `match_body` - (`string`, optional) A snippet from the Response Body which must be present in the Response. EOF - default = "TLSv1_2" - type = string + type = map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })) + validation { + condition = var.probes != null ? alltrue(flatten([ + for _, backend in var.probes : [ + contains(["Http", "Https"], backend.protocol) + ]])) : true + error_message = "Possible values for `protocol` are `Http` and `Https`." + } + validation { + condition = (length(flatten([for _, probe in var.probes : probe.name])) == + length(distinct(flatten([for _, probe in var.probes : probe.name])))) + error_message = "The `name` property has to be unique among all probes." + } + validation { + condition = alltrue(flatten([ + for _, probe in var.probes : ((coalesce(probe.port, 80)) >= 1 && (coalesce(probe.port, 80)) <= 65535) + ])) + error_message = "The probe `port` should be a valid TCP port number from 1 to 65535." + } + validation { + condition = alltrue(flatten([ + for _, probe in var.probes : (probe.timeout != null ? probe.timeout >= 1 && probe.timeout <= 86400 : true) + ])) + error_message = "The probe `timeout` property should can take values between 1 and 86400 (seconds)." + } + validation { + condition = alltrue(flatten([ + for _, probe in var.probes : (probe.interval != null ? probe.interval >= 1 && probe.interval <= 86400 : true) + ])) + error_message = "The probe `interval` property should can take values between 1 and 86400 (seconds)." + } + validation { + condition = alltrue(flatten([ + for _, probe in var.probes : (probe.threshold != null ? probe.threshold >= 1 && probe.threshold <= 20 : true) + ])) + error_message = "The probe `threshold` property should can take values between 1 and 20." + } } -variable "ssl_policy_cipher_suites" { +variable "rewrites" { description = <<-EOF - A list of accepted cipher suites. Required only for `ssl_policy_type` set to `Custom`. - For possible values see [documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites). + A map of rewrites for the Application Gateway. + + Every rewrite contains attributes: + - `name` - (`string`) Rewrite Rule Set name + - `rules` - (`object`, optional) Rewrite Rule Set defined with attributes: + - `name` - (`string`, required) Rewrite Rule name. + - `sequence` - (`number`, required) Rule sequence of the rewrite rule that determines + the order of execution in a set. + - `conditions` - (`map`, optional) One or more condition blocks as defined below: + - `pattern` - (`string`, required) The pattern, either fixed string or regular expression, + that evaluates the truthfulness of the condition. + - `ignore_case` - (`string`, optional, defaults to `false`) Perform a case in-sensitive comparison. + - `negate` - (`bool`, optional, defaults to `false`) Negate the result of the condition evaluation. + - `request_headers` - (`map`, optional) Map of request header, where header name is the key, + header value is the value of the object in the map. + - `response_headers`- (`map`, optional) Map of response header, where header name is the key, + header value is the value of the object in the map. EOF - default = ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] - type = list(string) + type = map(object({ + name = string + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })) + validation { + condition = (length(flatten([for _, rewrite in var.rewrites : rewrite.name])) == + length(distinct(flatten([for _, rewrite in var.rewrites : rewrite.name])))) + error_message = "The `name` property has to be unique among all rewrites." + } } -variable "ssl_profiles" { +variable "rules" { description = <<-EOF - A map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property. + A map of rules for the Application Gateway. - The structure of the map is as follows: - ``` - { - profile_name = { - ssl_policy_type = string - ssl_policy_min_protocol_version = string - ssl_policy_cipher_suites = list - } + A rule combines, backend, listener, rewrites and redirects configurations. + A key is an application name that is used to prefix all components inside Application Gateway + that are created for this application. + + Every rule contains attributes: + - `name` - (`string`, required) Rule name. + - `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value + from 1 to 20000 with 1 being the highest priority and 20000 being the lowest priority. + - `backend` - (`string`, optional) Backend settings` key + - `listener` - (`string`, required) Listener's key + - `rewrite` - (`string`, optional) Rewrite's key + - `url_path_map` - (`string`, optional) URL Path Map's key + - `redirect` - (`string`, optional) Redirect's key + EOF + type = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + validation { + condition = alltrue(flatten([ + for _, rule in var.rules : [ + rule.priority >= 1, rule.priority <= 20000 + ]])) + error_message = "Rule priority is integer value from 1 to 20000." } - ``` - For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` variables as SSL profile is a named SSL policy - same properties apply. The only difference is that you cannot name an SSL policy inside an SSL profile. + validation { + condition = (length(flatten([for _, rule in var.rules : rule.name])) == + length(distinct(flatten([for _, rule in var.rules : rule.name])))) + error_message = "The `name` property has to be unique among all rules." + } + validation { + condition = alltrue([for _, rule in var.rules : + rule.backend != null && rule.redirect == null && rule.url_path_map == null || + rule.backend == null && rule.redirect != null && rule.url_path_map == null || + rule.backend == null && rule.redirect == null && rule.url_path_map != null + ]) + error_message = "Either `backend`, `redirect` or `url_path_map` is required, but not all as they are mutually exclusive." + } +} + +variable "redirects" { + description = <<-EOF + A map of redirects for the Application Gateway. + + Every redirect contains attributes: + - `name` - (`string`, required) The name of redirect. + - `type` - (`string`, required) The type of redirect. + Possible values are Permanent, Temporary, Found and SeeOther + - `target_listener` - (`string`, optional) The name of the listener to redirect to. + - `target_url` - (`string`, optional) The URL to redirect the request to. + - `include_path` - (`bool`, optional) Whether or not to include the path in the redirected URL. + - `include_query_string` - (`bool`, optional) Whether or not to include the query string in the redirected URL. EOF - default = {} - type = map(any) + type = map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })) + validation { + condition = var.redirects != null ? alltrue(flatten([ + for _, redirect in var.redirects : [ + contains(["Permanent", "Temporary", "Found", "SeeOther"], coalesce(redirect.type, "Permanent")) + ]])) : true + error_message = "Possible values for `type` are Permanent, Temporary, Found and SeeOther." + } + validation { + condition = (length(flatten([for _, redirect in var.redirects : redirect.name])) == + length(distinct(flatten([for _, redirect in var.redirects : redirect.name])))) + error_message = "The `name` property has to be unique among all redirects." + } } -variable "tags" { - description = "Azure tags to apply to the created resources." - default = {} - type = map(string) -} \ No newline at end of file +variable "url_path_maps" { + description = <<-EOF + A map of URL path maps for the Application Gateway. + + Every URL path map contains attributes: + - `name` - (`string`, required) The name of redirect. + - `backend` - (`string`, required) The default backend for redirect. + - `path_rules` - (`map`, optional) The map of rules, where every object has attributes: + - `paths` - (`list`, required) List of paths + - `backend` - (`string`, optional) Backend's key + - `redirect` - (`string`, optional) Redirect's key + EOF + type = map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })) + validation { + condition = (length(flatten([for _, url_path_map in var.url_path_maps : url_path_map.name])) == + length(distinct(flatten([for _, url_path_map in var.url_path_maps : url_path_map.name])))) + error_message = "The `name` property has to be unique among all URL path maps." + } +} diff --git a/modules/appgw/versions.tf b/modules/appgw/versions.tf index 501042ff..8dc5c1eb 100644 --- a/modules/appgw/versions.tf +++ b/modules/appgw/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 7e8b486796f4a041352eac14b239d53a1479247b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:49:22 +0100 Subject: [PATCH 15/49] refactor: vmss + application insights (#360) --- .gitignore | 6 + examples/appgw/README.md | 6 +- examples/appgw/variables.tf | 6 +- examples/common_vmseries/example.tfvars | 6 +- examples/common_vmseries/main.tf | 34 +- examples/common_vmseries/outputs.tf | 2 +- examples/common_vmseries/variables.tf | 60 +- .../example.tfvars | 107 ++- .../common_vmseries_and_autoscale/main.tf | 110 +-- .../common_vmseries_and_autoscale/outputs.tf | 14 +- .../variables.tf | 312 ++++--- .../common_vmseries_and_autoscale/versions.tf | 2 +- .../bootstrap_package/software/10.2.4.img | 1 + examples/dedicated_vmseries/example.tfvars | 10 +- examples/dedicated_vmseries/main.tf | 32 +- examples/dedicated_vmseries/outputs.tf | 2 +- examples/dedicated_vmseries/variables.tf | 63 +- .../example.tfvars | 153 +--- .../dedicated_vmseries_and_autoscale/main.tf | 110 +-- .../outputs.tf | 14 +- .../variables.tf | 312 ++++--- .../versions.tf | 2 +- examples/gwlb_with_vmseries/main.tf | 31 +- examples/gwlb_with_vmseries/variables.tf | 60 +- examples/lb/.header.md | 3 - examples/lb/README.md | 290 ------ examples/lb/brownfield/main.tf | 46 - examples/lb/example.tfvars | 123 --- examples/lb/main.tf | 88 -- examples/lb/outputs.tf | 47 - examples/lb/variables.tf | 192 ---- examples/lb/versions.tf | 22 - examples/standalone_panorama/example.tfvars | 2 +- examples/standalone_panorama/variables.tf | 2 +- examples/standalone_vmseries/example.tfvars | 2 +- examples/standalone_vmseries/main.tf | 33 +- examples/standalone_vmseries/outputs.tf | 2 +- examples/standalone_vmseries/variables.tf | 71 +- examples/test_infrastructure/example.tfvars | 14 +- examples/vnet/brownfield/main.tf | 37 - examples/vnet/example.tfvars | 108 --- examples/vnet/variables.tf | 110 --- modules/appgw/README.md | 23 +- modules/appgw/main.tf | 2 +- modules/appgw/variables.tf | 23 +- modules/appgw/versions.tf | 2 +- modules/application_insights/README.md | 85 -- modules/application_insights/main.tf | 24 - modules/application_insights/outputs.tf | 10 - modules/application_insights/variables.tf | 49 - modules/bootstrap/versions.tf | 2 +- modules/name_templater/versions.tf | 2 +- modules/ngfw_metrics/.header.md | 55 ++ modules/ngfw_metrics/README.md | 228 +++++ modules/ngfw_metrics/main.tf | 36 + .../main_test.go | 2 +- modules/ngfw_metrics/outputs.tf | 10 + modules/ngfw_metrics/variables.tf | 98 ++ .../versions.tf | 4 +- modules/panorama/versions.tf | 2 +- modules/vmseries/versions.tf | 2 +- modules/vmss/.header.md | 99 ++ modules/vmss/README.md | 732 ++++++++++++--- modules/vmss/dt_string_converter/.header.md | 7 + modules/vmss/dt_string_converter/README.md | 50 ++ modules/vmss/dt_string_converter/main.tf | 19 + modules/vmss/main.tf | 489 ++++++---- modules/vmss/outputs.tf | 11 + modules/vmss/variables.tf | 845 +++++++++++------- modules/vmss/versions.tf | 4 +- 70 files changed, 2890 insertions(+), 2672 deletions(-) create mode 100644 examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img delete mode 100644 examples/lb/.header.md delete mode 100644 examples/lb/README.md delete mode 100644 examples/lb/brownfield/main.tf delete mode 100644 examples/lb/example.tfvars delete mode 100644 examples/lb/main.tf delete mode 100644 examples/lb/outputs.tf delete mode 100644 examples/lb/variables.tf delete mode 100644 examples/lb/versions.tf delete mode 100644 examples/vnet/brownfield/main.tf delete mode 100644 examples/vnet/example.tfvars delete mode 100644 examples/vnet/variables.tf delete mode 100644 modules/application_insights/README.md delete mode 100644 modules/application_insights/main.tf delete mode 100644 modules/application_insights/outputs.tf delete mode 100644 modules/application_insights/variables.tf create mode 100644 modules/ngfw_metrics/.header.md create mode 100644 modules/ngfw_metrics/README.md create mode 100644 modules/ngfw_metrics/main.tf rename modules/{application_insights => ngfw_metrics}/main_test.go (86%) create mode 100644 modules/ngfw_metrics/outputs.tf create mode 100644 modules/ngfw_metrics/variables.tf rename modules/{application_insights => ngfw_metrics}/versions.tf (61%) create mode 100644 modules/vmss/.header.md create mode 100644 modules/vmss/dt_string_converter/.header.md create mode 100644 modules/vmss/dt_string_converter/README.md create mode 100644 modules/vmss/dt_string_converter/main.tf diff --git a/.gitignore b/.gitignore index 41853f5f..ad65ccc2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,12 @@ .vscode .idea + +# Ceritifcates +*.pem +*.crt +*.pfx +*.key # Palo auth codes authcodes # Crash log files diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 008b3abd..abb9fc3b 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -208,9 +208,9 @@ Type: map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf index e239a86d..bcdece72 100644 --- a/examples/appgw/variables.tf +++ b/examples/appgw/variables.tf @@ -168,9 +168,9 @@ variable "appgws" { type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 6fd79705..e74cc3fc 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -7,7 +7,6 @@ tags = { "CreatedWith" = "Terraform" } - # --- VNET PART --- # vnets = { "transit" = { @@ -23,7 +22,7 @@ vnets = { 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_address_prefixes = ["1.2.3.4"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -157,6 +156,9 @@ load_balancers = { } } +ngfw_metrics = { + name = "metrics" +} # --- VMSERIES PART --- # diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 58cf1f2c..07262aed 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -17,7 +17,7 @@ locals { # Obtain Public IP address of code deployment machine data "http" "this" { - count = length(var.bootstrap_storage) > 0 && contains([for v in values(var.bootstrap_storage) : v.storage_acl], true) ? 1 : 0 + count = length(var.bootstrap_storage) > 0 && anytrue([for v in values(var.bootstrap_storage) : try(v.storage_acl, false)]) ? 1 : 0 url = "https://ifconfig.me/ip" } @@ -133,23 +133,23 @@ module "load_balancer" { # create the actual VMSeries VMs and resources -module "ai" { - source = "../../modules/application_insights" +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + count = var.ngfw_metrics != null ? 1 : 0 - name = "${var.name_prefix}${each.key}" - resource_group_name = local.resource_group.name + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } tags = var.tags } @@ -177,7 +177,7 @@ resource "local_file" "bootstrap_xml" { 1 ) - ai_instr_key = try(module.ai[try(var.application_insights.name, "${each.value.name}-ai")].metrics_instrumentation_key, null) + ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) ai_update_interval = try( each.value.bootstrap_storage.ai_update_interval, @@ -198,7 +198,7 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + module.ngfw_metrics, module.vnet ] } @@ -334,7 +334,7 @@ module "appgw" { name = "vmseries" vmseries_ips = [ for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name ].private_ip_address if try(v.add_to_appgw_backend, false) ] } diff --git a/examples/common_vmseries/outputs.tf b/examples/common_vmseries/outputs.tf index d652953d..10533a56 100644 --- a/examples/common_vmseries/outputs.tf +++ b/examples/common_vmseries/outputs.tf @@ -19,7 +19,7 @@ output "natgw_public_ips" { output "metrics_instrumentation_keys" { description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." - value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) sensitive = true } diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index c31961b9..56f820b2 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -318,42 +318,38 @@ variable "availability_sets" { type = any } -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: + A map controlling metrics-relates resources. - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. + When set to explicit `null` (default) it will disable any metrics resources in this deployment. - Names for all AI instances are prefixed with `var.name_prefix`. + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - Properties supported (for details on each property see [modules documentation](../../modules/application_insights/README.md)): + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - - `name` : (optional, string) a name of a single AI instance - - `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated) - - `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode - - `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details - - `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details - - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } + Following properties are available: - application_insights = { - metrics_retention_in_days = 365 - } - ``` + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } variable "bootstrap_storage" { @@ -485,12 +481,14 @@ variable "appgws" { - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF + default = {} + nullable = false type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 148e8ee8..fa70dd1c 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -7,7 +7,6 @@ tags = { "CreatedWith" = "Terraform" } - # --- VNET PART --- # vnets = { "transit" = { @@ -23,7 +22,7 @@ vnets = { 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_address_prefixes = ["0.0.0.0/0"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -128,10 +127,12 @@ vnets = { # --- LOAD BALANCING PART --- # load_balancers = { "public" = { - name = "public-lb" - 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 + name = "public-lb" + nsg_auto_rules_settings = { + 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 + } frontend_ips = { "app1" = { name = "app1" @@ -214,25 +215,33 @@ appgws = { } } - - # --- VMSERIES PART --- # -application_insights = {} - -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" -vmss = { - "common" = { - name = "common-vmss" - vnet_key = "transit" - zones = ["1", "2", "3"] - bootstrap_options = "type=dhcp-client" +ngfw_metrics = { + name = "ngwf-log-analytics-wrksp" +} +scale_sets = { + common = { + name = "common-vmss" + image = { + version = "10.2.4" + } + authentication = { + disable_password_authentication = false + } + virtual_machine_scale_set = { + vnet_key = "transit" + bootstrap_options = "type=dhcp-client" + zones = ["1", "2", "3"] + } + autoscaling_configuration = { + default_count = 1 + } interfaces = [ { - name = "management" - subnet_key = "management" - create_pip = true # see disclaimer on README for details + name = "management" + subnet_key = "management" + create_public_ip = true }, { name = "private" @@ -244,30 +253,40 @@ vmss = { subnet_key = "public" load_balancer_key = "public" application_gateway_key = "public" - create_pip = true + create_public_ip = true } ] - - autoscale_config = { - count_default = 2 - count_minimum = 1 - count_maximum = 3 - } - autoscale_metrics = { - "DataPlaneCPUUtilizationPct" = { - scaleout_threshold = 80 - scalein_threshold = 20 - } - } - scaleout_config = { - statistic = "Average" - time_aggregation = "Average" - window_minutes = 10 - cooldown_minutes = 30 - } - scalein_config = { - window_minutes = 10 - cooldown_minutes = 300 - } + autoscaling_profiles = [ + { + name = "default_profile" + default_count = 0 + }, + { + name = "weekday_profile" + default_count = 2 + minimum_count = 1 + maximum_count = 3 + recurrence = { + days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + start_time = "07:30" + end_time = "17:00" + } + scale_rules = [ + { + name = "DataPlaneCPUUtilizationPct" + scale_out_config = { + threshold = 70 + grain_window_minutes = 5 + aggregation_window_minutes = 30 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 40 + cooldown_window_minutes = 120 + } + }, + ] + }, + ] } } diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index df4d9a3f..bea154c9 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -1,6 +1,9 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.scale_sets : v.authentication.password == null + if !v.authentication.disable_password_authentication + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,8 +14,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) - disable_password_authentication = local.vmseries_password == null ? true : false + authentication = { + for k, v in var.scale_sets : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = try(coalesce(v.authentication.password, random_password.this[0].result), null) + } + ) + } } # Create or source the Resource Group. @@ -77,8 +88,6 @@ module "natgw" { depends_on = [module.vnet] } - - # create load balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -123,21 +132,27 @@ module "load_balancer" { depends_on = [module.vnet] } +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" -# Create the scale sets and related resources. -module "ai" { - source = "../../modules/application_insights" + count = var.ngfw_metrics != null ? 1 : 0 - for_each = { for k, v in var.vmss : k => "${v.name}-ai" if can(v.autoscale_metrics) } + create_workspace = var.ngfw_metrics.create_workspace - name = "${var.name_prefix}${each.value}" - resource_group_name = local.resource_group.name + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { + for k, v in var.scale_sets : + k => { name = "${var.name_prefix}${v.name}-ai" } + if length(v.autoscaling_profiles) > 0 + } tags = var.tags } @@ -178,71 +193,32 @@ module "appgw" { module "vmss" { source = "../../modules/vmss" - for_each = var.vmss + for_each = var.scale_sets name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location - username = var.vmseries_username - password = local.vmseries_password - disable_password_authentication = local.disable_password_authentication - img_sku = var.vmseries_sku - img_version = try(each.value.version, var.vmseries_version) - vm_size = try(each.value.vm_size, var.vmseries_vm_size) - zone_balance = var.enable_zones - zones = var.enable_zones ? try(each.value.zones, null) : [] - - encryption_at_host_enabled = try(each.value.encryption_at_host_enabled, null) - overprovision = try(each.value.overprovision, null) - platform_fault_domain_count = try(each.value.platform_fault_domain_count, null) - proximity_placement_group_id = try(each.value.proximity_placement_group_id, null) - scale_in_policy = try(each.value.scale_in_policy, null) - scale_in_force_deletion = try(each.value.scale_in_force_deletion, null) - single_placement_group = try(each.value.single_placement_group, null) - storage_account_type = try(each.value.storage_account_type, null) - disk_encryption_set_id = try(each.value.disk_encryption_set_id, null) - use_custom_image = try(each.value.use_custom_image, false) - custom_image_id = try(each.value.use_custom_image, false) ? each.value.custom_image_id : null - - accelerated_networking = try(each.value.accelerated_networking, null) + authentication = local.authentication[each.key] + virtual_machine_scale_set = each.value.virtual_machine_scale_set + image = each.value.image + interfaces = [ for v in each.value.interfaces : { name = v.name - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_pip = try(v.create_pip, false) - pip_domain_name_label = try(v.pip_domain_name_label, null) + subnet_id = module.vnet[each.value.virtual_machine_scale_set.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + pip_domain_name_label = v.pip_domain_name_label lb_backend_pool_ids = try([module.load_balancer[v.load_balancer_key].backend_pool_id], []) appgw_backend_pool_ids = try([module.appgw[v.application_gateway_key].backend_pool_id], []) } ] - bootstrap_options = each.value.bootstrap_options - - application_insights_id = can(each.value.autoscale_metrics) ? module.ai[each.key].application_insights_id : null - - autoscale_count_default = try(each.value.autoscale_config.count_default, null) - autoscale_count_minimum = try(each.value.autoscale_config.count_minimum, null) - autoscale_count_maximum = try(each.value.autoscale_config.count_maximum, null) - autoscale_notification_emails = try(each.value.autoscale_config.notification_emails, null) - - autoscale_metrics = try(each.value.autoscale_metrics, {}) - - scaleout_statistic = try(each.value.scaleout_config.statistic, null) - scaleout_time_aggregation = try(each.value.scaleout_config.time_aggregation, null) - scaleout_window_minutes = try(each.value.scaleout_config.window_minutes, null) - scaleout_cooldown_minutes = try(each.value.scaleout_config.cooldown_minutes, null) - - scalein_statistic = try(each.value.scalein_config.statistic, null) - scalein_time_aggregation = try(each.value.scalein_config.time_aggregation, null) - scalein_window_minutes = try(each.value.scalein_config.window_minutes, null) - scalein_cooldown_minutes = try(each.value.scalein_config.cooldown_minutes, null) + autoscaling_configuration = merge( + each.value.autoscaling_configuration, + { application_insights_id = try(module.ngfw_metrics[0].application_insights_ids[each.key], null) } + ) + autoscaling_profiles = each.value.autoscaling_profiles tags = var.tags - - depends_on = [ - module.ai, - module.vnet, - module.appgw - ] } diff --git a/examples/common_vmseries_and_autoscale/outputs.tf b/examples/common_vmseries_and_autoscale/outputs.tf index 688a40da..eb6c8a5e 100644 --- a/examples/common_vmseries_and_autoscale/outputs.tf +++ b/examples/common_vmseries_and_autoscale/outputs.tf @@ -1,17 +1,17 @@ -output "username" { - description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username +output "usernames" { + description = "Initial firewall administrative usernames for all deployed Scale Sets." + value = { for k, v in module.vmss : k => v.username } } -output "password" { - description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password +output "passwords" { + description = "Initial firewall administrative passwords for all deployed Scale Sets." + value = { for k, v in module.vmss : k => v.password } sensitive = true } output "metrics_instrumentation_keys" { description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." - value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) sensitive = true } diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 4649dd1e..b95ec4fe 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -40,12 +40,6 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - ### VNET @@ -272,153 +266,203 @@ variable "load_balancers" { })) } - -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: - - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. + A map controlling metrics-relates resources. - Names for all AI instances are prefixed with `var.name_prefix`. + When set to explicit `null` (default) it will disable any metrics resources in this deployment. - Properties supported (for details on each property see [modules documentation](../../modules/application_insights/README.md)): + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - - `name` : (optional, string) a name of a single AI instance - - `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated) - - `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode - - `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details - - `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } + Following properties are available: - application_insights = { - metrics_retention_in_days = 365 - } - ``` + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } +### VMSERIES +variable "scale_sets" { + description = <<-EOF + A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. -### GENERIC VMSERIES -variable "vmseries_version" { - description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable." - type = string -} + For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. -variable "vmseries_vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable." - type = string -} + The basic Scale Set configuration properties are as follows: -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} + - `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` + - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: -variable "vmss" { - description = <<-EOF - A map defining all Virtual Machine Scale Sets. + - do not specify anything else - a random password will be generated for you + - specify at least one of `password` or `ssh_keys` properties. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md) + For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). - Following properties are available: - - `name` : (string|required) name of the Virtual Machine Scale Set. - - `vm_size` : size of the VMSeries virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`. - - `version` : PanOS version, when specified overrides `var.vmseries_version`. - - `vnet_key` : (string|required) a key of a VNET defined in the `var.vnets` map. - - `bootstrap_options` : (string|`''`) bootstrap options passed to every VM instance upon creation. - - `zones` : (list(string)|`[]`) a list of Availability Zones to use for Zone redundancy - - `encryption_at_host_enabled` : (bool|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted - - `overprovision` : (bool|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept - - `platform_fault_domain_count` : (number|`null` - Azure defaults) number of fault domains to use - - `proximity_placement_group_id` : (string|`null`) ID of a proximity placement group the VMSS should be placed in - - `scale_in_policy` : (string|`null` - Azure defaults) policy of removing VMs when scaling in - - `scale_in_force_deletion` : (bool|`null` - module default) forces (`true`) deletion of VMs during scale in - - `single_placement_group` : (bool|`null` - Azure defaults) limit the Scale Set to one Placement Group - - `storage_account_type` : (string|`null` - module defaults) type of managed disk that will be used on all VMs - - `disk_encryption_set_id` : (string|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk - - `accelerated_networking` : (bool|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces - - `use_custom_image` : (bool|`false`) - - `custom_image_id` : (string|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series - - `application_insights_id` : (string|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling - - `interfaces` : (list(string)|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name` : (string|required) string that will form the NIC name - - `subnet_key` : (string|required) a key of a subnet as defined in `var.vnets` - - `create_pip` : (bool|`false`) flag to create Public IP for an interface, defaults to `false` - - `load_balancer_key` : (string|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable - - `application_gateway_key` : (string|`null`) key of an Application Gateway defined in the `var.appgws` - - `pip_domain_name_label` : (string|`null`) prefix which should be used for the Domain Name Label for each VM instance - - `autoscale_config` : (map|`{}`) map containing basic autoscale configuration - - `count_default` : (number|`null` - module defaults) default number or instances when autoscalling is not available - - `count_minimum` : (number|`null` - module defaults) minimum number of instances to reach when scaling in - - `count_maximum` : (number|`null` - module defaults) maximum number of instances when scaling out - - `notification_emails` : (list(string)|`null` - module defaults) a list of e-mail addresses to notify about scaling events - - `autoscale_metrics` : (map|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details - - `scaleout_config` : (map|`{}`) scale out configuration, for details see module documentation - - `statistic` : (string|`null` - module defaults) aggregation method for statistics coming from different VMs - - `time_aggregation` : (string|`null` - module defaults) aggregation method applied to statistics in time window - - `window_minutes` : (string|`null` - module defaults) time windows used to analyze statistics - - `cooldown_minutes` : (string|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again - - `scalein_config` : (map|`{}`) scale in configuration, same properties supported as for `scaleout_config` - - Example, no auto scaling: + - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. - ``` - { - "vmss" = { - name = "ngfw-vmss" - vnet_key = "transit" - bootstrap_options = "type=dhcp-client" - - interfaces = [ - { - name = "management" - subnet_key = "management" - }, - { - name = "private" - subnet_key = "private" - }, - { - name = "public" - subnet_key = "public" - load_balancer_key = "public" - application_gateway_key = "public" - } - ] - } - ``` + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + + - `version` - (`string`) describes the PanOS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PanOS image + + For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). + + - `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set + configuration options. + + Below we present only the most important ones, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported + - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from + this Scale Set will be created + - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series instance + + - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics thresholds, etc) + + Below we present only the most important properties, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in the + scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the + metrics to the thresholds + + - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. Following properties are available: + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets` + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be + added to the Load Balancee's backend pool + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance + + - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + configuration please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + vnet_key = string + bootstrap_options = optional(string) + size = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) } @@ -456,12 +500,14 @@ variable "appgws" { - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF + default = {} + nullable = false type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/common_vmseries_and_autoscale/versions.tf b/examples/common_vmseries_and_autoscale/versions.tf index 1f99597c..c49189a0 100644 --- a/examples/common_vmseries_and_autoscale/versions.tf +++ b/examples/common_vmseries_and_autoscale/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + # required_version = ">= 1.2, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img b/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img new file mode 100644 index 00000000..6b584e8e --- /dev/null +++ b/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img @@ -0,0 +1 @@ +content \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 194932a8..74796a10 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -23,7 +23,7 @@ vnets = { 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_address_prefixes = ["1.2.3.4"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -210,7 +210,7 @@ appgws = { bootstrap_storage = { bootstrap = { - name = "xmplbootstrapdedicated" + name = "fosixbootstrap" public_snet_key = "public" private_snet_key = "private" storage_acl = true @@ -221,10 +221,14 @@ bootstrap_storage = { subnet_key = "management" } } - storage_allow_inbound_public_ips = ["1.2.3.4"] # TODO: whitelist public IP addresses subnets (minimum /30 CIDR) that will be used to apply the terraform code from + storage_allow_inbound_public_ips = ["134.238.135.14", "134.238.135.140"] } } +ngfw_metrics = { + name = "metrics" +} + vmseries_version = "10.2.3" vmseries_vm_size = "Standard_DS3_v2" vmseries = { diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index e4b64dc7..2100f7e2 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -63,6 +63,7 @@ module "vnet" { tags = var.tags } + module "natgw" { source = "../../modules/natgw" @@ -130,24 +131,25 @@ module "load_balancer" { + # create the actual VMSeries VMs and resources -module "ai" { - source = "../../modules/application_insights" +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + count = var.ngfw_metrics != null ? 1 : 0 - name = "${var.name_prefix}${each.key}" - resource_group_name = local.resource_group.name + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } tags = var.tags } @@ -175,7 +177,7 @@ resource "local_file" "bootstrap_xml" { 1 ) - ai_instr_key = try(module.ai[try(var.application_insights.name, "${each.value.name}-ai")].metrics_instrumentation_key, null) + ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) ai_update_interval = try( each.value.bootstrap_storage.ai_update_interval, @@ -196,7 +198,7 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + module.ngfw_metrics, module.vnet ] } diff --git a/examples/dedicated_vmseries/outputs.tf b/examples/dedicated_vmseries/outputs.tf index d652953d..10533a56 100644 --- a/examples/dedicated_vmseries/outputs.tf +++ b/examples/dedicated_vmseries/outputs.tf @@ -19,7 +19,7 @@ output "natgw_public_ips" { output "metrics_instrumentation_keys" { description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." - value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) sensitive = true } diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index b6028de7..afeeb471 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -318,42 +318,38 @@ variable "availability_sets" { type = any } -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: + A map controlling metrics-relates resources. - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. + When set to explicit `null` (default) it will disable any metrics resources in this deployment. - Names for all AI instances are prefixed with `var.name_prefix`. + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - Properties supported (for details on each property see [modules documentation](../../modules/application_insights/README.md)): + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - - `name` : (optional, string) a name of a single AI instance - - `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated) - - `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode - - `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details - - `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details - - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } + Following properties are available: - application_insights = { - metrics_retention_in_days = 365 - } - ``` + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } variable "bootstrap_storage" { @@ -367,8 +363,7 @@ variable "bootstrap_storage" { - `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist. - `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`. - `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly. - - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tried to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files are successfully uploaded to the Storage Account. - + - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account. The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence: - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. @@ -486,12 +481,14 @@ variable "appgws" { - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF + default = {} + nullable = false type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 1f0c2986..abac7db7 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -6,7 +6,6 @@ tags = { "CreatedBy" = "Palo Alto Networks" "CreatedWith" = "Terraform" } -enable_zones = false # --- VNET PART --- # vnets = { @@ -23,7 +22,7 @@ vnets = { 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_address_prefixes = ["0.0.0.0/0"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -142,11 +141,13 @@ natgws = { # --- LOAD BALANCING PART --- # load_balancers = { "public" = { - name = "public-lb" - 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 + name = "public-lb" + zones = null + nsg_auto_rules_settings = { + 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 + } frontend_ips = { "app1" = { name = "app1" @@ -183,66 +184,28 @@ load_balancers = { } } +# --- VMSERIES PART --- # +ngfw_metrics = { + name = "ngwf-log-analytics-wrksp" +} - -# --- APPLICATION GATEWAYs --- # -appgws = { - "public" = { - name = "appgw" - public_ip = { - name = "pip" +scale_sets = { + inbound = { + name = "inbound-vmss" + image = { + version = "10.2.4" } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 - } - listeners = { - minimum = { - name = "minimum-listener" - port = 80 - } + authentication = { + disable_password_authentication = false } - rewrites = { - minimum = { - name = "minimum-set" - rules = { - "xff-strip-port" = { - name = "minimum-xff-strip-port" - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } + virtual_machine_scale_set = { + vnet_key = "transit" + bootstrap_options = "type=dhcp-client" + zones = null } - rules = { - minimum = { - name = "minimum-rule" - priority = 1 - backend = "minimum" - listener = "minimum" - rewrite = "minimum" - } + autoscaling_configuration = { + default_count = 2 } - } -} - - - -# --- VMSERIES PART --- # -application_insights = {} - -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" -vmss = { - "inbound" = { - name = "inbound-vmss" - vnet_key = "transit" - bootstrap_options = "type=dhcp-client" - interfaces = [ { name = "management" @@ -253,40 +216,28 @@ vmss = { subnet_key = "private" }, { - name = "public" - subnet_key = "public" - load_balancer_key = "public" - application_gateway_key = "public" + name = "public" + subnet_key = "public" + load_balancer_key = "public" } ] - - autoscale_config = { - count_default = 2 - count_minimum = 1 - count_maximum = 3 + } + obew = { + name = "obew-vmss" + image = { + version = "10.2.4" } - autoscale_metrics = { - "DataPlaneCPUUtilizationPct" = { - scaleout_threshold = 80 - scalein_threshold = 20 - } + authentication = { + disable_password_authentication = false } - scaleout_config = { - statistic = "Average" - time_aggregation = "Average" - window_minutes = 10 - cooldown_minutes = 30 + virtual_machine_scale_set = { + vnet_key = "transit" + bootstrap_options = "type=dhcp-client" + zones = null } - scalein_config = { - window_minutes = 10 - cooldown_minutes = 300 + autoscaling_configuration = { + default_count = 2 } - } - "obew" = { - name = "obew-vmss" - vnet_key = "transit" - bootstrap_options = "type=dhcp-client" - interfaces = [ { name = "management" @@ -302,27 +253,5 @@ vmss = { subnet_key = "public" } ] - - autoscale_config = { - count_default = 2 - count_minimum = 1 - count_maximum = 3 - } - autoscale_metrics = { - "DataPlaneCPUUtilizationPct" = { - scaleout_threshold = 70 - scalein_threshold = 20 - } - } - scaleout_config = { - statistic = "Average" - time_aggregation = "Average" - window_minutes = 10 - cooldown_minutes = 30 - } - scalein_config = { - window_minutes = 10 - cooldown_minutes = 300 - } } } diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index df4d9a3f..bea154c9 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -1,6 +1,9 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.scale_sets : v.authentication.password == null + if !v.authentication.disable_password_authentication + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,8 +14,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) - disable_password_authentication = local.vmseries_password == null ? true : false + authentication = { + for k, v in var.scale_sets : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = try(coalesce(v.authentication.password, random_password.this[0].result), null) + } + ) + } } # Create or source the Resource Group. @@ -77,8 +88,6 @@ module "natgw" { depends_on = [module.vnet] } - - # create load balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -123,21 +132,27 @@ module "load_balancer" { depends_on = [module.vnet] } +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" -# Create the scale sets and related resources. -module "ai" { - source = "../../modules/application_insights" + count = var.ngfw_metrics != null ? 1 : 0 - for_each = { for k, v in var.vmss : k => "${v.name}-ai" if can(v.autoscale_metrics) } + create_workspace = var.ngfw_metrics.create_workspace - name = "${var.name_prefix}${each.value}" - resource_group_name = local.resource_group.name + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { + for k, v in var.scale_sets : + k => { name = "${var.name_prefix}${v.name}-ai" } + if length(v.autoscaling_profiles) > 0 + } tags = var.tags } @@ -178,71 +193,32 @@ module "appgw" { module "vmss" { source = "../../modules/vmss" - for_each = var.vmss + for_each = var.scale_sets name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location - username = var.vmseries_username - password = local.vmseries_password - disable_password_authentication = local.disable_password_authentication - img_sku = var.vmseries_sku - img_version = try(each.value.version, var.vmseries_version) - vm_size = try(each.value.vm_size, var.vmseries_vm_size) - zone_balance = var.enable_zones - zones = var.enable_zones ? try(each.value.zones, null) : [] - - encryption_at_host_enabled = try(each.value.encryption_at_host_enabled, null) - overprovision = try(each.value.overprovision, null) - platform_fault_domain_count = try(each.value.platform_fault_domain_count, null) - proximity_placement_group_id = try(each.value.proximity_placement_group_id, null) - scale_in_policy = try(each.value.scale_in_policy, null) - scale_in_force_deletion = try(each.value.scale_in_force_deletion, null) - single_placement_group = try(each.value.single_placement_group, null) - storage_account_type = try(each.value.storage_account_type, null) - disk_encryption_set_id = try(each.value.disk_encryption_set_id, null) - use_custom_image = try(each.value.use_custom_image, false) - custom_image_id = try(each.value.use_custom_image, false) ? each.value.custom_image_id : null - - accelerated_networking = try(each.value.accelerated_networking, null) + authentication = local.authentication[each.key] + virtual_machine_scale_set = each.value.virtual_machine_scale_set + image = each.value.image + interfaces = [ for v in each.value.interfaces : { name = v.name - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_pip = try(v.create_pip, false) - pip_domain_name_label = try(v.pip_domain_name_label, null) + subnet_id = module.vnet[each.value.virtual_machine_scale_set.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + pip_domain_name_label = v.pip_domain_name_label lb_backend_pool_ids = try([module.load_balancer[v.load_balancer_key].backend_pool_id], []) appgw_backend_pool_ids = try([module.appgw[v.application_gateway_key].backend_pool_id], []) } ] - bootstrap_options = each.value.bootstrap_options - - application_insights_id = can(each.value.autoscale_metrics) ? module.ai[each.key].application_insights_id : null - - autoscale_count_default = try(each.value.autoscale_config.count_default, null) - autoscale_count_minimum = try(each.value.autoscale_config.count_minimum, null) - autoscale_count_maximum = try(each.value.autoscale_config.count_maximum, null) - autoscale_notification_emails = try(each.value.autoscale_config.notification_emails, null) - - autoscale_metrics = try(each.value.autoscale_metrics, {}) - - scaleout_statistic = try(each.value.scaleout_config.statistic, null) - scaleout_time_aggregation = try(each.value.scaleout_config.time_aggregation, null) - scaleout_window_minutes = try(each.value.scaleout_config.window_minutes, null) - scaleout_cooldown_minutes = try(each.value.scaleout_config.cooldown_minutes, null) - - scalein_statistic = try(each.value.scalein_config.statistic, null) - scalein_time_aggregation = try(each.value.scalein_config.time_aggregation, null) - scalein_window_minutes = try(each.value.scalein_config.window_minutes, null) - scalein_cooldown_minutes = try(each.value.scalein_config.cooldown_minutes, null) + autoscaling_configuration = merge( + each.value.autoscaling_configuration, + { application_insights_id = try(module.ngfw_metrics[0].application_insights_ids[each.key], null) } + ) + autoscaling_profiles = each.value.autoscaling_profiles tags = var.tags - - depends_on = [ - module.ai, - module.vnet, - module.appgw - ] } diff --git a/examples/dedicated_vmseries_and_autoscale/outputs.tf b/examples/dedicated_vmseries_and_autoscale/outputs.tf index 688a40da..eb6c8a5e 100644 --- a/examples/dedicated_vmseries_and_autoscale/outputs.tf +++ b/examples/dedicated_vmseries_and_autoscale/outputs.tf @@ -1,17 +1,17 @@ -output "username" { - description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username +output "usernames" { + description = "Initial firewall administrative usernames for all deployed Scale Sets." + value = { for k, v in module.vmss : k => v.username } } -output "password" { - description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password +output "passwords" { + description = "Initial firewall administrative passwords for all deployed Scale Sets." + value = { for k, v in module.vmss : k => v.password } sensitive = true } output "metrics_instrumentation_keys" { description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." - value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) sensitive = true } diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 4649dd1e..b95ec4fe 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -40,12 +40,6 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - ### VNET @@ -272,153 +266,203 @@ variable "load_balancers" { })) } - -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: - - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. + A map controlling metrics-relates resources. - Names for all AI instances are prefixed with `var.name_prefix`. + When set to explicit `null` (default) it will disable any metrics resources in this deployment. - Properties supported (for details on each property see [modules documentation](../../modules/application_insights/README.md)): + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - - `name` : (optional, string) a name of a single AI instance - - `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated) - - `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode - - `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details - - `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } + Following properties are available: - application_insights = { - metrics_retention_in_days = 365 - } - ``` + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } +### VMSERIES +variable "scale_sets" { + description = <<-EOF + A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. -### GENERIC VMSERIES -variable "vmseries_version" { - description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable." - type = string -} + For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. -variable "vmseries_vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable." - type = string -} + The basic Scale Set configuration properties are as follows: -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} + - `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` + - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: -variable "vmss" { - description = <<-EOF - A map defining all Virtual Machine Scale Sets. + - do not specify anything else - a random password will be generated for you + - specify at least one of `password` or `ssh_keys` properties. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md) + For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). - Following properties are available: - - `name` : (string|required) name of the Virtual Machine Scale Set. - - `vm_size` : size of the VMSeries virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`. - - `version` : PanOS version, when specified overrides `var.vmseries_version`. - - `vnet_key` : (string|required) a key of a VNET defined in the `var.vnets` map. - - `bootstrap_options` : (string|`''`) bootstrap options passed to every VM instance upon creation. - - `zones` : (list(string)|`[]`) a list of Availability Zones to use for Zone redundancy - - `encryption_at_host_enabled` : (bool|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted - - `overprovision` : (bool|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept - - `platform_fault_domain_count` : (number|`null` - Azure defaults) number of fault domains to use - - `proximity_placement_group_id` : (string|`null`) ID of a proximity placement group the VMSS should be placed in - - `scale_in_policy` : (string|`null` - Azure defaults) policy of removing VMs when scaling in - - `scale_in_force_deletion` : (bool|`null` - module default) forces (`true`) deletion of VMs during scale in - - `single_placement_group` : (bool|`null` - Azure defaults) limit the Scale Set to one Placement Group - - `storage_account_type` : (string|`null` - module defaults) type of managed disk that will be used on all VMs - - `disk_encryption_set_id` : (string|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk - - `accelerated_networking` : (bool|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces - - `use_custom_image` : (bool|`false`) - - `custom_image_id` : (string|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series - - `application_insights_id` : (string|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling - - `interfaces` : (list(string)|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name` : (string|required) string that will form the NIC name - - `subnet_key` : (string|required) a key of a subnet as defined in `var.vnets` - - `create_pip` : (bool|`false`) flag to create Public IP for an interface, defaults to `false` - - `load_balancer_key` : (string|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable - - `application_gateway_key` : (string|`null`) key of an Application Gateway defined in the `var.appgws` - - `pip_domain_name_label` : (string|`null`) prefix which should be used for the Domain Name Label for each VM instance - - `autoscale_config` : (map|`{}`) map containing basic autoscale configuration - - `count_default` : (number|`null` - module defaults) default number or instances when autoscalling is not available - - `count_minimum` : (number|`null` - module defaults) minimum number of instances to reach when scaling in - - `count_maximum` : (number|`null` - module defaults) maximum number of instances when scaling out - - `notification_emails` : (list(string)|`null` - module defaults) a list of e-mail addresses to notify about scaling events - - `autoscale_metrics` : (map|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details - - `scaleout_config` : (map|`{}`) scale out configuration, for details see module documentation - - `statistic` : (string|`null` - module defaults) aggregation method for statistics coming from different VMs - - `time_aggregation` : (string|`null` - module defaults) aggregation method applied to statistics in time window - - `window_minutes` : (string|`null` - module defaults) time windows used to analyze statistics - - `cooldown_minutes` : (string|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again - - `scalein_config` : (map|`{}`) scale in configuration, same properties supported as for `scaleout_config` - - Example, no auto scaling: + - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. - ``` - { - "vmss" = { - name = "ngfw-vmss" - vnet_key = "transit" - bootstrap_options = "type=dhcp-client" - - interfaces = [ - { - name = "management" - subnet_key = "management" - }, - { - name = "private" - subnet_key = "private" - }, - { - name = "public" - subnet_key = "public" - load_balancer_key = "public" - application_gateway_key = "public" - } - ] - } - ``` + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + + - `version` - (`string`) describes the PanOS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PanOS image + + For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). + + - `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set + configuration options. + + Below we present only the most important ones, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported + - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from + this Scale Set will be created + - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series instance + + - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics thresholds, etc) + + Below we present only the most important properties, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in the + scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the + metrics to the thresholds + + - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. Following properties are available: + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets` + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be + added to the Load Balancee's backend pool + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance + + - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + configuration please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + vnet_key = string + bootstrap_options = optional(string) + size = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) } @@ -456,12 +500,14 @@ variable "appgws" { - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property EOF + default = {} + nullable = false type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/dedicated_vmseries_and_autoscale/versions.tf b/examples/dedicated_vmseries_and_autoscale/versions.tf index 1f99597c..c49189a0 100644 --- a/examples/dedicated_vmseries_and_autoscale/versions.tf +++ b/examples/dedicated_vmseries_and_autoscale/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + # required_version = ">= 1.2, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index a5cd81d0..3c9c188a 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -74,23 +74,23 @@ module "gwlb" { } # VM-Series -module "ai" { - source = "../../modules/application_insights" +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + count = var.ngfw_metrics != null ? 1 : 0 - name = "${var.name_prefix}${each.key}" - resource_group_name = local.resource_group.name + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } tags = var.tags } @@ -106,8 +106,7 @@ resource "local_file" "bootstrap_xml" { module.vnet[var.vmseries_common.vnet_key].subnet_cidrs[each.value.interfaces[1].subnet_key], 1 ) - - ai_instr_key = try(module.ai[try(var.application_insights.name, "${var.name_prefix}${each.value.name}-ai")].metrics_instrumentation_key, null) + ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) ai_update_interval = try( each.value.ai_update_interval, @@ -118,7 +117,7 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + module.ngfw_metrics, module.vnet ] } diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index c4e77e08..e2b1a841 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -124,42 +124,36 @@ variable "gateway_load_balancers" { } # VM-Series -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: - - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. - - Names for all AI instances are prefixed with `var.name_prefix`. - - Properties supported (for details on each property see [module documentation](../modules/application_insights/README.md)): - - - `name` - (optional|string) Name of a single AI instance - - `workspace_mode` - (optional|bool) Use AI Workspace mode instead of the Classical (deprecated), defaults to `true`. - - `workspace_name` - (optional|string) Name of the Log Analytics Workspace created when AI is deployed in Workspace mode, defaults to AI name suffixed with `-wrkspc`. - - `workspace_sku` - (optional|string) SKU used by WAL, see module documentation for details, defaults to PerGB2018. - - `metrics_retention_in_days` - (optional|number) Defaults to current Azure default value, see module documentation for details. - - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } - - application_insights = { - metrics_retention_in_days = 365 - } - ``` + A map defining metrics related resources for Next Generation Firewall. + + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + + > [!Note] + > We do not explicitly define Application Insights instances. Each Virtual Machine will receive one automatically + > as long as this object is not `null`. + > The name of the Application Insights instance will be derived from the VM's name and suffixed with `-ai`. + + Following properties are available: + + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } variable "bootstrap_storages" { diff --git a/examples/lb/.header.md b/examples/lb/.header.md deleted file mode 100644 index 46e6e81d..00000000 --- a/examples/lb/.header.md +++ /dev/null @@ -1,3 +0,0 @@ -# LB - -Sample LB documentation. diff --git a/examples/lb/README.md b/examples/lb/README.md deleted file mode 100644 index 5ad6de64..00000000 --- a/examples/lb/README.md +++ /dev/null @@ -1,290 +0,0 @@ -<!-- BEGIN_TF_DOCS --> -# LB - -Sample LB documentation. - -## 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 Balancers. - - - - -## Module's Nameplate - - -Requirements needed by this module: - -- `terraform`, version: >= 1.3, < 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 - -<sup>[back to list](#modules-required-inputs)</sup> - - - -#### resource_group_name - -Name of the Resource Group. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### 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) - })), {}) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - - - - -### Optional Inputs - - -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -#### 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: `` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### 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` - -<sup>[back to list](#modules-optional-inputs)</sup> - - - -#### load_balancers - -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` - - -Type: - -```hcl -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) - })), {}) - })), {}) - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/lb/brownfield/main.tf b/examples/lb/brownfield/main.tf deleted file mode 100644 index 55c22b8a..00000000 --- a/examples/lb/brownfield/main.tf +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 555a2bde..00000000 --- a/examples/lb/example.tfvars +++ /dev/null @@ -1,123 +0,0 @@ -# --- 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 = ["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" - } - } - frontend_ips = { - "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" = { - 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" - zones = ["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" - } - } - } - } - } -} \ No newline at end of file diff --git a/examples/lb/main.tf b/examples/lb/main.tf deleted file mode 100644 index 0bc194fa..00000000 --- a/examples/lb/main.tf +++ /dev/null @@ -1,88 +0,0 @@ -# Create or source the Resource Group. -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - name = var.resource_group_name -} - -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - - network_security_groups = { - for k, v in each.value.network_security_groups : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { - for k, v in each.value.route_tables : - k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - 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 - - 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 => 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] -} diff --git a/examples/lb/outputs.tf b/examples/lb/outputs.tf deleted file mode 100644 index e1ec9160..00000000 --- a/examples/lb/outputs.tf +++ /dev/null @@ -1,47 +0,0 @@ -# output "fronts" { -# value = local.fronts -# } - - - - - -# output "username" { -# description = "Initial administrative username to use for VM-Series." -# value = var.vmseries_username -# } - -# output "password" { -# description = "Initial administrative password to use for VM-Series." -# value = local.vmseries_password -# sensitive = true -# } - -# output "natgw_public_ips" { -# description = "Nat Gateways Public IP resources." -# value = length(var.natgws) > 0 ? { for k, v in module.natgw : k => { -# pip = v.natgw_pip -# pip_prefix = v.natgw_pip_prefix -# } } : null -# } - -# output "metrics_instrumentation_keys" { -# description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." -# value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null -# sensitive = true -# } - -# output "lb_frontend_ips" { -# description = "IP Addresses of the load balancers." -# value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null -# } - -# output "vmseries_mgmt_ips" { -# description = "IP addresses for the VMSeries management interface." -# value = { for k, v in module.vmseries : k => v.mgmt_ip_address } -# } - -# output "bootstrap_storage_urls" { -# value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null -# sensitive = true -# } diff --git a/examples/lb/variables.tf b/examples/lb/variables.tf deleted file mode 100644 index 8a422944..00000000 --- a/examples/lb/variables.tf +++ /dev/null @@ -1,192 +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) - })), {}) - })) -} - -variable "load_balancers" { - description = <<-EOF - 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) - })), {}) - })), {}) - })) -} \ No newline at end of file diff --git a/examples/lb/versions.tf b/examples/lb/versions.tf deleted file mode 100644 index 50ff584a..00000000 --- a/examples/lb/versions.tf +++ /dev/null @@ -1,22 +0,0 @@ -terraform { - required_version = ">= 1.3, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - random = { - source = "hashicorp/random" - } - http = { - source = "hashicorp/http" - } - } -} - -provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 6bee91a4..16486232 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -26,7 +26,7 @@ vnets = { 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_address_prefixes = ["1.2.3.4"] source_port_range = "*" destination_address_prefix = "10.1.0.0/24" destination_port_ranges = ["22", "443"] diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 34dcce02..96c9021e 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -36,7 +36,7 @@ variable "create_resource_group" { } variable "resource_group_name" { - description = "Name of the Resource Group to ." + description = "Name of the Resource Group." type = string } diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 73d3bdb4..f2900c71 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -24,7 +24,7 @@ vnets = { 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_address_prefixes = ["1.2.3.4"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 958edc36..2100f7e2 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -17,7 +17,7 @@ locals { # Obtain Public IP address of code deployment machine data "http" "this" { - count = length(var.bootstrap_storage) > 0 && contains([for v in values(var.bootstrap_storage) : v.storage_acl], true) ? 1 : 0 + count = length(var.bootstrap_storage) > 0 && anytrue([for v in values(var.bootstrap_storage) : try(v.storage_acl, false)]) ? 1 : 0 url = "https://ifconfig.me/ip" } @@ -131,24 +131,25 @@ module "load_balancer" { + # create the actual VMSeries VMs and resources -module "ai" { - source = "../../modules/application_insights" +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + count = var.ngfw_metrics != null ? 1 : 0 - name = "${var.name_prefix}${each.key}" - resource_group_name = local.resource_group.name + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) location = var.location - workspace_mode = try(var.application_insights.workspace_mode, null) - workspace_name = try(var.application_insights.workspace_name, "${var.name_prefix}${each.key}-wrkspc") - workspace_sku = try(var.application_insights.workspace_sku, null) - metrics_retention_in_days = try(var.application_insights.metrics_retention_in_days, null) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } tags = var.tags } @@ -176,7 +177,7 @@ resource "local_file" "bootstrap_xml" { 1 ) - ai_instr_key = try(module.ai[try(var.application_insights.name, "${each.value.name}-ai")].metrics_instrumentation_key, null) + ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) ai_update_interval = try( each.value.bootstrap_storage.ai_update_interval, @@ -197,7 +198,7 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + module.ngfw_metrics, module.vnet ] } diff --git a/examples/standalone_vmseries/outputs.tf b/examples/standalone_vmseries/outputs.tf index d652953d..10533a56 100644 --- a/examples/standalone_vmseries/outputs.tf +++ b/examples/standalone_vmseries/outputs.tf @@ -19,7 +19,7 @@ output "natgw_public_ips" { output "metrics_instrumentation_keys" { description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." - value = var.application_insights != null ? { for k, v in module.ai : k => v.metrics_instrumentation_key } : null + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) sensitive = true } diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 2c49cee8..afeeb471 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -210,10 +210,13 @@ variable "load_balancers" { - `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: + `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` @@ -315,42 +318,38 @@ variable "availability_sets" { type = any } -variable "application_insights" { +variable "ngfw_metrics" { description = <<-EOF - A map defining Azure Application Insights. There are three ways to use this variable: - - * when the value is set to `null` (default) no AI is created - * when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key - * when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name. + A map controlling metrics-relates resources. - Names for all AI instances are prefixed with `var.name_prefix`. + When set to explicit `null` (default) it will disable any metrics resources in this deployment. - Properties supported (for details on each property see [modules documentation](../../modules/application_insights/README.md)): + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - - `name` : (optional, string) a name of a single AI instance - - `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated) - - `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode - - `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details - - `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year: - ``` - vmseries = { - 'vm-1' = { - .... - } - 'vm-2' = { - .... - } - } + Following properties are available: - application_insights = { - metrics_retention_in_days = 365 - } - ``` + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. EOF default = null - type = map(string) + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) } variable "bootstrap_storage" { @@ -364,7 +363,7 @@ variable "bootstrap_storage" { - `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist. - `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`. - `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly. - - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tried to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files are succuessfuly uploaded to the Storage Account. + - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account. The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence: - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. @@ -487,9 +486,9 @@ variable "appgws" { type = map(object({ name = string public_ip = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + resource_group_name = optional(string) + create = optional(bool, true) }) vnet_key = string subnet_key = string diff --git a/examples/test_infrastructure/example.tfvars b/examples/test_infrastructure/example.tfvars index de10de89..989299b7 100644 --- a/examples/test_infrastructure/example.tfvars +++ b/examples/test_infrastructure/example.tfvars @@ -14,8 +14,8 @@ vnets = { name = "spoke-east" address_space = ["10.100.0.0/25"] # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-common" # TODO: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET + # hub_resource_group_name = "example-transit-vnet-common" # FIXME: replace with the name of transit VNET's Resource Group Name + # hub_vnet_name = "example-transit" # FIXME: replace with the name of the transit VNET route_tables = { nva = { name = "east2NVA" @@ -24,7 +24,7 @@ vnets = { name = "2NVA-udr" address_prefix = "0.0.0.0/0" next_hop_type = "VirtualAppliance" - next_hop_ip_address = "10.0.0.30" # TODO: this by default matches the private IP of the private Load Balancer deployed in any of the examples; adjust if needed + next_hop_ip_address = "10.0.0.30" # FIXME: this by default matches the private IP of the private Load Balancer deployed in any of the examples; adjust if needed } } } @@ -33,7 +33,7 @@ vnets = { "vms" = { name = "vms" address_prefixes = ["10.100.0.0/26"] - route_table = "nva" + route_table_key = "nva" } "bastion" = { name = "AzureBastionSubnet" @@ -45,8 +45,8 @@ vnets = { name = "spoke-west" address_space = ["10.100.1.0/25"] # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-common" # TODO: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # TODO: replace with the name of the transit VNET + # hub_resource_group_name = "example-transit-vnet-common" # FIXME: replace with the name of transit VNET's Resource Group Name + # hub_vnet_name = "example-transit" # FIXME: replace with the name of the transit VNET route_tables = { nva = { name = "west2NVA" @@ -55,7 +55,7 @@ vnets = { name = "2NVA-udr" address_prefix = "0.0.0.0/0" next_hop_type = "VirtualAppliance" - next_hop_ip_address = "10.0.0.30" # TODO: replace with IP address of the private Load Balancer in the transit VNET + next_hop_ip_address = "10.0.0.30" # FIXME: replace with IP address of the private Load Balancer in the transit VNET } } } diff --git a/examples/vnet/brownfield/main.tf b/examples/vnet/brownfield/main.tf deleted file mode 100644 index cbca165a..00000000 --- a/examples/vnet/brownfield/main.tf +++ /dev/null @@ -1,37 +0,0 @@ -terraform { - required_version = ">= 1.3, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - } -} - -provider "azurerm" { - features { - } -} - -resource "azurerm_resource_group" "this" { - name = "fosix-vnet-brownfield" - location = "North Europe" -} - -resource "azurerm_virtual_network" "this" { - name = "fosix-brownfield-vnet" - location = azurerm_resource_group.this.location - resource_group_name = azurerm_resource_group.this.name - address_space = ["10.0.0.0/8"] -} - -resource "azurerm_subnet" "this" { - for_each = { - one-snet = "10.0.0.0/24" - two-snet = "10.0.1.0/24" - } - - name = each.key - resource_group_name = azurerm_resource_group.this.name - virtual_network_name = azurerm_virtual_network.this.name - address_prefixes = [each.value] -} \ No newline at end of file diff --git a/examples/vnet/example.tfvars b/examples/vnet/example.tfvars deleted file mode 100644 index 91882a5f..00000000 --- a/examples/vnet/example.tfvars +++ /dev/null @@ -1,108 +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 = { - brownfield = { - name = "fosix-brownfield-vnet" - resource_group_name = "fosix-vnet-brownfield" - create_virtual_network = false - create_subnets = false - subnets = { - "a" = { name = "one-snet" } - "b" = { name = "two-snet" } - } - } - empty = { - name = "empty" - address_space = ["10.0.0.0/29"] - } - non-empty = { - name = "non-empty" - address_space = ["8.0.0.0/5"] - 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 = "*" - source_port_ranges = ["33", "44-55"] - destination_address_prefix = "10.0.0.0/28" - destination_port_ranges = ["22", "443"] - } - } - } - "nsg2" = { - name = "nsg2" - 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 = "*" - source_port_ranges = ["33", "44-55"] - 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/24" - next_hop_type = "None" - } - "udrka" = { - name = "udrb" - address_prefix = "10.0.1.0/24" - next_hop_type = "None" - } - } - } - "rtb" = { - name = "b_udr" - routes = { - "udr" = { - name = "udr" - address_prefix = "0.0.0.0/0" - 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" - } - "some_other_subnet" = { - name = "some-other-subnet" - address_prefixes = ["10.0.1.0/25"] - network_security_group_key = "nsg" - route_table_key = "rt" - } - } - } -} diff --git a/examples/vnet/variables.tf b/examples/vnet/variables.tf deleted file mode 100644 index 066f2247..00000000 --- a/examples/vnet/variables.tf +++ /dev/null @@ -1,110 +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 `true`) 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`, optional, 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 - resource_group_name = optional(string) - create_virtual_network = optional(bool, true) - address_space = optional(list(string)) - network_security_groups = optional(map(object({ - name = 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 - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_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/appgw/README.md b/modules/appgw/README.md index 8db7ab58..e80bb504 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -806,7 +806,7 @@ Name | Type | Description [`name`](#name) | `string` | The name of the Application Gateway. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. [`location`](#location) | `string` | The name of the Azure region to deploy the resources in. -[`public_ip`](#public_ip) | `object` | Public IP address. +[`public_ip`](#public_ip) | `object` | A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. [`subnet_id`](#subnet_id) | `string` | An ID of a subnet that will host the Application Gateway. [`ssl_profiles`](#ssl_profiles) | `map` | A map of SSL profiles. [`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. @@ -848,7 +848,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.25 @@ -898,15 +898,23 @@ Type: string #### public_ip -Public IP address. +A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. + +Following properties are available: + +- `name` - (`string`, required) name of the created or source Public IP resource +- `create` - (`bool`, optional, defaults to `true`) controls if the public IP is created or sourced. +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the + existing Public IP resource + Type: ```hcl object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + create = optional(bool, true) + resource_group_name = optional(string) }) ``` @@ -1202,7 +1210,8 @@ Default value: `map[]` A list of zones the Application Gateway should be available in. -NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, +**Note!** \ +This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to a single zone or zone-redundant (so available in all zones in a region). Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index 268c68f6..a9bc9430 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -18,7 +18,7 @@ locals { data "azurerm_public_ip" "this" { count = var.public_ip.create ? 0 : 1 name = var.public_ip.name - resource_group_name = coalesce(var.public_ip.resource_group, var.resource_group_name) + resource_group_name = coalesce(var.public_ip.resource_group_name, var.resource_group_name) } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index 9b79ea10..f08e6a68 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -26,7 +26,8 @@ variable "zones" { description = <<-EOF A list of zones the Application Gateway should be available in. - NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, + **Note!** \ + This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to a single zone or zone-redundant (so available in all zones in a region). Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during @@ -44,16 +45,25 @@ variable "zones" { type = list(string) validation { condition = var.zones == null || length(setsubtract(var.zones, ["1", "2", "3"])) == 0 - error_message = "The `var.zones` can either bea non empty list of Availability Zones or explicit `null`." + error_message = "The `var.zones` can either be a non empty list of Availability Zones or explicit `null`." } } variable "public_ip" { - description = "Public IP address." + description = <<-EOF + A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. + + Following properties are available: + + - `name` - (`string`, required) name of the created or source Public IP resource + - `create` - (`bool`, optional, defaults to `true`) controls if the public IP is created or sourced. + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the + existing Public IP resource + EOF type = object({ - name = string - resource_group = optional(string) - create = optional(bool, true) + name = string + create = optional(bool, true) + resource_group_name = optional(string) }) } @@ -299,7 +309,6 @@ variable "ssl_profiles" { variable "frontend_ip_configuration_name" { description = "Frontend IP configuration name" default = "public_ipconfig" - nullable = false type = string } diff --git a/modules/appgw/versions.tf b/modules/appgw/versions.tf index 8dc5c1eb..2c796798 100644 --- a/modules/appgw/versions.tf +++ b/modules/appgw/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.3, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/modules/application_insights/README.md b/modules/application_insights/README.md deleted file mode 100644 index 35f4d13f..00000000 --- a/modules/application_insights/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Palo Alto Networks Application Insights Module for Azure - -A Terraform module for deploying a Application Insights in Azure cloud. - -Azure AI can be used to gather metric from Palo Alto's VMSeries firewall. This can be done for both a standalone firewall as for a Scale Set deployment. - -In both situations the instrumentation key for the Application Insights has to be provided in the firewall's configuration. For more information please refer to [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall). - -**NOTICE** - -* Azure support for classic Application Insights mode will end on Feb 29th 2024. It's already not available in some of the new regions. This module by default deploys Application Insights in Workspace mode. - -* The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split back to obtain a result for a single firewall. Thus for example if three firewalls use the same Instrumentation Key and report their respective session utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, the max of 90%, but it is *not possible* to know which of the firewalls reported the 90% utilization. - -* Since upgrade to provider 3.x, when destroying infrastructure a resource is being left behind: `microsoft.alertsmanagement/smartdetectoralertrules`. This resource is not present in the state nor code, it's being created by Azure automatically and therefore it prevents resource group deletion. A workaround is to set the following provider configuration: - - provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } - } - -## Usage - -The following snippet deploys Application Insights in Workspace mode, setting the retention to 1 year. - -```hcl -module "ai" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/application_insights" - - name = "vmseries-ai - metrics_retention_in_days = 365 - location = "West US" - resource_group_name = "vmseries-rg" -} -``` - -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_application_insights.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_insights) | resource | -| [azurerm_log_analytics_workspace.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name"></a> [name](#input\_name) | Name of the Application Insights instance. | `string` | n/a | yes | -| <a name="input_workspace_mode"></a> [workspace\_mode](#input\_workspace\_mode) | Application Insights mode. If `true` (default), the 'Workspace-based' mode is used. With `false`, the mode is set to legacy 'Classic'.<br><br>NOTICE. Azure support for classic Application Insights mode will end on Feb 29th 2024. It's already not available in some of the new regions. | `bool` | `true` | no | -| <a name="input_workspace_name"></a> [workspace\_name](#input\_workspace\_name) | The name of the Log Analytics workspace. Can be `null`, in which case a default name is auto-generated. | `string` | `null` | no | -| <a name="input_workspace_sku"></a> [workspace\_sku](#input\_workspace\_sku) | Azure Log Analytics Workspace mode SKU. For more information refer to [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/azure-monitor//usage-estimated-costs#moving-to-the-new-pricing-model). | `string` | `"PerGB2018"` | no | -| <a name="input_metrics_retention_in_days"></a> [metrics\_retention\_in\_days](#input\_metrics\_retention\_in\_days) | Specifies the retention period in days. Possible values are 0, 30, 60, 90, 120, 180, 270, 365, 550 or 730. Azure defaults is 90. | `number` | `null` | no | -| <a name="input_location"></a> [location](#input\_location) | A name of a region in which the resources will be creatied. | `string` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | A name of an existing Resource Group. | `string` | n/a | yes | -| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags assigned to all resources created by this module. | `map(string)` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_metrics_instrumentation_key"></a> [metrics\_instrumentation\_key](#output\_metrics\_instrumentation\_key) | The Instrumentation Key of the created instance of Azure Application Insights. <br><br>The instance is unused by default, but is ready to receive custom PAN-OS metrics from the firewalls. To use it, paste this Instrumentation Key into PAN-OS -> Device -> VM-Series -> Azure. | -| <a name="output_application_insights_id"></a> [application\_insights\_id](#output\_application\_insights\_id) | An Azure ID of the Application Insights resource created by this module. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> diff --git a/modules/application_insights/main.tf b/modules/application_insights/main.tf deleted file mode 100644 index b1619bb7..00000000 --- a/modules/application_insights/main.tf +++ /dev/null @@ -1,24 +0,0 @@ -resource "azurerm_log_analytics_workspace" "this" { - count = var.workspace_mode ? 1 : 0 - - name = try(var.workspace_name, "${var.name}-wrkspc") - location = var.location - - resource_group_name = var.resource_group_name - retention_in_days = var.metrics_retention_in_days - sku = var.workspace_sku - - tags = var.tags -} - -resource "azurerm_application_insights" "this" { - name = var.name - location = var.location - resource_group_name = var.resource_group_name # same RG, so no RBAC modification is needed - - workspace_id = var.workspace_mode ? azurerm_log_analytics_workspace.this[0].id : null - application_type = "other" - retention_in_days = var.metrics_retention_in_days - - tags = var.tags -} diff --git a/modules/application_insights/outputs.tf b/modules/application_insights/outputs.tf deleted file mode 100644 index 9f0a00e2..00000000 --- a/modules/application_insights/outputs.tf +++ /dev/null @@ -1,10 +0,0 @@ -output "metrics_instrumentation_key" { - description = "The Instrumentation Key of the created instance of Azure Application Insights." - value = azurerm_application_insights.this.instrumentation_key - sensitive = true -} - -output "application_insights_id" { - description = "An Azure ID of the Application Insights resource created by this module." - value = azurerm_application_insights.this.id -} \ No newline at end of file diff --git a/modules/application_insights/variables.tf b/modules/application_insights/variables.tf deleted file mode 100644 index 78087d83..00000000 --- a/modules/application_insights/variables.tf +++ /dev/null @@ -1,49 +0,0 @@ -variable "name" { - description = "Name of the Application Insights instance." - type = string -} - -variable "workspace_mode" { - description = <<-EOF - Application Insights mode. If `true` (default), the 'Workspace-based' mode is used. With `false`, the mode is set to legacy 'Classic'. - - NOTICE. Azure support for classic Application Insights mode will end on Feb 29th 2024. It's already not available in some of the new regions. - EOF - default = true - type = bool - nullable = false -} - -variable "workspace_name" { - description = "The name of the Log Analytics workspace. Can be `null`, in which case a default name is auto-generated." - default = null - type = string -} - -variable "workspace_sku" { - description = "Azure Log Analytics Workspace mode SKU. For more information refer to [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/azure-monitor//usage-estimated-costs#moving-to-the-new-pricing-model)." - default = "PerGB2018" - type = string -} - -variable "metrics_retention_in_days" { - description = "Specifies the retention period in days. Possible values are 0, 30, 60, 90, 120, 180, 270, 365, 550 or 730. Azure defaults is 90." - default = null - type = number -} - -variable "location" { - description = "A name of a region in which the resources will be creatied." - type = string -} - -variable "resource_group_name" { - description = "A name of an existing Resource Group." - type = string -} - -variable "tags" { - description = "A map of tags assigned to all resources created by this module." - default = {} - type = map(string) -} diff --git a/modules/bootstrap/versions.tf b/modules/bootstrap/versions.tf index 394c8034..1611a7bf 100644 --- a/modules/bootstrap/versions.tf +++ b/modules/bootstrap/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/modules/name_templater/versions.tf b/modules/name_templater/versions.tf index c195e513..5c3325b9 100644 --- a/modules/name_templater/versions.tf +++ b/modules/name_templater/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { random = { source = "hashicorp/random" diff --git a/modules/ngfw_metrics/.header.md b/modules/ngfw_metrics/.header.md new file mode 100644 index 00000000..35639b3e --- /dev/null +++ b/modules/ngfw_metrics/.header.md @@ -0,0 +1,55 @@ +# Palo Alto Networks Metrics Infrastructure Module for Azure + +A Terraform module deploying Azure Application Insights (Log Analytics Workspace mode). + +The main purpose of this module is to deploy Application Insights that can be used to monitor internal PanOS metrics. +It will work with both a standalone Next Generation Firewall and ones deployed inside a Virtual Machine Scale Set. +In both situations the instrumentation key for the Application Insights has to be provided in the firewall's configuration. +For more information please refer to [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall). + +**Note!** \ +This module supports only the workspace mode - Azure support for classic Application Insights mode will end on Feb 29th 2024. + +This module is designed to deploy (or source) a single Log Analytics Workspace and to create one or more Application Insights +instances connected to that workspace. + +**Important!** \ +The metrics gathered within a single Azure Application Insights instance cannot be split back to obtain a result for a single +firewall. Thus, for example, if three firewalls use the same Instrumentation Key and report their respective session +utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, the max of 90%, but it is +**not possible** to know which of the firewalls reported the 90% utilization. +Therefore each firewall (or a Scale Set) should send the metrics to a dedicated Application Insights instance. + +Since upgrade to provider 3.x, when destroying infrastructure a resource is being left behind: +`microsoft.alertsmanagement/smartdetectoralertrules`. This resource is not present in the state nor code, it's being created by +Azure automatically and therefore it prevents Resource Group deletion. +A workaround is to set the following provider configuration: + +```hcl +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} +``` + +## Usage + +The following snippet deploys Log Analytics Workspace and two Application Insights instances (using defaults where possible): + +```hcl +module "ngfw_metrics" { + source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/ngfw_metrics" + + name = "ngfw-law" + resource_group_name = "ngfw-rg" + location = "West US" + + application_insights = { + ai1 = { name = "fw1-ai" } + ai2 = { name = "fw2-ai" } + } +} +``` diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md new file mode 100644 index 00000000..a01477ea --- /dev/null +++ b/modules/ngfw_metrics/README.md @@ -0,0 +1,228 @@ +<!-- BEGIN_TF_DOCS --> +# Palo Alto Networks Metrics Infrastructure Module for Azure + +A Terraform module deploying Azure Application Insights (Log Analytics Workspace mode). + +The main purpose of this module is to deploy Application Insights that can be used to monitor internal PanOS metrics. +It will work with both a standalone Next Generation Firewall and ones deployed inside a Virtual Machine Scale Set. +In both situations the instrumentation key for the Application Insights has to be provided in the firewall's configuration. +For more information please refer to [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall). + +**Note!** \ +This module supports only the workspace mode - Azure support for classic Application Insights mode will end on Feb 29th 2024. + +This module is designed to deploy (or source) a single Log Analytics Workspace and to create one or more Application Insights +instances connected to that workspace. + +**Important!** \ +The metrics gathered within a single Azure Application Insights instance cannot be split back to obtain a result for a single +firewall. Thus, for example, if three firewalls use the same Instrumentation Key and report their respective session +utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, the max of 90%, but it is +**not possible** to know which of the firewalls reported the 90% utilization. +Therefore each firewall (or a Scale Set) should send the metrics to a dedicated Application Insights instance. + +Since upgrade to provider 3.x, when destroying infrastructure a resource is being left behind: +`microsoft.alertsmanagement/smartdetectoralertrules`. This resource is not present in the state nor code, it's being created by +Azure automatically and therefore it prevents Resource Group deletion. +A workaround is to set the following provider configuration: + +```hcl +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} +``` + +## Usage + +The following snippet deploys Log Analytics Workspace and two Application Insights instances (using defaults where possible): + +```hcl +module "ngfw_metrics" { + source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/ngfw_metrics" + + name = "ngfw-law" + resource_group_name = "ngfw-rg" + location = "West US" + + application_insights = { + ai1 = { name = "fw1-ai" } + ai2 = { name = "fw2-ai" } + } +} +``` + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Log Analytics Workspace. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`application_insights`](#application_insights) | `map` | A map defining Application Insights instances. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`create_workspace`](#create_workspace) | `bool` | Controls creation or sourcing of a Log Analytics Workspace. +[`log_analytics_workspace`](#log_analytics_workspace) | `object` | Configuration of the log analytics workspace. + + + +## Module's Outputs + +Name | Description +--- | --- +`metrics_instrumentation_keys` | The Instrumentation Key of the Application Insights instances. +`application_insights_ids` | An Azure ID of the Application Insights instances. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.3, < 2.0 +- `azurerm`, version: ~> 3.80 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.80 + + + + +Resources used in this module: + +- `application_insights` (managed) +- `log_analytics_workspace` (managed) +- `log_analytics_workspace` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Log Analytics Workspace. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + + +#### application_insights + +A map defining Application Insights instances. + +Following properties are available: + +- `name` - (`string`, required) the name of the Application Insights instance +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that will + host the Application Insights instance. + + This property can be handy in case one would like to use an existing Log Analytics Workspace, but for whatever reason the + Application Insights instances should be created in a separate Resource Group (due to limited access for example). + +- `metrics_retention_in_days` - (`number`, optional, defaults to `var.log_analytics_workspace.metrics_retention_in_days`) + Application Insights data retention in days, possible values are between 30 and 730. + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + metrics_retention_in_days = optional(number) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### create_workspace + +Controls creation or sourcing of a Log Analytics Workspace. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### log_analytics_workspace + +Configuration of the log analytics workspace. + +Following properties are available: + +- `sku` - (`string`, optional, defaults to Azure defaults) the SKU of the Log Analytics Workspace. + + As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: + `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. + +- `metrics_retention_in_days` - (`number`, optional, defaults to Azure defaults) workspace data retention in days, + possible values are between 30 and 730. + + +Type: + +```hcl +object({ + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/ngfw_metrics/main.tf b/modules/ngfw_metrics/main.tf new file mode 100644 index 00000000..a41d5191 --- /dev/null +++ b/modules/ngfw_metrics/main.tf @@ -0,0 +1,36 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace +resource "azurerm_log_analytics_workspace" "this" { + count = var.create_workspace ? 1 : 0 + + name = var.name + resource_group_name = var.resource_group_name + location = var.location + + retention_in_days = var.log_analytics_workspace.metrics_retention_in_days + sku = var.log_analytics_workspace.sku + + tags = var.tags +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/log_analytics_workspace +data "azurerm_log_analytics_workspace" "this" { + count = var.create_workspace ? 0 : 1 + + name = var.name + resource_group_name = var.resource_group_name +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_insights +resource "azurerm_application_insights" "this" { + for_each = var.application_insights + + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, var.resource_group_name) + location = var.location + + workspace_id = var.create_workspace ? azurerm_log_analytics_workspace.this[0].id : data.azurerm_log_analytics_workspace.this[0].id + application_type = "other" + retention_in_days = each.value.metrics_retention_in_days == null ? var.log_analytics_workspace.metrics_retention_in_days : each.value.metrics_retention_in_days + + tags = var.tags +} diff --git a/modules/application_insights/main_test.go b/modules/ngfw_metrics/main_test.go similarity index 86% rename from modules/application_insights/main_test.go rename to modules/ngfw_metrics/main_test.go index 5aa1ca8a..7061ab30 100644 --- a/modules/application_insights/main_test.go +++ b/modules/ngfw_metrics/main_test.go @@ -1,4 +1,4 @@ -package application_insights +package ngfw_metrics import ( "testing" diff --git a/modules/ngfw_metrics/outputs.tf b/modules/ngfw_metrics/outputs.tf new file mode 100644 index 00000000..fbc9768d --- /dev/null +++ b/modules/ngfw_metrics/outputs.tf @@ -0,0 +1,10 @@ +output "metrics_instrumentation_keys" { + description = "The Instrumentation Key of the Application Insights instances." + value = { for k, v in azurerm_application_insights.this : k => v.instrumentation_key } + sensitive = true +} + +output "application_insights_ids" { + description = "An Azure ID of the Application Insights instances." + value = { for k, v in azurerm_application_insights.this : k => v.id } +} diff --git a/modules/ngfw_metrics/variables.tf b/modules/ngfw_metrics/variables.tf new file mode 100644 index 00000000..ac1a4e20 --- /dev/null +++ b/modules/ngfw_metrics/variables.tf @@ -0,0 +1,98 @@ +variable "name" { + description = "The name of the Azure Log Analytics Workspace." + type = string +} + +variable "resource_group_name" { + description = "The name of the Resource Group to use." + type = string +} + +variable "location" { + description = "The name of the Azure region to deploy the resources in." + type = string +} + +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} + +variable "create_workspace" { + description = "Controls creation or sourcing of a Log Analytics Workspace." + default = true + nullable = false + type = bool +} + +variable "log_analytics_workspace" { + description = <<-EOF + Configuration of the log analytics workspace. + + Following properties are available: + + - `sku` - (`string`, optional, defaults to Azure defaults) the SKU of the Log Analytics Workspace. + + As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: + `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. + + - `metrics_retention_in_days` - (`number`, optional, defaults to Azure defaults) workspace data retention in days, + possible values are between 30 and 730. + EOF + default = {} + nullable = false + type = object({ + sku = optional(string) + metrics_retention_in_days = optional(number) + }) + validation { + condition = var.log_analytics_workspace.sku != null ? contains([ + "Free", + "PerNode", + "Premium", + "Standard", + "Standalone", + "Unlimited", + "CapacityReservation", + "PerGB2018"], + var.log_analytics_workspace.sku + ) : true + error_message = "The `var.log_analytics_workspace.sku` property has to have a value of either: `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation` or `PerGB2018`." + } + validation { + condition = var.log_analytics_workspace.metrics_retention_in_days != null ? var.log_analytics_workspace.metrics_retention_in_days >= 30 && var.log_analytics_workspace.metrics_retention_in_days <= 730 : true + error_message = "The `var.log_analytics_workspace.metrics_retention_in_days` property can take values between 30 and 730 (both inclusive)." + } +} + +variable "application_insights" { + description = <<-EOF + A map defining Application Insights instances. + + Following properties are available: + + - `name` - (`string`, required) the name of the Application Insights instance + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that will + host the Application Insights instance. + + This property can be handy in case one would like to use an existing Log Analytics Workspace, but for whatever reason the + Application Insights instances should be created in a separate Resource Group (due to limited access for example). + + - `metrics_retention_in_days` - (`number`, optional, defaults to `var.log_analytics_workspace.metrics_retention_in_days`) + Application Insights data retention in days, possible values are between 30 and 730. + EOF + type = map(object({ + name = string + resource_group_name = optional(string) + metrics_retention_in_days = optional(number) + })) + validation { + condition = alltrue([ + for _, v in var.application_insights : + v.metrics_retention_in_days >= 30 && v.metrics_retention_in_days <= 730 + if v.metrics_retention_in_days != null + ]) + error_message = "The `metrics_retention_in_days` property can take values between 30 and 730 (both inclusive)." + } +} diff --git a/modules/application_insights/versions.tf b/modules/ngfw_metrics/versions.tf similarity index 61% rename from modules/application_insights/versions.tf rename to modules/ngfw_metrics/versions.tf index 501042ff..9abec711 100644 --- a/modules/application_insights/versions.tf +++ b/modules/ngfw_metrics/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/panorama/versions.tf b/modules/panorama/versions.tf index 394c8034..1611a7bf 100644 --- a/modules/panorama/versions.tf +++ b/modules/panorama/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/modules/vmseries/versions.tf b/modules/vmseries/versions.tf index 501042ff..2c796798 100644 --- a/modules/vmseries/versions.tf +++ b/modules/vmseries/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/modules/vmss/.header.md b/modules/vmss/.header.md new file mode 100644 index 00000000..0304aaac --- /dev/null +++ b/modules/vmss/.header.md @@ -0,0 +1,99 @@ +# Palo Alto Networks VMSS Module for Azure + +A terraform module for deploying a Scale Set based on Next Generation Firewalls in Azure. + +**NOTE!** \ +Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running in a +Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. + +For this mode to actually work the `roll_instances_when_required` provider feature has to be also configured and set to `false`. +Unfortunately this cannot be set in the `vmss` module, it has to be specified in the **root** module. + +Therefore, when using this module please add the following `provider` block to your code: + +```hcl +provider "azurerm" { + features { + virtual_machine_scale_set { + roll_instances_when_required = false + } + } +} +``` + +## About rolling upgrades and auto healing + +Both, the rolling upgrade mode and auto healing target the 1<sup>st</sup> NIC on a Scale Set VM with a health probe to verify if +the VM is capable of handling traffic. Furthermore, for the health probe to work the 1<sup>st</sup> interface has to be added to +a Load Balancer. + +This provides some obstacles when deploying such setup with Next Generation Firewall based Scale Set: most importantly the health +probe would target the management interface which could lead to false-positives. A management service can respond to TCP/Http +probes, while the data plane remains unconfigured. An easy solution would to bo configure an interface swap, unfortunately this +is not available in the Azure VMSeries image yet. + +## Custom Metrics and Autoscaling + +Firewalls can publish custom metrics (for example `panSessionUtilization`) to Azure Application Insights to improve the +autoscaling. This is a suggested way of setting up scaling rules as these metrics are gathered only from the data plane. + +This however requires some additional steps: + +- deploy the [`ngfw_metrics`](../ngfw_metrics/README.md) module, this module outputs two properties: + - `application_insights_ids` - a map of IDs of the deployed Application Insights instances + - `metrics_instrumentation_keys` - a map of instrumentation keys for the deployed Application Insights instances +- configure this module with the ID of the desired Application Insights instance, use the + [`var.autoscaling_configuration.application_insights_id`](#autoscaling_configuration) property +- depending on the bootstrap method you use, configure the PanOS VMSeries plugins with the metrics instrumentation key + belonging to the Application Insights instance of your choice. + +The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain +back a result for solely a single firewall. Thus for example if three firewalls use the same Instrumentation Key and report +their respective session utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, +the max of 90%, but it is *not possible* to know which of the firewalls reported the 90% utilization. + +Therefore each Scale Set instance should be configured with a dedicated Application Insights instance. + +## Usage + +Below you can find a simple example deploying a Scale Set w/o autoscaling, using defaults where possible: + +```hcl +module "vmss" { + source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmss" + + name = "ngfw-vmss" + resource_group_name = "hub-rg" + location = "West Europe" + + authentication = { + username = "panadmin" + password = "c0mpl1c@t3d" + disable_password_authentication = true + } + vm_image_configuration = { + img_version = "10.2.4" + } + scale_set_configuration = {} + interfaces = [ + { + name = "managmeent" + subnet_id = "management_subnet_ID_string" + }, + { + name = "private" + subnet_id = "private_subnet_ID_string" + lb_backend_pool_ids = ["LBI_backend_pool_ID"] + }, + { + name = "managmeent" + subnet_id = "management_subnet_ID_string" + lb_backend_pool_ids = ["LBE_backend_pool_ID"] + appgw_backend_pool_ids = ["AppGW_backend_pool_ID"] + } + ] + + autoscaling_configuration = {} + autoscaling_profiles = [] +} +``` \ No newline at end of file diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 5a46571e..c2233a4f 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -1,9 +1,14 @@ +<!-- BEGIN_TF_DOCS --> # Palo Alto Networks VMSS Module for Azure -A terraform module for VMSS VM-Series firewalls in Azure. +A terraform module for deploying a Scale Set based on Next Generation Firewalls in Azure. -**NOTE** \ -Due to [lack of proper method of running health probes](./main.tf#L21-54) against Pan-OS based VMs running in a Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. For this mode to actually work the `roll_instances_when_required` provider feature has to be also configured and set to `false`. Unfortunately this cannot be set in the `vmss` module, it has to be specified in the **root** module. +**NOTE!** \ +Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running in a +Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. + +For this mode to actually work the `roll_instances_when_required` provider feature has to be also configured and set to `false`. +Unfortunately this cannot be set in the `vmss` module, it has to be specified in the **root** module. Therefore, when using this module please add the following `provider` block to your code: @@ -17,118 +22,629 @@ provider "azurerm" { } ``` +## About rolling upgrades and auto healing + +Both, the rolling upgrade mode and auto healing target the 1<sup>st</sup> NIC on a Scale Set VM with a health probe to verify if +the VM is capable of handling traffic. Furthermore, for the health probe to work the 1<sup>st</sup> interface has to be added to +a Load Balancer. + +This provides some obstacles when deploying such setup with Next Generation Firewall based Scale Set: most importantly the health +probe would target the management interface which could lead to false-positives. A management service can respond to TCP/Http +probes, while the data plane remains unconfigured. An easy solution would to bo configure an interface swap, unfortunately this +is not available in the Azure VMSeries image yet. + +## Custom Metrics and Autoscaling + +Firewalls can publish custom metrics (for example `panSessionUtilization`) to Azure Application Insights to improve the +autoscaling. This is a suggested way of setting up scaling rules as these metrics are gathered only from the data plane. + +This however requires some additional steps: + +- deploy the [`ngfw_metrics`](../ngfw\_metrics/README.md) module, this module outputs two properties: + - `application_insights_ids` - a map of IDs of the deployed Application Insights instances + - `metrics_instrumentation_keys` - a map of instrumentation keys for the deployed Application Insights instances +- configure this module with the ID of the desired Application Insights instance, use the + [`var.autoscaling_configuration.application_insights_id`](#autoscaling\_configuration) property +- depending on the bootstrap method you use, configure the PanOS VMSeries plugins with the metrics instrumentation key + belonging to the Application Insights instance of your choice. + +The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain +back a result for solely a single firewall. Thus for example if three firewalls use the same Instrumentation Key and report +their respective session utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, +the max of 90%, but it is *not possible* to know which of the firewalls reported the 90% utilization. + +Therefore each Scale Set instance should be configured with a dedicated Application Insights instance. + ## Usage +Below you can find a simple example deploying a Scale Set w/o autoscaling, using defaults where possible: + ```hcl module "vmss" { source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmss" - location = "Australia Central" - name_prefix = "pan" - password = "your-password" - subnet_mgmt = azurerm_subnet.subnet_mgmt - subnet_private = azurerm_subnet.subnet_private - subnet_public = module.networks.subnet_public - bootstrap_storage_account = module.panorama.bootstrap_storage_account - bootstrap_share_name = "inboundsharename" - vhd_container = "vhd-storage-container-id" - lb_backend_pool_id = "private-backend-pool-id" + name = "ngfw-vmss" + resource_group_name = "hub-rg" + location = "West Europe" + + authentication = { + username = "panadmin" + password = "c0mpl1c@t3d" + disable_password_authentication = true + } + vm_image_configuration = { + img_version = "10.2.4" + } + scale_set_configuration = {} + interfaces = [ + { + name = "managmeent" + subnet_id = "management_subnet_ID_string" + }, + { + name = "private" + subnet_id = "private_subnet_ID_string" + lb_backend_pool_ids = ["LBI_backend_pool_ID"] + }, + { + name = "managmeent" + subnet_id = "management_subnet_ID_string" + lb_backend_pool_ids = ["LBE_backend_pool_ID"] + appgw_backend_pool_ids = ["AppGW_backend_pool_ID"] + } + ] + + autoscaling_configuration = {} + autoscaling_profiles = [] } ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_linux_virtual_machine_scale_set.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) | resource | -| [azurerm_monitor_autoscale_setting.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_autoscale_setting) | resource | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name"></a> [name](#input\_name) | Name of the created scale set. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Region to install VM-Series and dependencies. | `string` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the existing resource group where to place the resources created. | `string` | n/a | yes | -| <a name="input_vm_size"></a> [vm\_size](#input\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. | `string` | `"Standard_D3_v2"` | no | -| <a name="input_interfaces"></a> [interfaces](#input\_interfaces) | List of the network interface specifications.<br><br>NOTICE. The ORDER in which you specify the interfaces DOES MATTER.<br>Interfaces will be attached to VM in the order you define here, therefore:<br>* The first should be the management interface, which does not participate in data filtering.<br>* The remaining ones are the dataplane interfaces.<br><br>Options for an interface object:<br>- `name` - (required\|string) Interface name.<br>- `subnet_id` - (required\|string) Identifier of an existing subnet to create interface in.<br>- `create_pip` - (optional\|bool) If true, create a public IP for the interface<br>- `lb_backend_pool_ids` - (optional\|list(string)) A list of identifiers of an existing Load Balancer backend pools to associate interface with.<br>- `appgw_backend_pool_ids` - (optional\|list(String)) A list of identifier of the Application Gateway backend pools to associate interface with.<br>- `pip_domain_name_label` - (optional\|string) The Prefix which should be used for the Domain Name Label for each Virtual Machine Instance.<br><br>Example:<pre>[<br> {<br> name = "management"<br> subnet_id = azurerm_subnet.my_mgmt_subnet.id<br> create_pip = true<br> },<br> {<br> name = "private"<br> subnet_id = azurerm_subnet.my_priv_subnet.id<br> },<br> {<br> name = "public"<br> subnet_id = azurerm_subnet.my_pub_subnet.id<br> lb_backend_pool_ids = [azurerm_lb_backend_address_pool.lb_backend.id]<br> }<br>]</pre> | `any` | n/a | yes | -| <a name="input_username"></a> [username](#input\_username) | Initial administrative username to use for VM-Series. | `string` | `"panadmin"` | no | -| <a name="input_password"></a> [password](#input\_password) | Initial administrative password to use for VM-Series. | `string` | n/a | yes | -| <a name="input_ssh_keys"></a> [ssh\_keys](#input\_ssh\_keys) | A list of initial administrative SSH public keys that allow key-pair authentication. If not defined the `password` variable must be specified.<br><br>This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available:<pre>[<br> file("/path/to/public/keys/key_1.pub"),<br> file("/path/to/public/keys/key_2.pub")<br>]</pre> | `list(string)` | `[]` | no | -| <a name="input_disable_password_authentication"></a> [disable\_password\_authentication](#input\_disable\_password\_authentication) | If true, disables password-based authentication on VM-Series instances. | `bool` | `true` | no | -| <a name="input_encryption_at_host_enabled"></a> [encryption\_at\_host\_enabled](#input\_encryption\_at\_host\_enabled) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set#encryption_at_host_enabled). | `bool` | `null` | no | -| <a name="input_overprovision"></a> [overprovision](#input\_overprovision) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). | `bool` | `false` | no | -| <a name="input_platform_fault_domain_count"></a> [platform\_fault\_domain\_count](#input\_platform\_fault\_domain\_count) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). | `number` | `null` | no | -| <a name="input_proximity_placement_group_id"></a> [proximity\_placement\_group\_id](#input\_proximity\_placement\_group\_id) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). | `string` | `null` | no | -| <a name="input_scale_in_policy"></a> [scale\_in\_policy](#input\_scale\_in\_policy) | Which virtual machines are chosen for removal when a Virtual Machine Scale Set is scaled in. Either:<br><br>- `Default`, which, baring the availability zone usage and fault domain usage, deletes VM with the highest-numbered instance id,<br>- `NewestVM`, which, baring the availability zone usage, deletes VM with the newest creation time,<br>- `OldestVM`, which, baring the availability zone usage, deletes VM with the oldest creation time. | `string` | `null` | no | -| <a name="input_scale_in_force_deletion"></a> [scale\_in\_force\_deletion](#input\_scale\_in\_force\_deletion) | When set to `true` will force delete machines selected for removal by the `scale_in_policy`. | `bool` | `false` | no | -| <a name="input_single_placement_group"></a> [single\_placement\_group](#input\_single\_placement\_group) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). | `bool` | `null` | no | -| <a name="input_zone_balance"></a> [zone\_balance](#input\_zone\_balance) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). | `bool` | `true` | no | -| <a name="input_zones"></a> [zones](#input\_zones) | The availability zones to use, for example `["1", "2", "3"]`. If an empty list, no Availability Zones are used: `[]`. | `list(string)` | <pre>[<br> "1",<br> "2",<br> "3"<br>]</pre> | no | -| <a name="input_storage_account_type"></a> [storage\_account\_type](#input\_storage\_account\_type) | Type of Managed Disk which should be created. Possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs. | `string` | `"StandardSSD_LRS"` | no | -| <a name="input_disk_encryption_set_id"></a> [disk\_encryption\_set\_id](#input\_disk\_encryption\_set\_id) | The ID of the Disk Encryption Set which should be used to encrypt this Data Disk. | `string` | `null` | no | -| <a name="input_use_custom_image"></a> [use\_custom\_image](#input\_use\_custom\_image) | If true, use `custom_image_id` and ignore the inputs `username`, `password`, `img_version`, `img_publisher`, `img_offer`, `img_sku` (all these are used only for published images, not custom ones). | `bool` | `false` | no | -| <a name="input_custom_image_id"></a> [custom\_image\_id](#input\_custom\_image\_id) | Absolute ID of your own Custom Image to be used for creating new VM-Series. The Custom Image is expected to contain PAN-OS software. | `string` | `null` | no | -| <a name="input_enable_plan"></a> [enable\_plan](#input\_enable\_plan) | Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku "byol", which means "bring your own license", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image. | `bool` | `true` | no | -| <a name="input_img_publisher"></a> [img\_publisher](#input\_img\_publisher) | The Azure Publisher identifier for a image which should be deployed. | `string` | `"paloaltonetworks"` | no | -| <a name="input_img_offer"></a> [img\_offer](#input\_img\_offer) | The Azure Offer identifier corresponding to a published image. For `img_version` 9.1.1 or above, use "vmseries-flex"; for 9.1.0 or below use "vmseries1". | `string` | `"vmseries-flex"` | no | -| <a name="input_img_sku"></a> [img\_sku](#input\_img\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_img_version"></a> [img\_version](#input\_img\_version) | VM-Series PAN-OS version - list available for a default `img_offer` with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` | `string` | n/a | yes | -| <a name="input_accelerated_networking"></a> [accelerated\_networking](#input\_accelerated\_networking) | If true, enable Azure accelerated networking (SR-IOV) for all dataplane network interfaces. [Requires](https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-new-features/virtualization-features/support-for-azure-accelerated-networking-sriov) PAN-OS 9.0 or higher. The PAN-OS management interface (nic0) is never accelerated, whether this variable is true or false. | `bool` | `true` | no | -| <a name="input_application_insights_id"></a> [application\_insights\_id](#input\_application\_insights\_id) | An ID of Application Insights instance that should be used to provide metrics for autoscaling.<br><br>**Note**, to avoid false positives this should be an instance dedicated to this VMSS.<pre></pre> | `string` | `null` | no | -| <a name="input_autoscale_count_default"></a> [autoscale\_count\_default](#input\_autoscale\_count\_default) | The minimum number of instances that should be present in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the metrics to the thresholds. | `number` | `2` | no | -| <a name="input_autoscale_count_minimum"></a> [autoscale\_count\_minimum](#input\_autoscale\_count\_minimum) | The minimum number of instances that should be present in the scale set. | `number` | `2` | no | -| <a name="input_autoscale_count_maximum"></a> [autoscale\_count\_maximum](#input\_autoscale\_count\_maximum) | The maximum number of instances that should be present in the scale set. | `number` | `5` | no | -| <a name="input_autoscale_notification_emails"></a> [autoscale\_notification\_emails](#input\_autoscale\_notification\_emails) | List of email addresses to notify about autoscaling events. | `list(string)` | `[]` | no | -| <a name="input_autoscale_webhooks_uris"></a> [autoscale\_webhooks\_uris](#input\_autoscale\_webhooks\_uris) | Map where each key is an arbitrary identifier and each value is a webhook URI. The URIs receive autoscaling events. | `map(string)` | `{}` | no | -| <a name="input_autoscale_metrics"></a> [autoscale\_metrics](#input\_autoscale\_metrics) | Map of objects, where each key is the metric name to be used for autoscaling.<br>Each value of the map has the attributes `scaleout_threshold` and `scalein_threshold`, which cause the instance count to grow by 1 when metrics are greater or equal, or decrease by 1 when lower or equal, respectively.<br>The thresholds are applied to results of metrics' aggregation over a time window.<br>Example:<pre>{<br> "DataPlaneCPUUtilizationPct" = {<br> scaleout_threshold = 80<br> scalein_threshold = 20<br> }<br> "panSessionUtilization" = {<br> scaleout_threshold = 80<br> scalein_threshold = 20<br> }<br>}</pre>Other possible metrics include panSessionActive, panSessionThroughputKbps, panSessionThroughputPps, DataPlanePacketBufferUtilization. | `map(any)` | `{}` | no | -| <a name="input_scaleout_statistic"></a> [scaleout\_statistic](#input\_scaleout\_statistic) | Aggregation to use within each minute (the time grain) for metrics coming from different virtual machines. Possible values are Average, Min and Max. | `string` | `"Max"` | no | -| <a name="input_scaleout_time_aggregation"></a> [scaleout\_time\_aggregation](#input\_scaleout\_time\_aggregation) | Specifies how the metric should be combined over the time `scaleout_window_minutes`. Possible values are Average, Count, Maximum, Minimum, Last and Total. | `string` | `"Maximum"` | no | -| <a name="input_scaleout_window_minutes"></a> [scaleout\_window\_minutes](#input\_scaleout\_window\_minutes) | This is amount of time in minutes that autoscale engine will look back for metrics. For example, 10 minutes means that every time autoscale runs,<br>it will query metrics for the past 10 minutes. This allows metrics to stabilize and avoids reacting to transient spikes.<br>Must be between 5 and 720 minutes. | `number` | `10` | no | -| <a name="input_scaleout_cooldown_minutes"></a> [scaleout\_cooldown\_minutes](#input\_scaleout\_cooldown\_minutes) | Azure only considers adding a VM after this number of minutes has passed since the last VM scaling action. It should be much higher than `scaleout_window_minutes`, to account both for the VM-Series spin-up time and for the subsequent metrics stabilization time. Must be between 1 and 10080 minutes. | `number` | `25` | no | -| <a name="input_scalein_statistic"></a> [scalein\_statistic](#input\_scalein\_statistic) | Aggregation to use within each minute (the time grain) for metrics coming from different virtual machines. Possible values are Average, Min and Max. | `string` | `"Max"` | no | -| <a name="input_scalein_time_aggregation"></a> [scalein\_time\_aggregation](#input\_scalein\_time\_aggregation) | Specifies how the metric should be combined over the time `scalein_window_minutes`. Possible values are Average, Count, Maximum, Minimum, Last and Total. | `string` | `"Maximum"` | no | -| <a name="input_scalein_window_minutes"></a> [scalein\_window\_minutes](#input\_scalein\_window\_minutes) | This is amount of time in minutes that autoscale engine will look back for metrics. For example, 10 minutes means that every time autoscale runs,<br>it will query metrics for the past 10 minutes. This allows metrics to stabilize and avoids reacting to transient spikes.<br>Must be between 5 and 720 minutes. | `number` | `15` | no | -| <a name="input_scalein_cooldown_minutes"></a> [scalein\_cooldown\_minutes](#input\_scalein\_cooldown\_minutes) | Azure only considers deleting a VM after this number of minutes has passed since the last VM scaling action. Should be higher or equal to `scalein_window_minutes`. Must be between 1 and 10080 minutes. | `number` | `2880` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to use for all the created resources. | `map(string)` | `{}` | no | -| <a name="input_bootstrap_options"></a> [bootstrap\_options](#input\_bootstrap\_options) | Bootstrap options to pass to VM-Series instance.<br><br>Proper syntax is a string of semicolon separated properties.<br>Example:<br> bootstrap\_options = "type=dhcp-client;panorama-server=1.2.3.4"<br><br>For more details on bootstrapping see documentation: https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components | `string` | `""` | no | -| <a name="input_diagnostics_storage_uri"></a> [diagnostics\_storage\_uri](#input\_diagnostics\_storage\_uri) | The storage account's blob endpoint to hold diagnostic files. | `string` | `null` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_scale_set_name"></a> [scale\_set\_name](#output\_scale\_set\_name) | Name of the created scale set. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> - -## Custom Metrics - -Firewalls can publish custom metrics (for example `panSessionUtilization`) to Azure Application Insights to improve the autoscaling. -This however requires a manual initialization: copy the outputs `metrics_instrumentation_key` and paste it into your -PAN-OS webUI -> Device -> VM-Series -> Azure. This module automatically -completes solely the Step 1 of the [official procedure](https://docs.paloaltonetworks.com/vm-series/10-0/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall.html). - -If you manage the configuration from Panorama, this can be done in the same place, however the PAN-OS `VM-Series plugin` needs to be installed **on both** Panorama and VM-Series. +## Module's Required Inputs -The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain -back a result for solely a single firewall. Thus for example if three firewalls use the same Instrumentation Key and report -their respective session utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, the max of 90%, but it is *not possible* to know which of the firewalls reported the 90% utilization. +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Virtual Machine Scale Set. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). +[`image`](#image) | `object` | Basic Azure VM configuration. +[`interfaces`](#interfaces) | `list` | List of the network interfaces specifications. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`virtual_machine_scale_set`](#virtual_machine_scale_set) | `object` | Scale set parameters configuration. +[`autoscaling_configuration`](#autoscaling_configuration) | `object` | Autoscaling configuration common to all policies. +[`autoscaling_profiles`](#autoscaling_profiles) | `list` | A list defining autoscaling profiles. + + + +## Module's Outputs + +Name | Description +--- | --- +`scale_set_name` | Name of the created scale set. +`username` | Firewall admin account name. +`password` | Firewall admin password + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.80 + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`ptd_time` | - | ./dt_string_converter | + + +Resources used in this module: + +- `linux_virtual_machine_scale_set` (managed) +- `monitor_autoscale_setting` (managed) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Virtual Machine Scale Set. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### authentication + +A map defining authentication settings (including username and password). + +Following properties are available: + +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VMseries username +- `password` - (`string`, optional, defaults to `null`) the initial administrative VMSeries password +- `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication +- `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys + +**Important!** \ +The `password` property is required when `ssh_keys` is not specified. You can have both, password and key authentication. + +**Important!** \ +`ssh_keys` property is a list of strings, so each item should be the actual public key value. +If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. + + + +Type: + +```hcl +object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### image + +Basic Azure VM configuration. + +Following properties are available: + +- `version` - (`string`, optional, defaults to `null`) VMSeries PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` +- `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image + which should be deployed +- `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a + published image +- `sku` - (`string`, optional, defaults to `byol`) VMSeries SKU, list available with + `az vm image list -o table --all --publisher paloaltonetworks` +- `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Market Place +- `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PanOS image to be used for + creating new Virtual Machines + +**Important!** \ +`custom_id` and `version` properties are mutually exclusive. + + +Type: + +```hcl +object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "vmseries-flex") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### interfaces + +List of the network interfaces specifications. + +**Note!** \ +The ORDER in which you specify the interfaces DOES MATTER. + +Interfaces will be attached to VM in the order you define here, therefore: + +- the first should be the management interface, which does not participate in data filtering +- the remaining ones are the dataplane interfaces. + +Following configuration options are available: + +- `name` - (`string`, required) the interface name +- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in +- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface +- `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend + pools to associate the interface with +- `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend + pools to associate the interface with +- `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name + Label for each Virtual Machine Instance. + +Example: + +```hcl +[ + { + name = "management" + subnet_id = azurerm_subnet.my_mgmt_subnet.id + create_pip = true + }, + { + name = "private" + subnet_id = azurerm_subnet.my_priv_subnet.id + }, + { + name = "public" + subnet_id = azurerm_subnet.my_pub_subnet.id + lb_backend_pool_ids = [azurerm_lb_backend_address_pool.lb_backend.id] + } +] +``` + + +Type: + +```hcl +list(object({ + name = string + subnet_id = string + create_public_ip = optional(bool, false) + lb_backend_pool_ids = optional(list(string), []) + appgw_backend_pool_ids = optional(list(string), []) + pip_domain_name_label = optional(string) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### virtual_machine_scale_set + +Scale set parameters configuration. + +This map contains basic, as well as some optional Virtual Machine Scale Set parameters. Both types contain sane defaults. +Nevertheless they should be at least reviewed to meet deployment requirements. + +List of either required or important properties: + +- `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. +- `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from + this Scale Set will be created +- `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values) +- `bootstrap_options` - bootstrap options to pass to VM-Series instance. + + Proper syntax is a string of semicolon separated properties, for example: + + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` + + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + +List of other, optional properties: + +- `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled) +- `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk +- `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling + Encryption at Host +- `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) +- `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that + are used by this Virtual Machine Scale Set +- `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be + limited to a Single Placement Group, which means the number of instances will be capped + at 100 Virtual Machines +- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files +- `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". +- `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned" +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM + + + +Type: + +```hcl +object({ + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string, "StandardSSD_LRS") + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool, true) + platform_fault_domain_count = optional(number) + single_placement_group = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### autoscaling_configuration + +Autoscaling configuration common to all policies. + +Following properties are available: + +- `application_insights_id` - (`string`, optional, defaults to `null`) an ID of Application Insights instance that should + be used to provide metrics for autoscaling; to **avoid false positives** this should be an + instance **dedicated to this Scale Set** +- `default_count` - (`number`, optional, defaults to `2`) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable + to compare the metrics to the thresholds +- `scale_in_policy` - (`string`, optional, defaults to Azure default) controls which VMs are chosen for removal + during a scale-in, can be one of: `Default`, `NewestVM`, `OldestVM`. +- `scale_in_force_deletion` - (`bool`, optional, defaults to `false`) when `true` will **force delete** machines during a + scale-in operation +- `notification_emails` - (`list`, optional, defaults to `[]`) list of email addresses to notify about autoscaling + events +- `webhooks_uris` - (`map`, optional, defaults to `{}`) the URIs that receive autoscaling events; a map where keys + are just arbitrary identifiers and the values are the webhook URIs + + +Type: + +```hcl +object({ + application_insights_id = optional(string) + default_count = optional(number, 2) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool, false) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### autoscaling_profiles + +A list defining autoscaling profiles. + +**Note!** \ +The order does matter. The 1<sup>st</sup> profile becomes the default one. + +There are some considerations when creating autoscaling configuration: + +1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule +2. all other profiles should contain schedules +3. the scaling rules are optional, if you skip them you will create a profile with a set number of VM instances + (in such case the `minimum_count` and `maximum_count` properties are skipped). + +Following properties are available: + +- `name` - (`string`, required) the name of the profile +- `default_count` - (`number`, required) the default number of VMs +- `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in +- `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out +- `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply + - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can + be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string) + - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, + possible values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". + - `start_time` - (`string`, required) profile start time in RFC3339 format + - `end_time` - (`string`, required) profile end time in RFC3339 format +- `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. + + **Note!** \ + By default all VMSS built-in metrics are available. These do not differentiate between management and data planes. + For more accuracy please use NGFW metrics. + + Each metric definition is a map with 3 properties: + + - `name` - (`string`, required) name of the rule + - `scale_out_config` - (`map`, required) definition of the rule used to scale-out + - `scale_in_config` - (`map`, required) definition of the rule used to scale-in + + Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for scale-out + but optional for scale-in, when skipped in the latter configuration, default to scale-out values: + + - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action + - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) + the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, + `==` or `!=`. + - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that + the rule monitors, between 1 minute and 12 hours (specified in minutes) + - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from + `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum` + - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze + metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than + `grain_window_minutes` + - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from + `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` + or `Total` + - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute + and 1 week (specified in minutes) + - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count + of instances in a Scale Set will be changed during a scale action + +Example: + +```hcl +# defining one profile +autoscaling_profiles = [ + { + name = "default_profile" + default_count = 2 + minimum_count = 2 + maximum_count = 4 + scale_rules = [ + { + name = "DataPlaneCPUUtilizationPct" + scale_out_config = { + threshold = 85 + grain_window_minutes = 1 + aggregation_window_minutes = 25 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 60 + cooldown_window_minutes = 120 + } + } + ] + } +] + +# defining a profile with a rule scaling to 1 NGFW, used when no other rule is applicable +# and a second rule used for autoscaling during 'office hours' +autoscaling_profiles = [ + { + name = "default_profile" + default_count = 1 + }, + { + name = "weekday_profile" + default_count = 2 + minimum_count = 2 + maximum_count = 10 + recurrence = { + days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + start_time = "07:30" + end_time = "17:00" + } + scale_rules = [ + { + name = "Percentage CPU" + scale_out_config = { + threshold = 70 + grain_window_minutes = 5 + aggregation_window_minutes = 30 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 40 + cooldown_window_minutes = 120 + } + }, + { + name = "Outbound Flows" + scale_out_config = { + threshold = 500 + grain_window_minutes = 5 + aggregation_window_minutes = 30 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 400 + cooldown_window_minutes = 60 + } + } + ] + }, +] +``` + + +Type: + +```hcl +list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string, ">=") + grain_window_minutes = number + grain_aggregation_type = optional(string, "Average") + aggregation_window_minutes = number + aggregation_window_type = optional(string, "Average") + cooldown_window_minutes = number + change_count_by = optional(number, 1) + }) + scale_in_config = object({ + threshold = number + operator = optional(string, "<=") + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string, "Average") + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string, "Average") + cooldown_window_minutes = number + change_count_by = optional(number, 1) + }) + })), []) + })) +``` + + +Default value: `[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/dt_string_converter/.header.md b/modules/vmss/dt_string_converter/.header.md new file mode 100644 index 00000000..62ffce68 --- /dev/null +++ b/modules/vmss/dt_string_converter/.header.md @@ -0,0 +1,7 @@ +# Palo Alto Date/Time string representation converted + +This is a very simple module used solely to convert time in minutes to a string representation required by the +Azure Scale Set's autoscaling metrics rules. + +It's a sub module of the `vmss` module created to deduplicate code required to perform the conversion between +two formats. It was not designed to be used outside of the `vmss` module. diff --git a/modules/vmss/dt_string_converter/README.md b/modules/vmss/dt_string_converter/README.md new file mode 100644 index 00000000..28a24b29 --- /dev/null +++ b/modules/vmss/dt_string_converter/README.md @@ -0,0 +1,50 @@ +<!-- BEGIN_TF_DOCS --> +# Palo Alto Date/Time string representation converted + +This is a very simple module used solely to convert time in minutes to a string representation required by the +Azure Scale Set's autoscaling metrics rules. + +It's a sub module of the `vmss` module created to deduplicate code required to perform the conversion between +two formats. It was not designed to be used outside of the `vmss` module. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`time`](#time) | `number` | The time value in minutes to be converted to string representation. + + + + +## Module's Outputs + +Name | Description +--- | --- +`dt_string` | Azure string time representation. + +## Module's Nameplate + + + + + + + + + +## Inputs/Outpus details + +### Required Inputs + + +#### time + +The time value in minutes to be converted to string representation. + +Type: number + +<sup>[back to list](#modules-required-inputs)</sup> + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/dt_string_converter/main.tf b/modules/vmss/dt_string_converter/main.tf new file mode 100644 index 00000000..365bde60 --- /dev/null +++ b/modules/vmss/dt_string_converter/main.tf @@ -0,0 +1,19 @@ +terraform {} + +variable "time" { + description = "The time value in minutes to be converted to string representation." + type = number +} + +locals { + minutes = "${var.time % 60}M" + hours = "${floor(var.time / 60) % 24}H" + days = "${floor(var.time / (60 * 24))}D" + t_string = "T${local.hours != "0H" ? local.hours : ""}${local.minutes != "0M" ? local.minutes : ""}" + dt_string = "P${local.days != "0D" ? local.days : ""}${local.t_string != "T" ? local.t_string : ""}" +} + +output "dt_string" { + description = "Azure string time representation." + value = local.dt_string +} \ No newline at end of file diff --git a/modules/vmss/main.tf b/modules/vmss/main.tf index fb82c2f8..c3a054eb 100644 --- a/modules/vmss/main.tf +++ b/modules/vmss/main.tf @@ -1,78 +1,69 @@ +locals { + password = sensitive(var.authentication.password) +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set resource "azurerm_linux_virtual_machine_scale_set" "this" { - name = var.name - location = var.location - resource_group_name = var.resource_group_name - admin_username = var.username - admin_password = var.disable_password_authentication ? null : var.password - disable_password_authentication = var.disable_password_authentication - encryption_at_host_enabled = var.encryption_at_host_enabled - overprovision = var.overprovision - platform_fault_domain_count = var.platform_fault_domain_count - proximity_placement_group_id = var.proximity_placement_group_id - single_placement_group = var.single_placement_group - instances = var.autoscale_count_default - computer_name_prefix = null - sku = var.vm_size - tags = var.tags - zones = var.zones - zone_balance = var.zone_balance - provision_vm_agent = false + name = var.name + computer_name_prefix = null + location = var.location + resource_group_name = var.resource_group_name + + admin_username = var.authentication.username + admin_password = var.authentication.disable_password_authentication ? null : local.password + disable_password_authentication = var.authentication.disable_password_authentication + dynamic "admin_ssh_key" { - for_each = var.ssh_keys + for_each = { for k, v in var.authentication.ssh_keys : k => v } content { - username = var.username - public_key = ssh_keys.value + username = var.authentication.username + public_key = admin_ssh_key.value } } - lifecycle { - precondition { - condition = var.password != null || var.ssh_keys != [] - error_message = "Either password or ssh_key must be set in order to have access to the device" + encryption_at_host_enabled = var.virtual_machine_scale_set.encryption_at_host_enabled + overprovision = var.virtual_machine_scale_set.overprovision + extension_operations_enabled = var.virtual_machine_scale_set.allow_extension_operations + platform_fault_domain_count = var.virtual_machine_scale_set.platform_fault_domain_count + single_placement_group = var.virtual_machine_scale_set.single_placement_group + sku = var.virtual_machine_scale_set.size + zones = var.virtual_machine_scale_set.zones + zone_balance = length(coalesce(var.virtual_machine_scale_set.zones, [])) >= 2 # zone balance is available from at least 2 zones + provision_vm_agent = false + + dynamic "plan" { + for_each = var.image.enable_marketplace_plan ? [1] : [] + content { + name = var.image.sku + publisher = var.image.publisher + product = var.image.offer } } - # Allowing upgrade_mode = "Rolling" would be actually a big architectural change. First of all: - # - # Error: `health_probe_id` must be set or a health extension must be specified when `upgrade_mode` is set to "Rolling" - # - # VM-Series do not have a health extension. - # Having health_probe_id, as visible in the next error message below, Azure requires the first NIC to be - # the load-balanced one. Azure complains about "inbound-nic-fw-mgmt", which in that case was the primary IP config - # of the first NIC: - # - # Error: Error creating Linux Virtual Machine Scale Set "inbound-VMSS" (Resource Group "example-vmss-inbound"): - # compute.VirtualMachineScaleSetsClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: - # Code="CannotUseHealthProbeWithoutLoadBalancing" - # Message="VM scale set /subscriptions/d47f1af8-9795-4e86-bbce-da72cfd0f8ec/resourceGroups/EXAMPLE-VMSS-INBOUND/providers/Microsoft.Compute/virtualMachineScaleSets/inbound-VMSS cannot use probe /subscriptions/d47f1af8-9795-4e86-bbce-da72cfd0f8ec/resourceGroups/example-vmss-inbound/providers/Microsoft.Network/loadBalancers/inbound-public-elb/probes/inbound-public-elb as a HealthProbe because primary IP configuration inbound-nic-fw-mgmt of the scale set does not use load balancing. LoadBalancerBackendAddressPools property of the IP configuration must reference backend address pool of the load balancer that contains the probe." - # Details=[] - # │ - # │ with module.inbound_scale_set.azurerm_linux_virtual_machine_scale_set.this, - # │ on ../../modules/vmss/main.tf line 1, in resource "azurerm_linux_virtual_machine_scale_set" "this": - # │ 1: resource "azurerm_linux_virtual_machine_scale_set" "this" { - # - # Hence mgmt-interface-swap seems to be required on VM-Series, which would need a major overhaul of the - # subnet-related inputs. Without the mgmt-interface-swap, it seems impossible to have upgrade_mode = "Rolling". - # - # The phony LB on a management network does not seem a viable solution. For now Azure does not support two internal - # load balancers per VM. Also, health checking HTTP/SSH on management port would wrongly consider that unconfigured - # VM-Series is good to use. Unconfigured VM-Series still shows HTTP/SSH on the management interface. This does not - # happen when checking a dataplane interface, because the data only shows HTTP/SSH after the initial commit applies - # a specific management profile. - # - # Also the inbound vmss would have the ethernet1/1 public and ethernet1/2 private, but outbound vmss would have - # the ethernet1/1 private and ethernet1/2 public. That ensures the respective LB health probe works on ethernet1/1, - # which is the first NIC. - # - # The automatic_instance_repair also suffers from exactly the same problem: - # "Automatic repairs not supported for this Virtual Machine Scale Set because a health probe or health extension was not provided." - upgrade_mode = "Manual" - - custom_data = base64encode(var.bootstrap_options) + source_image_reference { + publisher = var.image.custom_id == null ? var.image.publisher : null + offer = var.image.custom_id == null ? var.image.offer : null + sku = var.image.custom_id == null ? var.image.sku : null + version = var.image.version + } + + source_image_id = var.image.custom_id + os_disk { + caching = "ReadWrite" + disk_encryption_set_id = var.virtual_machine_scale_set.disk_encryption_set_id # The Disk Encryption Set must have the Reader Role Assignment scoped on the Key Vault - in addition to an Access Policy to the Key Vault. + storage_account_type = var.virtual_machine_scale_set.disk_type + } + + + instances = var.autoscaling_configuration.default_count + + upgrade_mode = "Manual" # See README for more details no this setting. + + custom_data = var.virtual_machine_scale_set.bootstrap_options == null ? null : base64encode(var.virtual_machine_scale_set.bootstrap_options) scale_in { - rule = var.scale_in_policy - force_deletion_enabled = var.scale_in_force_deletion + rule = var.autoscaling_configuration.scale_in_policy + force_deletion_enabled = var.autoscaling_configuration.scale_in_force_deletion } dynamic "network_interface" { @@ -80,180 +71,306 @@ resource "azurerm_linux_virtual_machine_scale_set" "this" { iterator = nic content { - name = "${var.name}-${nic.value.name}" + name = nic.value.name primary = nic.key == 0 ? true : false enable_ip_forwarding = nic.key == 0 ? false : true - enable_accelerated_networking = nic.key == 0 ? false : var.accelerated_networking + enable_accelerated_networking = nic.key == 0 ? false : var.virtual_machine_scale_set.accelerated_networking ip_configuration { name = "primary" primary = true subnet_id = nic.value.subnet_id - load_balancer_backend_address_pool_ids = nic.key == 0 ? [] : try(nic.value.lb_backend_pool_ids, []) - application_gateway_backend_address_pool_ids = nic.key == 0 ? [] : try(nic.value.appgw_backend_pool_ids, []) + load_balancer_backend_address_pool_ids = nic.value.lb_backend_pool_ids + application_gateway_backend_address_pool_ids = nic.value.appgw_backend_pool_ids dynamic "public_ip_address" { - for_each = try(nic.value.create_pip, false) ? ["one"] : [] + for_each = nic.value.create_public_ip ? [1] : [] iterator = pip content { - name = "${var.name}-${nic.value.name}-pip" - domain_name_label = try(nic.value.pip_domain_name_label, null) + name = nic.value.name + domain_name_label = nic.value.pip_domain_name_label } } } } } - - boot_diagnostics { - storage_account_uri = var.diagnostics_storage_uri - } + boot_diagnostics { storage_account_uri = var.virtual_machine_scale_set.diagnostics_storage_uri } identity { - type = "SystemAssigned" # (Required) The type of Managed Identity which should be assigned to the Linux Virtual Machine Scale Set. Possible values are SystemAssigned, UserAssigned and SystemAssigned, UserAssigned. + type = var.virtual_machine_scale_set.identity_type + identity_ids = var.virtual_machine_scale_set.identity_ids } - source_image_id = var.custom_image_id - - source_image_reference { - publisher = var.use_custom_image ? null : var.img_publisher - offer = var.use_custom_image ? null : var.img_offer - sku = var.use_custom_image ? null : var.img_sku - version = var.use_custom_image ? null : var.img_version - } + tags = var.tags +} - os_disk { - caching = "ReadWrite" - disk_encryption_set_id = var.disk_encryption_set_id # The Disk Encryption Set must have the Reader Role Assignment scoped on the Key Vault - in addition to an Access Policy to the Key Vault. - storage_account_type = var.storage_account_type - } +locals { + # this loop will pull out all `window_minutes`-like properties from the scaling rules + # into one map that can be fed into the `pdt_time` module + profile_time_windows_flat = flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + [ + for k, v in rule.scale_out_config : + { + name = "${profile.name}-${replace(lower(rule.name), " ", "_")}-scale_out-${k}" + value = v + } + if strcontains(k, "window_minutes") + ], + [ + for k, v in rule.scale_in_config : + { + name = "${profile.name}-${replace(lower(rule.name), " ", "_")}-scale_in-${k}" + value = v + } + if strcontains(k, "window_minutes") && v != null + ] + ] + ] + ]) + profile_time_windows = { for v in local.profile_time_windows_flat : v.name => v.value } +} - dynamic "plan" { - for_each = var.enable_plan ? ["one"] : [] +module "ptd_time" { + source = "./dt_string_converter" + for_each = local.profile_time_windows + time = each.value +} - content { - name = var.img_sku - publisher = var.img_publisher - product = var.img_offer - } +locals { + panos_metrics = [ + "DataPlaneCPUUtilizationPct", + "DataPlanePacketBufferUtilization", + "panGPGatewayUtilizationPct", + "panGPGWUtilizationActiveTunnels", + "panSessionActive", + "panSessionConnectionsPerSecond", + "panSessionSslProxyUtilization", + "panSessionThroughputKbps", + "panSessionThroughputPps", + "panSessionUtilization" + ] + operator = { + ">" = "GreaterThan" + ">=" = "GreaterThanOrEqual" + "<" = "LessThan" + "<=" = "LessThanOrEqual" + "==" = "Equals" + "!=" = "NotEquals" } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_autoscale_setting resource "azurerm_monitor_autoscale_setting" "this" { - count = length(var.autoscale_metrics) > 0 ? 1 : 0 + count = length(var.autoscaling_profiles) > 0 ? 1 : 0 - name = "${var.name}-autoscale" + name = var.name location = var.location resource_group_name = var.resource_group_name target_resource_id = azurerm_linux_virtual_machine_scale_set.this.id - profile { - name = "autoscale profile" + # the default profile or (when more then one) the profiles representing start times + + # This code covers the Microsoft logic for creating multiple autoscaling profiles. + # From the variables point of view, the definition tries to mimic the Azure Portal interface. + # When defining 2 and more profiles, for the 2nd and latter we are specifying start and end time (in HH:MM format). + # From the code/ARM point of view, there is nothing like end time for a profile. Instead of that the 1st (default) + # profile is being duplicated with the start time equal to the end time of a particular profile. + + # Example. We have 3 profiles (let's skip the days part when configuring a profile window as it is not used in the logic): + # - profile1 - the 1st one, default + # - profile2 - starts at 7:00 ends at 17:00 + # - profile3 - start at 21:00 end at 22:30 + # In this case `profile1` will never be created explicitly. Instead of that we will get 4 profiles, like the following: + # - vmss_profile_1 - starts at 7:00, contains configuration of `profile2` + # - vmss_profile_2 - starts at 21:00, contains configuration of `profile3` + # - vmss_profile_3 - starts at 17:00, contains configuration of `profile1` + # - vmss_profile_4 - starts at 22:30, contains configuration of `profile1` + # `vmss_profile_1` and `vmss_profile_2` will have names of `profile2` and `profile3` respectively. + # `vmss_profile_3` and `vmss_profile_4` will have auto-calculated names in the following format: + # `name:profile1,for:profile#`, where `profile#` will be `profile3` and `profile4` respectively. + + # Therefore, in the code below you have two dynamic `profile` blocks. The 1st one is defining the profiles with the + # starting time. + # The 2nd block is being run only when you have more than one profile and defines the closing profiles, + # so the ones with the end time. + + dynamic "profile" { + # for_each = length(var.autoscaling_profiles) == 1 ? var.autoscaling_profiles : slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) + for_each = slice(var.autoscaling_profiles, length(var.autoscaling_profiles) == 1 ? 0 : 1, length(var.autoscaling_profiles)) + content { + name = profile.value.name - capacity { - default = var.autoscale_count_default - minimum = var.autoscale_count_minimum - maximum = var.autoscale_count_maximum - } + capacity { + default = profile.value.default_count + minimum = coalesce(profile.value.minimum_count, profile.value.default_count) + maximum = coalesce(profile.value.maximum_count, profile.value.default_count) + } - dynamic "rule" { - for_each = var.autoscale_metrics - - content { - metric_trigger { - metric_name = rule.key - metric_resource_id = rule.key == "Percentage CPU" ? azurerm_linux_virtual_machine_scale_set.this.id : var.application_insights_id - metric_namespace = "Azure.ApplicationInsights" - operator = "GreaterThanOrEqual" - threshold = rule.value.scaleout_threshold - - statistic = var.scaleout_statistic - time_aggregation = var.scaleout_time_aggregation - time_grain = "PT1M" # PT1M means: Period of Time 1 Minute - time_window = local.scaleout_window + dynamic "recurrence" { + for_each = profile.value.recurrence != null ? [1] : [] + content { + days = profile.value.recurrence.days + hours = [split(":", profile.value.recurrence.start_time)[0]] + minutes = [split(":", profile.value.recurrence.start_time)[1]] + timezone = profile.value.recurrence.timezone } + } + + # scale out rule portion + dynamic "rule" { + for_each = profile.value.scale_rules + content { + metric_trigger { + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_out_config.operator] + + threshold = rule.value.scale_out_config.threshold + statistic = rule.value.scale_out_config.grain_aggregation_type + time_aggregation = rule.value.scale_out_config.aggregation_window_type + time_grain = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + time_window = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + } - scale_action { - direction = "Increase" - value = "1" - type = "ChangeCount" - cooldown = local.scaleout_cooldown + scale_action { + direction = "Increase" + value = rule.value.scale_out_config.change_count_by + type = "ChangeCount" + cooldown = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes"].dt_string + } + } + } + + # scale in rule portion + dynamic "rule" { + for_each = profile.value.scale_rules + content { + metric_trigger { + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_in_config.operator] + + threshold = rule.value.scale_in_config.threshold + statistic = rule.value.scale_in_config.grain_aggregation_type + time_aggregation = rule.value.scale_in_config.aggregation_window_type + time_grain = try( + module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes"].dt_string, + module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + ) + time_window = try( + module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes"].dt_string, + module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + ) + } + + scale_action { + direction = "Decrease" + value = rule.value.scale_in_config.change_count_by + type = "ChangeCount" + cooldown = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes"].dt_string + } } } } + } - dynamic "rule" { - for_each = var.autoscale_metrics - - content { - metric_trigger { - metric_name = rule.key - metric_resource_id = rule.key == "Percentage CPU" ? azurerm_linux_virtual_machine_scale_set.this.id : var.application_insights_id - metric_namespace = "Azure.ApplicationInsights" - operator = "LessThanOrEqual" - threshold = rule.value.scalein_threshold - - statistic = var.scalein_statistic - time_aggregation = var.scalein_time_aggregation - time_grain = "PT1M" - time_window = local.scalein_window + # for more than one profile, these are the profiles representing end times + dynamic "profile" { + # for_each = length(var.autoscaling_profiles) == 1 ? var.autoscaling_profiles : slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) + for_each = [for index, profile in var.autoscaling_profiles : profile if index != 0] + content { + name = "{\"name\":\"${var.autoscaling_profiles[0].name}\",\"for\":\"${profile.value.name}\"}" + + capacity { + default = var.autoscaling_profiles[0].default_count + minimum = coalesce(var.autoscaling_profiles[0].minimum_count, var.autoscaling_profiles[0].default_count) + maximum = coalesce(var.autoscaling_profiles[0].maximum_count, var.autoscaling_profiles[0].default_count) + } + + dynamic "recurrence" { + for_each = profile.value.recurrence != null ? [1] : [] + content { + days = profile.value.recurrence.days + hours = [split(":", profile.value.recurrence.end_time)[0]] + minutes = [split(":", profile.value.recurrence.end_time)[1]] + timezone = profile.value.recurrence.timezone } + } - scale_action { - direction = "Decrease" - value = "1" - type = "ChangeCount" - cooldown = local.scalein_cooldown + # scale out rule portion + dynamic "rule" { + for_each = var.autoscaling_profiles[0].scale_rules + content { + metric_trigger { + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_out_config.operator] + + threshold = rule.value.scale_out_config.threshold + statistic = rule.value.scale_out_config.grain_aggregation_type + time_aggregation = rule.value.scale_out_config.aggregation_window_type + time_grain = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + time_window = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + } + + scale_action { + direction = "Increase" + value = rule.value.scale_out_config.change_count_by + type = "ChangeCount" + cooldown = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes"].dt_string + } + } + } + + # scale in rule portion + dynamic "rule" { + for_each = var.autoscaling_profiles[0].scale_rules + content { + metric_trigger { + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_in_config.operator] + + threshold = rule.value.scale_in_config.threshold + statistic = rule.value.scale_in_config.grain_aggregation_type + time_aggregation = rule.value.scale_in_config.aggregation_window_type + time_grain = try( + module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes"].dt_string, + module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + ) + time_window = try( + module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes"].dt_string, + module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + ) + } + + scale_action { + direction = "Decrease" + value = rule.value.scale_in_config.change_count_by + type = "ChangeCount" + cooldown = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes"].dt_string + } } } + } - } + } notification { - email { - custom_emails = var.autoscale_notification_emails - } + email { custom_emails = var.autoscaling_configuration.notification_emails } dynamic "webhook" { - for_each = var.autoscale_webhooks_uris - - content { - service_uri = webhook.value - } + for_each = var.autoscaling_configuration.webhooks_uris + content { service_uri = webhook.value } } } tags = var.tags } - -locals { - # Azure demands Period of Time 1 day 12 hours and 30 minutes to be written as "P1DT12H30M". - # Note the "T", which we insert only if there are non-zero hours/minutes. - # What's worse, time periods "PT61M" and "PT1H1M" are equal for Azure, so Azure corrects them, but they are - # considered inequal inside the `terraform plan`. - # Using solely the number of minutes ("PT61M") causes a bad Terraform apply loop. The same happens for any string - # that Azure decides to correct for us. - scaleout_cooldown_minutes = "${var.scaleout_cooldown_minutes % 60}M" - scaleout_cooldown_hours = "${floor(var.scaleout_cooldown_minutes / 60) % 24}H" - scaleout_cooldown_days = "${floor(var.scaleout_cooldown_minutes / (60 * 24))}D" - scaleout_cooldown_t = "T${local.scaleout_cooldown_hours != "0H" ? local.scaleout_cooldown_hours : ""}${local.scaleout_cooldown_minutes != "0M" ? local.scaleout_cooldown_minutes : ""}" - scaleout_cooldown = "P${local.scaleout_cooldown_days != "0D" ? local.scaleout_cooldown_days : ""}${local.scaleout_cooldown_t != "T" ? local.scaleout_cooldown_t : ""}" - - scaleout_window_minutes = "${var.scaleout_window_minutes % 60}M" - scaleout_window_hours = "${floor(var.scaleout_window_minutes / 60) % 24}H" - scaleout_window_days = "${floor(var.scaleout_window_minutes / (60 * 24))}D" - scaleout_window_t = "T${local.scaleout_window_hours != "0H" ? local.scaleout_window_hours : ""}${local.scaleout_window_minutes != "0M" ? local.scaleout_window_minutes : ""}" - scaleout_window = "P${local.scaleout_window_days != "0D" ? local.scaleout_window_days : ""}${local.scaleout_window_t != "T" ? local.scaleout_window_t : ""}" - - scalein_cooldown_minutes = "${var.scalein_cooldown_minutes % 60}M" - scalein_cooldown_hours = "${floor(var.scalein_cooldown_minutes / 60) % 24}H" - scalein_cooldown_days = "${floor(var.scalein_cooldown_minutes / (60 * 24))}D" - scalein_cooldown_t = "T${local.scalein_cooldown_hours != "0H" ? local.scalein_cooldown_hours : ""}${local.scalein_cooldown_minutes != "0M" ? local.scalein_cooldown_minutes : ""}" - scalein_cooldown = "P${local.scalein_cooldown_days != "0D" ? local.scalein_cooldown_days : ""}${local.scalein_cooldown_t != "T" ? local.scalein_cooldown_t : ""}" - - scalein_window_minutes = "${var.scalein_window_minutes % 60}M" - scalein_window_hours = "${floor(var.scalein_window_minutes / 60) % 24}H" - scalein_window_days = "${floor(var.scalein_window_minutes / (60 * 24))}D" - scalein_window_t = "T${local.scalein_window_hours != "0H" ? local.scalein_window_hours : ""}${local.scalein_window_minutes != "0M" ? local.scalein_window_minutes : ""}" - scalein_window = "P${local.scalein_window_days != "0D" ? local.scalein_window_days : ""}${local.scalein_window_t != "T" ? local.scalein_window_t : ""}" -} diff --git a/modules/vmss/outputs.tf b/modules/vmss/outputs.tf index 6fb0d074..c49ff837 100644 --- a/modules/vmss/outputs.tf +++ b/modules/vmss/outputs.tf @@ -2,3 +2,14 @@ output "scale_set_name" { description = "Name of the created scale set." value = azurerm_linux_virtual_machine_scale_set.this.name } + +output "username" { + description = "Firewall admin account name." + value = var.authentication.username +} + +output "password" { + description = "Firewall admin password" + value = local.password + sensitive = true +} \ No newline at end of file diff --git a/modules/vmss/variables.tf b/modules/vmss/variables.tf index 94419198..5d2b8bcd 100644 --- a/modules/vmss/variables.tf +++ b/modules/vmss/variables.tf @@ -1,44 +1,219 @@ variable "name" { - description = "Name of the created scale set." + description = "The name of the Azure Virtual Machine Scale Set." type = string } -variable "location" { - description = "Region to install VM-Series and dependencies." +variable "resource_group_name" { + description = "The name of the Resource Group to use." type = string } -variable "resource_group_name" { - description = "Name of the existing resource group where to place the resources created." +variable "location" { + description = "The name of the Azure region to deploy the resources in." type = string } -variable "vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." - default = "Standard_D3_v2" - type = string +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} + +variable "authentication" { + description = <<-EOF + A map defining authentication settings (including username and password). + + Following properties are available: + + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VMseries username + - `password` - (`string`, optional, defaults to `null`) the initial administrative VMSeries password + - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication + - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys + + **Important!** \ + The `password` property is required when `ssh_keys` is not specified. You can have both, password and key authentication. + + **Important!** \ + `ssh_keys` property is a list of strings, so each item should be the actual public key value. + If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. + + EOF + type = object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + validation { + condition = !( + var.authentication.password == null && !var.authentication.disable_password_authentication && length(var.authentication.ssh_keys) == 0 + ) + error_message = "At least `password` or `ssh_keys` property has to be set when `disable_password_authentication` is `false`." + } + validation { + condition = !( + var.authentication.disable_password_authentication && length(var.authentication.ssh_keys) == 0 + ) + error_message = "The `ssh_keys` property has to be set when `disable_password_authentication` is `true`." + } +} + +variable "image" { + description = <<-EOF + Basic Azure VM configuration. + + Following properties are available: + + - `version` - (`string`, optional, defaults to `null`) VMSeries PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` + - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image + which should be deployed + - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a + published image + - `sku` - (`string`, optional, defaults to `byol`) VMSeries SKU, list available with + `az vm image list -o table --all --publisher paloaltonetworks` + - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Market Place + - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PanOS image to be used for + creating new Virtual Machines + + **Important!** \ + `custom_id` and `version` properties are mutually exclusive. + EOF + type = object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "vmseries-flex") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) + validation { + condition = (var.image.custom_id != null && var.image.version == null + ) || ( + var.image.custom_id == null && var.image.version != null + ) + error_message = "Either `custom_id` or `version` has to be defined." + } +} + +variable "virtual_machine_scale_set" { + description = <<-EOF + Scale set parameters configuration. + + This map contains basic, as well as some optional Virtual Machine Scale Set parameters. Both types contain sane defaults. + Nevertheless they should be at least reviewed to meet deployment requirements. + + List of either required or important properties: + + - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. + - `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from + this Scale Set will be created + - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values) + - `bootstrap_options` - bootstrap options to pass to VM-Series instance. + + Proper syntax is a string of semicolon separated properties, for example: + + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` + + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + + List of other, optional properties: + + - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled) + - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk + - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling + Encryption at Host + - `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) + - `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that + are used by this Virtual Machine Scale Set + - `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be + limited to a Single Placement Group, which means the number of instances will be capped + at 100 Virtual Machines + - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files + - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". + - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned" + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM + + EOF + default = {} + nullable = false + type = object({ + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string, "StandardSSD_LRS") + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool, true) + platform_fault_domain_count = optional(number) + single_placement_group = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) + }) + validation { + condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine_scale_set.disk_type) + error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + } + # validation { + # condition = length(var.virtual_machine_scale_set.zones) == 3 || var.virtual_machine_scale_set.zones == null + # error_message = "The `var.virtual_machine_scale_set.zones` can either be a list of all Availability Zones or explicit `null`." + # } + validation { + condition = contains( + ["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], + var.virtual_machine_scale_set.identity_type + ) + error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + } + validation { + condition = var.virtual_machine_scale_set.identity_type == "SystemAssigned" ? length(var.virtual_machine_scale_set.identity_ids) == 0 : length(var.virtual_machine_scale_set.identity_ids) >= 0 + error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + } } variable "interfaces" { description = <<-EOF - List of the network interface specifications. + List of the network interfaces specifications. + + **Note!** \ + The ORDER in which you specify the interfaces DOES MATTER. - NOTICE. The ORDER in which you specify the interfaces DOES MATTER. Interfaces will be attached to VM in the order you define here, therefore: - * The first should be the management interface, which does not participate in data filtering. - * The remaining ones are the dataplane interfaces. + + - the first should be the management interface, which does not participate in data filtering + - the remaining ones are the dataplane interfaces. - Options for an interface object: - - `name` - (required|string) Interface name. - - `subnet_id` - (required|string) Identifier of an existing subnet to create interface in. - - `create_pip` - (optional|bool) If true, create a public IP for the interface - - `lb_backend_pool_ids` - (optional|list(string)) A list of identifiers of an existing Load Balancer backend pools to associate interface with. - - `appgw_backend_pool_ids` - (optional|list(String)) A list of identifier of the Application Gateway backend pools to associate interface with. - - `pip_domain_name_label` - (optional|string) The Prefix which should be used for the Domain Name Label for each Virtual Machine Instance. + Following configuration options are available: + + - `name` - (`string`, required) the interface name + - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in + - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface + - `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend + pools to associate the interface with + - `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend + pools to associate the interface with + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name + Label for each Virtual Machine Instance. Example: - ``` + ```hcl [ { name = "management" @@ -57,325 +232,343 @@ variable "interfaces" { ] ``` EOF - type = any -} - -variable "username" { - description = "Initial administrative username to use for VM-Series." - default = "panadmin" - type = string -} - -variable "password" { - description = "Initial administrative password to use for VM-Series." - type = string - sensitive = true + type = list(object({ + name = string + subnet_id = string + create_public_ip = optional(bool, false) + lb_backend_pool_ids = optional(list(string), []) + appgw_backend_pool_ids = optional(list(string), []) + pip_domain_name_label = optional(string) + })) + validation { + condition = length(var.interfaces[0].lb_backend_pool_ids) == 0 && length(var.interfaces[0].appgw_backend_pool_ids) == 0 + error_message = "The `lb_backend_pool_ids` and `appgw_backend_pool_ids` properties are not acceptable for the 1st (management) interface." + } } -variable "ssh_keys" { +variable "autoscaling_configuration" { description = <<-EOF - A list of initial administrative SSH public keys that allow key-pair authentication. If not defined the `password` variable must be specified. - - This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available: - - ``` - [ - file("/path/to/public/keys/key_1.pub"), - file("/path/to/public/keys/key_2.pub") - ] - ``` + Autoscaling configuration common to all policies. + + Following properties are available: + + - `application_insights_id` - (`string`, optional, defaults to `null`) an ID of Application Insights instance that should + be used to provide metrics for autoscaling; to **avoid false positives** this should be an + instance **dedicated to this Scale Set** + - `default_count` - (`number`, optional, defaults to `2`) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable + to compare the metrics to the thresholds + - `scale_in_policy` - (`string`, optional, defaults to Azure default) controls which VMs are chosen for removal + during a scale-in, can be one of: `Default`, `NewestVM`, `OldestVM`. + - `scale_in_force_deletion` - (`bool`, optional, defaults to `false`) when `true` will **force delete** machines during a + scale-in operation + - `notification_emails` - (`list`, optional, defaults to `[]`) list of email addresses to notify about autoscaling + events + - `webhooks_uris` - (`map`, optional, defaults to `{}`) the URIs that receive autoscaling events; a map where keys + are just arbitrary identifiers and the values are the webhook URIs EOF - default = [] - type = list(string) -} - -variable "disable_password_authentication" { - description = "If true, disables password-based authentication on VM-Series instances." - default = true - type = bool -} - -variable "encryption_at_host_enabled" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set#encryption_at_host_enabled)." - default = null - type = bool -} - -variable "overprovision" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set)." - default = false - type = bool + default = {} nullable = false + type = object({ + application_insights_id = optional(string) + default_count = optional(number, 2) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool, false) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }) + validation { + condition = var.autoscaling_configuration.scale_in_policy != null ? contains(["Default", "NewestVM", "OldestVM"], var.autoscaling_configuration.scale_in_policy) : true + error_message = "The `scale_in_policy` property can be one of: `Default`, `NewestVM`, `OldestVM`." + } } -variable "platform_fault_domain_count" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set)." - default = null - type = number -} - -variable "proximity_placement_group_id" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set)." - default = null - type = string -} - -variable "scale_in_policy" { +variable "autoscaling_profiles" { description = <<-EOF - Which virtual machines are chosen for removal when a Virtual Machine Scale Set is scaled in. Either: - - - `Default`, which, baring the availability zone usage and fault domain usage, deletes VM with the highest-numbered instance id, - - `NewestVM`, which, baring the availability zone usage, deletes VM with the newest creation time, - - `OldestVM`, which, baring the availability zone usage, deletes VM with the oldest creation time. - EOF - default = null - type = string -} - -variable "scale_in_force_deletion" { - description = "When set to `true` will force delete machines selected for removal by the `scale_in_policy`." - default = false - type = bool - nullable = false -} - -variable "single_placement_group" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set)." - default = null - type = bool -} - -variable "zone_balance" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set)." - default = true - type = bool -} - -variable "zones" { - description = "The availability zones to use, for example `[\"1\", \"2\", \"3\"]`. If an empty list, no Availability Zones are used: `[]`." - default = ["1", "2", "3"] - type = list(string) - nullable = false -} - -variable "storage_account_type" { - description = "Type of Managed Disk which should be created. Possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs." - default = "StandardSSD_LRS" - type = string - nullable = false -} - -variable "disk_encryption_set_id" { - description = "The ID of the Disk Encryption Set which should be used to encrypt this Data Disk." - default = null - type = string -} - -variable "use_custom_image" { - description = "If true, use `custom_image_id` and ignore the inputs `username`, `password`, `img_version`, `img_publisher`, `img_offer`, `img_sku` (all these are used only for published images, not custom ones)." - default = false - type = bool -} - -variable "custom_image_id" { - description = "Absolute ID of your own Custom Image to be used for creating new VM-Series. The Custom Image is expected to contain PAN-OS software." - default = null - type = string -} - -variable "enable_plan" { - description = "Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku \"byol\", which means \"bring your own license\", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image." - default = true - type = bool -} - -variable "img_publisher" { - description = "The Azure Publisher identifier for a image which should be deployed." - default = "paloaltonetworks" - type = string -} - -variable "img_offer" { - description = "The Azure Offer identifier corresponding to a published image. For `img_version` 9.1.1 or above, use \"vmseries-flex\"; for 9.1.0 or below use \"vmseries1\"." - default = "vmseries-flex" - type = string -} - -variable "img_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} + A list defining autoscaling profiles. + + **Note!** \ + The order does matter. The 1<sup>st</sup> profile becomes the default one. + + There are some considerations when creating autoscaling configuration: + + 1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule + 2. all other profiles should contain schedules + 3. the scaling rules are optional, if you skip them you will create a profile with a set number of VM instances + (in such case the `minimum_count` and `maximum_count` properties are skipped). + + Following properties are available: + + - `name` - (`string`, required) the name of the profile + - `default_count` - (`number`, required) the default number of VMs + - `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in + - `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out + - `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply + - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can + be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string) + - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, + possible values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". + - `start_time` - (`string`, required) profile start time in RFC3339 format + - `end_time` - (`string`, required) profile end time in RFC3339 format + - `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. + + **Note!** \ + By default all VMSS built-in metrics are available. These do not differentiate between management and data planes. + For more accuracy please use NGFW metrics. + + Each metric definition is a map with 3 properties: + + - `name` - (`string`, required) name of the rule + - `scale_out_config` - (`map`, required) definition of the rule used to scale-out + - `scale_in_config` - (`map`, required) definition of the rule used to scale-in + + Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for scale-out + but optional for scale-in, when skipped in the latter configuration, default to scale-out values: + + - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action + - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) + the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, + `==` or `!=`. + - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that + the rule monitors, between 1 minute and 12 hours (specified in minutes) + - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from + `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum` + - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze + metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than + `grain_window_minutes` + - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from + `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` + or `Total` + - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute + and 1 week (specified in minutes) + - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count + of instances in a Scale Set will be changed during a scale action -variable "img_version" { - description = "VM-Series PAN-OS version - list available for a default `img_offer` with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`" - type = string -} - -variable "accelerated_networking" { - description = "If true, enable Azure accelerated networking (SR-IOV) for all dataplane network interfaces. [Requires](https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-new-features/virtualization-features/support-for-azure-accelerated-networking-sriov) PAN-OS 9.0 or higher. The PAN-OS management interface (nic0) is never accelerated, whether this variable is true or false." - default = true - type = bool - nullable = false -} + Example: -variable "application_insights_id" { - description = <<-EOF - An ID of Application Insights instance that should be used to provide metrics for autoscaling. + ```hcl + # defining one profile + autoscaling_profiles = [ + { + name = "default_profile" + default_count = 2 + minimum_count = 2 + maximum_count = 4 + scale_rules = [ + { + name = "DataPlaneCPUUtilizationPct" + scale_out_config = { + threshold = 85 + grain_window_minutes = 1 + aggregation_window_minutes = 25 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 60 + cooldown_window_minutes = 120 + } + } + ] + } + ] - **Note**, to avoid false positives this should be an instance dedicated to this VMSS. + # defining a profile with a rule scaling to 1 NGFW, used when no other rule is applicable + # and a second rule used for autoscaling during 'office hours' + autoscaling_profiles = [ + { + name = "default_profile" + default_count = 1 + }, + { + name = "weekday_profile" + default_count = 2 + minimum_count = 2 + maximum_count = 10 + recurrence = { + days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] + start_time = "07:30" + end_time = "17:00" + } + scale_rules = [ + { + name = "Percentage CPU" + scale_out_config = { + threshold = 70 + grain_window_minutes = 5 + aggregation_window_minutes = 30 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 40 + cooldown_window_minutes = 120 + } + }, + { + name = "Outbound Flows" + scale_out_config = { + threshold = 500 + grain_window_minutes = 5 + aggregation_window_minutes = 30 + cooldown_window_minutes = 60 + } + scale_in_config = { + threshold = 400 + cooldown_window_minutes = 60 + } + } + ] + }, + ] ``` EOF - default = null - type = string -} - -variable "autoscale_count_default" { - description = "The minimum number of instances that should be present in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the metrics to the thresholds." - default = 2 - type = number - nullable = false -} - -variable "autoscale_count_minimum" { - description = "The minimum number of instances that should be present in the scale set." - default = 2 - type = number - nullable = false -} - -variable "autoscale_count_maximum" { - description = "The maximum number of instances that should be present in the scale set." - default = 5 - type = number - nullable = false -} - -variable "autoscale_notification_emails" { - description = "List of email addresses to notify about autoscaling events." default = [] - type = list(string) nullable = false -} - -variable "autoscale_webhooks_uris" { - description = "Map where each key is an arbitrary identifier and each value is a webhook URI. The URIs receive autoscaling events." - default = {} - type = map(string) -} - -variable "autoscale_metrics" { - description = <<-EOF - Map of objects, where each key is the metric name to be used for autoscaling. - Each value of the map has the attributes `scaleout_threshold` and `scalein_threshold`, which cause the instance count to grow by 1 when metrics are greater or equal, or decrease by 1 when lower or equal, respectively. - The thresholds are applied to results of metrics' aggregation over a time window. - Example: - ``` - { - "DataPlaneCPUUtilizationPct" = { - scaleout_threshold = 80 - scalein_threshold = 20 - } - "panSessionUtilization" = { - scaleout_threshold = 80 - scalein_threshold = 20 - } + type = list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string, ">=") + grain_window_minutes = number + grain_aggregation_type = optional(string, "Average") + aggregation_window_minutes = number + aggregation_window_type = optional(string, "Average") + cooldown_window_minutes = number + change_count_by = optional(number, 1) + }) + scale_in_config = object({ + threshold = number + operator = optional(string, "<=") + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string, "Average") + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string, "Average") + cooldown_window_minutes = number + change_count_by = optional(number, 1) + }) + })), []) + })) + validation { # profiles count + condition = length(var.autoscaling_profiles) <= 20 + error_message = "Azure supports up to 20 autoscaling profiles." + } + validation { + condition = length(var.autoscaling_profiles) > 0 ? var.autoscaling_profiles[0].recurrence == null : true + error_message = "The `autoscaling_profiles->recurrence` property is not allowed in the 1st profile definition." + } + validation { # recurrence + condition = length(var.autoscaling_profiles) > 0 ? alltrue([ + for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : v.recurrence != null + ]) : true + error_message = "The `autoscaling_profiles->recurrence` property is required in all profiles except the 1st one." + } + validation { # recurrence.days + condition = length(var.autoscaling_profiles) > 0 ? alltrue(flatten( + [for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : + [for day in v.recurrence.days : + contains( + ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], + day + ) + ] + ] + )) : true + error_message = "The `autoscaling_profiles->recurrence.days` property can be one of: `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday` or `Sunday`." + } + validation { # recurrence.start_time + condition = length(var.autoscaling_profiles) > 0 ? alltrue([ + for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : + can(regex("^(([0,1][0-9])|(2[0-3])):([0-5][0-9])$", v.recurrence.start_time)) + ]) : true + error_message = "The `autoscaling_profiles->recurrence.start_time` property has to be a time in RFC3339 format." + } + validation { # recurrence.end_time + condition = length(var.autoscaling_profiles) > 0 ? alltrue([ + for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : + can(regex("^(([0,1][0-9])|(2[0-3])):([0-5][0-9])$", v.recurrence.end_time)) + ]) : true + error_message = "The `autoscaling_profiles->recurrence.end_time` property has to be a time in RFC3339 format." + } + validation { # scale_rules count + condition = alltrue([for profile in var.autoscaling_profiles : length(profile.scale_rules) <= 10]) + error_message = "Azure supports up to 10 scale rules per autoscaling profile." + } + validation { # scale_rule->operator + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + contains([">", ">=", "<", "<=", "==", "!="], rule[config].operator) + ] + ] + ])) + error_message = "The `operator` property can be one of: `>`, `>=`, `<`, `<=`, `==` or `!=`." + } + validation { # scale_rule->grain_window_minutes + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + rule[config].grain_window_minutes >= 1 && rule[config].grain_window_minutes <= 720 + if rule[config].grain_window_minutes != null + ] + ] + ])) + error_message = "The `grain_window_minutes` property has to be between 1 minute and 12 hours." + } + validation { # scale_rule->grain_aggregation_type + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + contains(["Average", "Max", "Min", "Sum"], rule[config].grain_aggregation_type) + ] + ] + ])) + error_message = "The `grain_aggregation_type` property can be one of: `Average`, `Max`, `Min` or `Sum`." + } + validation { # scale_rule->aggregation_window_minutes + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + rule[config].aggregation_window_minutes >= 5 && rule[config].aggregation_window_minutes <= 720 && rule[config].aggregation_window_minutes > rule[config].grain_window_minutes + if rule[config].aggregation_window_minutes != null + ] + ] + ])) + error_message = "The `aggregation_window_minutes` property has to be between 5 minute and 12 hours and should be longer than `grain_window_minutes`." + } + validation { # scale_rule->aggregation_window_type + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + contains(["Average", "Maximum", "Minimum", "Total", "Count", "Last"], rule[config].aggregation_window_type) + ] + ] + ])) + error_message = "The `aggregation_window_type` property can be one of: `Average`, `Maximum`, `Minimum`, `Count`, `Last` or `Total`." + } + validation { # scale_rule->cooldown_window_minutes + condition = alltrue(flatten([ + for profile in var.autoscaling_profiles : [ + for rule in profile.scale_rules : [ + for config in ["scale_out_config", "scale_in_config"] : + rule[config].cooldown_window_minutes >= 1 && rule[config].cooldown_window_minutes <= 10080 + ] + ] + ])) + error_message = "The `cooldown_window_minutes` property has to be between 1 minute and 1 week." } - ``` - - Other possible metrics include panSessionActive, panSessionThroughputKbps, panSessionThroughputPps, DataPlanePacketBufferUtilization. - EOF - default = {} - type = map(any) -} - -variable "scaleout_statistic" { - description = "Aggregation to use within each minute (the time grain) for metrics coming from different virtual machines. Possible values are Average, Min and Max." - default = "Max" - type = string - nullable = false -} - -variable "scaleout_time_aggregation" { - description = "Specifies how the metric should be combined over the time `scaleout_window_minutes`. Possible values are Average, Count, Maximum, Minimum, Last and Total." - default = "Maximum" - type = string - nullable = false -} - -variable "scaleout_window_minutes" { - description = <<-EOF - This is amount of time in minutes that autoscale engine will look back for metrics. For example, 10 minutes means that every time autoscale runs, - it will query metrics for the past 10 minutes. This allows metrics to stabilize and avoids reacting to transient spikes. - Must be between 5 and 720 minutes. - EOF - default = 10 - type = number - nullable = false -} - -variable "scaleout_cooldown_minutes" { - description = "Azure only considers adding a VM after this number of minutes has passed since the last VM scaling action. It should be much higher than `scaleout_window_minutes`, to account both for the VM-Series spin-up time and for the subsequent metrics stabilization time. Must be between 1 and 10080 minutes." - default = 25 - type = number - nullable = false -} - -variable "scalein_statistic" { - description = "Aggregation to use within each minute (the time grain) for metrics coming from different virtual machines. Possible values are Average, Min and Max." - default = "Max" - type = string - nullable = false -} - -variable "scalein_time_aggregation" { - description = "Specifies how the metric should be combined over the time `scalein_window_minutes`. Possible values are Average, Count, Maximum, Minimum, Last and Total." - default = "Maximum" - type = string - nullable = false -} - -variable "scalein_window_minutes" { - description = <<-EOF - This is amount of time in minutes that autoscale engine will look back for metrics. For example, 10 minutes means that every time autoscale runs, - it will query metrics for the past 10 minutes. This allows metrics to stabilize and avoids reacting to transient spikes. - Must be between 5 and 720 minutes. - EOF - default = 15 - type = number - nullable = false -} - -variable "scalein_cooldown_minutes" { - description = "Azure only considers deleting a VM after this number of minutes has passed since the last VM scaling action. Should be higher or equal to `scalein_window_minutes`. Must be between 1 and 10080 minutes." - default = 2880 - type = number - nullable = false -} - -variable "tags" { - description = "Map of tags to use for all the created resources." - default = {} - type = map(string) -} - -variable "bootstrap_options" { - description = <<-EOF - Bootstrap options to pass to VM-Series instance. - - Proper syntax is a string of semicolon separated properties. - Example: - bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" - - For more details on bootstrapping see documentation: https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components - EOF - default = "" - type = string - sensitive = true } - -variable "diagnostics_storage_uri" { - description = "The storage account's blob endpoint to hold diagnostic files." - default = null - type = string -} \ No newline at end of file diff --git a/modules/vmss/versions.tf b/modules/vmss/versions.tf index 501042ff..9abec711 100644 --- a/modules/vmss/versions.tf +++ b/modules/vmss/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } From 549633b9e32d0673925d6feb86756f0d6487eed3 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:18:12 +0100 Subject: [PATCH 16/49] refactor(module/panorama)!: module refactor and adjusted examples (#373) As discussed with @FoSix --- examples/standalone_panorama/example.tfvars | 29 +- examples/standalone_panorama/main.tf | 66 ++- examples/standalone_panorama/outputs.tf | 4 +- examples/standalone_panorama/variables.tf | 194 +++++---- modules/panorama/.header.md | 18 + modules/panorama/README.md | 443 ++++++++++++++++---- modules/panorama/main.tf | 150 +++---- modules/panorama/outputs.tf | 5 +- modules/panorama/variables.tf | 343 +++++++++------ modules/panorama/versions.tf | 6 +- 10 files changed, 859 insertions(+), 399 deletions(-) create mode 100644 modules/panorama/.header.md diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 16486232..2693c794 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -26,7 +26,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances source_port_range = "*" destination_address_prefix = "10.1.0.0/24" destination_port_ranges = ["22", "443"] @@ -44,20 +44,37 @@ vnets = { } } - -panorama_version = "10.2.3" +# --- PANORAMA PART --- # panoramas = { "pn-1" = { - name = "panorama01" - vnet_key = "vnet" + name = "panorama01" + authentication = { + disable_password_authentication = false + #ssh_keys = ["~/.ssh/id_rsa.pub"] + } + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "vnet" + size = "Standard_D5_v2" + zone = null + disk_name = "panorama-os-disk" + } interfaces = [ { name = "management" subnet_key = "panorama" private_ip_address = "10.1.0.10" - create_pip = true + create_public_ip = true }, ] + logging_disks = { + "datadisk1" = { + name = "data-disk1" + lun = "0" + } + } } } diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index 889a294a..9b8992a4 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -1,6 +1,8 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.panoramas : v.authentication.password == null + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,7 +13,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) + authentication = { + for k, v in var.panoramas : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } } # Create or source the Resource Group. @@ -56,35 +67,50 @@ module "vnet" { tags = var.tags } +# Create Panorama virtual appliance + +resource "azurerm_availability_set" "this" { + for_each = var.availability_sets + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count + + tags = var.tags +} + module "panorama" { source = "../../modules/panorama" for_each = var.panoramas name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name location = var.location - avzone = try(each.value.avzone, null) - avzones = try(each.value.avzones, ["1", "2", "3"]) - enable_zones = var.enable_zones - custom_image_id = try(each.value.custom_image_id, null) - panorama_sku = var.panorama_sku - panorama_size = try(each.value.size, var.panorama_size) - panorama_version = try(each.value.version, var.panorama_version) + resource_group_name = local.resource_group.name + + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + } + ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${each.value.name}-${v.name}" - subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) - create_public_ip = try(v.create_pip, 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) + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${each.value.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address }] - logging_disks = try(each.value.data_disks, {}) - - username = var.vmseries_username - password = local.vmseries_password + logging_disks = { for k, v in each.value.logging_disks : + k => merge(v, { name = "${var.name_prefix}${coalesce(v.name, "${each.value.name}-osdisk")}" }) } tags = var.tags depends_on = [module.vnet] diff --git a/examples/standalone_panorama/outputs.tf b/examples/standalone_panorama/outputs.tf index 6a646dfc..08ab57b6 100644 --- a/examples/standalone_panorama/outputs.tf +++ b/examples/standalone_panorama/outputs.tf @@ -1,11 +1,11 @@ output "username" { description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username + value = { for k, v in local.authentication : k => v.username } } output "password" { description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password + value = { for k, v in local.authentication : k => v.password } sensitive = true } diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 96c9021e..571792d0 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -20,7 +20,8 @@ variable "name_prefix" { 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. + **Note!** \ + 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 @@ -47,7 +48,6 @@ variable "enable_zones" { } - ### VNET variable "vnets" { description = <<-EOF @@ -72,7 +72,6 @@ variable "vnets" { - `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 resource_group_name = optional(string) @@ -119,81 +118,136 @@ variable "vnets" { ### PANORAMA -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} +variable "availability_sets" { + description = <<-EOF + A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} + Following properties are supported: + - `name` - name of the Application Insights. + - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). + - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -variable "panorama_version" { - description = "Panorama PanOS version. It's also possible to specify the Pan-OS version per Panorama (in case you would like to deploy more than one), see `var.panoramas` variable." - type = string -} -variable "panorama_sku" { - description = "Panorama SKU, basically a type of licensing used in Azure." - default = "byol" - type = string -} -variable "panorama_size" { - description = "A size of a VM that will run Panorama. It's also possible to specify the the VM size per Panorama, see `var.panoramas` variable." - default = "Standard_D5_v2" - type = string + Please keep in mind that Azure defaults are not working for each region (especially small ones, w/o any Availability Zones). + Please verify how many update and fault domains are supported in a region before deploying this resource. + EOF + default = {} + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) } + variable "panoramas" { description = <<-EOF - A map containing Panorama definitions. + A map defining Azure Virtual Machine based on Palo Alto Networks Panorama image. - All definitions share a VM size, SKU and PanOS version (`panorama_size`, `panorama_sku`, `panorama_version` respectively). Defining more than one Panorama makes sense when creating for example HA pairs. - - Following properties are available: - - - `name` : a name of a Panorama VM - - `size` : size of the Panorama virtual machine, when specified overrides `var.panorama_size`. - - `version` : PanOS version, when specified overrides `var.panorama_version`. - - `vnet_key`: a VNET used to host Panorama VM, this is a key from a VNET definition stored in `vnets` variable - - `subnet_key`: a Subnet inside a VNET used to host Panorama VM, this is a key from a Subnet definition stored inside a VNET definition references by the `vnet_key` property - - `avzone`: when `enable_zones` is `true` this specifies the zone in which Panorama will be deployed - - `avzones`: when `enable_zones` is `true` these are availability zones used by Panorama's public IPs - - `custom_image_id`: a custom build of Panorama to use, overrides the stock image version. - - - `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name`: string that will form the NIC name - - `subnet_key` : (string) a key of a subnet as defined in `var.vnets` - - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false` - - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface - - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name` - - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used) - - - `logging_disks` : a map containing configuration of additional disks that should be attached to a Panorama appliance. Following properties are available: - - `size` : size of a disk, 2TB by default - - `lun` : slot to which the disk should be attached - - `disk_type` : type of a disk, determines throughput, `Standard_LRS` by default. + For details and defaults for available options please refer to the [`panorama`](../../modules/panorama/README.md) module. - Example: + The basic Panorama VM configuration properties are as follows: - ``` - { - "pn-1" = { - name = "panorama01" - vnet_key = "vnet" - interfaces = [ - { - name = "management" - subnet_key = "panorama" - private_ip_address = "10.1.0.10" - create_pip = true - }, - ] - } - } - ``` + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + Following properties are available: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. + + Following properties are available: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). + + - `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. + + Following properties are available: + + - `name` - (`string`, required) the Managed Disk name. + - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + authentication = object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + }) + interfaces = list(object({ + name = string + subnet_key = string + private_ip_address = optional(string) + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + })) + logging_disks = optional(map(object({ + name = string + size = optional(string) + lun = string + disk_type = optional(string) + })), {}) + })) } diff --git a/modules/panorama/.header.md b/modules/panorama/.header.md new file mode 100644 index 00000000..dcbbf287 --- /dev/null +++ b/modules/panorama/.header.md @@ -0,0 +1,18 @@ +# Palo Alto Networks Panorama Module for Azure + +A terraform module for deploying a working Panorama instance in Azure. + +## Usage + +For usage please refer to `standalone_panorama` reference architecture example. + +## Accept Azure Marketplace Terms + +Accept the Azure Marketplace terms for the Panorama images. In a typical situation use these commands: + +```sh +az vm image terms accept --publisher paloaltonetworks --offer panorama --plan byol --subscription MySubscription +``` + +You can revoke the acceptance later with the `az vm image terms cancel` command. +The acceptance applies to the entirety of your Azure Subscription. \ No newline at end of file diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 1fa7b8e5..388faab0 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -1,9 +1,12 @@ +<!-- BEGIN_TF_DOCS --> # Palo Alto Networks Panorama Module for Azure A terraform module for deploying a working Panorama instance in Azure. ## Usage +For usage please refer to `standalone_panorama` reference architecture example. + ## Accept Azure Marketplace Terms Accept the Azure Marketplace terms for the Panorama images. In a typical situation use these commands: @@ -15,98 +18,358 @@ az vm image terms accept --publisher paloaltonetworks --offer panorama --plan by You can revoke the acceptance later with the `az vm image terms cancel` command. The acceptance applies to the entirety of your Azure Subscription. -## Example +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Virtual Machine. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). +[`image`](#image) | `object` | Basic Azure VM configuration. +[`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. +[`interfaces`](#interfaces) | `list` | List of the network interface specifications. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`logging_disks`](#logging_disks) | `map` | A map of objects describing the additional disks configuration. + + + +## Module's Outputs + +Name | Description +--- | --- +`mgmt_ip_address` | Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address. +`interfaces` | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.80 + + + + +Resources used in this module: + +- `linux_virtual_machine` (managed) +- `managed_disk` (managed) +- `network_interface` (managed) +- `public_ip` (managed) +- `virtual_machine_data_disk_attachment` (managed) +- `public_ip` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Virtual Machine. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### authentication + +A map defining authentication settings (including username and password). + +Following properties are available: + +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama username. +- `password` - (`string`, optional, defaults to `null`) the initial administrative Panorama password. +- `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. +- `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. + +**Important!** \ +The `password` property is required when `ssh_keys` is not specified. + +**Important!** \ +`ssh_keys` property is a list of strings, so each item should be the actual public key value. +If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. + + + +Type: ```hcl -module "panorama" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/panorama" - - panorama_name = var.panorama_name - resource_group_name = azurerm_resource_group.this.name - location = var.location - avzone = var.avzone // Optional Availability Zone number - - interface = [ // Only one interface in Panorama VM is supported - { - name = "mgmt" - subnet_id = var.subnet_id - public_ip = true - public_ip_name = "panorama" - } - ] - - panorama_size = var.panorama_size - username = var.username - password = random_password.this.result - panorama_sku = var.panorama_sku - panorama_version = var.panorama_version - boot_diagnostic_storage_uri = module.bootstrap.storage_account.primary_blob_endpoint - tags = var.tags +object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### image + +Basic Azure VM configuration. + +Following properties are available: + +- `version` - (`string`, optional, defaults to `null`) Panorama PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer panorama --all` command. +- `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image + which should be deployed. +- `offer` - (`string`, optional, defaults to `panorama`) the Azure Offer identifier corresponding to a + published image. +- `sku` - (`string`, optional, defaults to `byol`) Panorama SKU; list available with + `az vm image list -o table --all --publisher paloaltonetworks` command. +- `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Marketplace. +- `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines. + +**Important!** \ +The `custom_id` and `version` properties are mutually exclusive. + + + +Type: + +```hcl +object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "panorama") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### virtual_machine + +Firewall parameters configuration. + +This map contains basic, as well as some optional Firewall parameters. Both types contain sane defaults. +Nevertheless they should be at least reviewed to meet deployment requirements. + +List of either required or important properties: + +- `size` - (`string`, optional, defaults to `Standard_D5_v2`) Azure VM size (type). Consult the *Panorama Deployment + Guide* as only a few selected sizes are supported. +- `zone` - (`number`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. +- `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). +- `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. + +List of other, optional properties: + +- `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use. +- `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk. +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. +- `encryption_at_host_enabled` - (`bool`, optional, defaults to `false`) should all the disks be encrypted by enabling + Encryption at Host. +- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files. +- `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". +- `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". + + + +Type: + +```hcl +object({ + size = optional(string, "Standard_D5_v2") + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + allow_extension_operations = optional(bool, false) + encryption_at_host_enabled = optional(bool, false) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### interfaces + +List of the network interface specifications. + +**Note!** \ +The ORDER in which you specify the interfaces DOES MATTER. + +Interfaces will be attached to VM in the order you define here, therefore: + +- The first should be the management interface, which does not participate in data filtering. +- The remaining ones are the dataplane interfaces. + +Following configuration options are available: + +- `name` - (`string`, required) the interface name. +- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. +- `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When + skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not + to change as long as the VM is running. Any stop/deallocate/restart operation might cause + the IP to change. +- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface. +- `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the + interface. When `create_public_ip` is set to `true` this will become a name of a newly + created Public IP interface. Otherwise this is a name of an existing interfaces that will + be sourced and attached to the interface. +- `public_ip_resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that + contains public IP that that will be associated with the interface. Used only when + `create_public_ip` is `false`. + +Example: + +```hcl +[ + # management interface with a new public IP + { + name = "pano-mgmt" + subnet_id = azurerm_subnet.my_mgmt_subnet.id + public_ip_name = "pano-mgmt-pip" + create_public_ip = true + }, + # public interface reusing an existing public IP resource + { + name = "pano-public" + subnet_id = azurerm_subnet.my_pub_subnet.id + create_public_ip = false + public_ip_name = "pano-public-pip" + }, +] +``` + + + +Type: + +```hcl +list(object({ + name = string + subnet_id = string + private_ip_address = optional(string) + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(any) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + + +#### logging_disks + + A map of objects describing the additional disks configuration. + +Following configuration options are available: + +- `name` - (`string`, required) the Managed Disk name. +- `size` - (`string`, optional, defaults to "2048") size of the disk in GB. The recommended size for additional disks + is at least 2TB (2048 GB). +- `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. +- `disk_type` - (`string`, optional, defaults to "StandardSSD_LRS") type of Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS`, `Premium_LRS` or `UltraSSD_LRS`. + +Example: + +```hcl +{ + logs-1 = { + size: "2048" + zone: "1" + lun: "1" + } + logs-2 = { + size: "2048" + zone: "2" + lun: "2" + disk_type: "StandardSSD_LRS" + } } ``` + + + +Type: + +```hcl +map(object({ + name = string + size = optional(string, "2048") + lun = string + disk_type = optional(string, "StandardSSD_LRS") + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.1 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_managed_disk.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk) | resource | -| [azurerm_network_interface.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_virtual_machine.panorama](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine) | resource | -| [azurerm_virtual_machine_data_disk_attachment.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | 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 | -|------|-------------|------|---------|:--------:| -| <a name="input_location"></a> [location](#input\_location) | Region to deploy Panorama into. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If false, the input `avzone` is ignored and all created public IPs default 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 | -| <a name="input_avzone"></a> [avzone](#input\_avzone) | The availability zone to use, for example "1", "2", "3". Ignored if `enable_zones` is false. Use `avzone = null` to disable the use of Availability Zones. | `any` | `null` | no | -| <a name="input_avzones"></a> [avzones](#input\_avzones) | After provider version 3.x you need to specify in which availability zone(s) you want to place IP.<br>ie: for zone-redundant with 3 availability zone in current region value will be:<pre>["1","2","3"]</pre> | `list(string)` | `[]` | no | -| <a name="input_name"></a> [name](#input\_name) | The Panorama common name. | `string` | n/a | yes | -| <a name="input_os_disk_name"></a> [os\_disk\_name](#input\_os\_disk\_name) | The name of OS disk. The name is auto-generated when not provided. | `string` | `null` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | The name of the existing resource group where to place all the resources created by this module. | `string` | n/a | yes | -| <a name="input_panorama_size"></a> [panorama\_size](#input\_panorama\_size) | Virtual Machine size. | `string` | `"Standard_D5_v2"` | no | -| <a name="input_username"></a> [username](#input\_username) | Initial administrative username to use for Panorama. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm). | `string` | `"panadmin"` | no | -| <a name="input_password"></a> [password](#input\_password) | Initial administrative password to use for Panorama. If not defined the `ssh_key` variable must be specified. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm). | `string` | `null` | no | -| <a name="input_ssh_keys"></a> [ssh\_keys](#input\_ssh\_keys) | A list of initial administrative SSH public keys that allow key-pair authentication.<br><br>This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available:<pre>[<br> file("/path/to/public/keys/key_1.pub"),<br> file("/path/to/public/keys/key_2.pub")<br>]</pre>If the `password` variable is also set, VM-Series will accept both authentication methods. | `list(string)` | `[]` | no | -| <a name="input_enable_plan"></a> [enable\_plan](#input\_enable\_plan) | Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku "byol", which means "bring your own license", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image. | `bool` | `true` | no | -| <a name="input_panorama_disk_type"></a> [panorama\_disk\_type](#input\_panorama\_disk\_type) | Specifies the type of managed disk to create. Possible values are either Standard\_LRS, StandardSSD\_LRS, Premium\_LRS or UltraSSD\_LRS. | `string` | `"StandardSSD_LRS"` | no | -| <a name="input_panorama_sku"></a> [panorama\_sku](#input\_panorama\_sku) | Panorama SKU. | `string` | `"byol"` | no | -| <a name="input_panorama_version"></a> [panorama\_version](#input\_panorama\_version) | Panorama PAN-OS Software version. List published images with `az vm image list -o table --all --publisher paloaltonetworks --offer panorama` | `string` | `"10.0.3"` | no | -| <a name="input_panorama_publisher"></a> [panorama\_publisher](#input\_panorama\_publisher) | Panorama Publisher. | `string` | `"paloaltonetworks"` | no | -| <a name="input_panorama_offer"></a> [panorama\_offer](#input\_panorama\_offer) | Panorama offer. | `string` | `"panorama"` | no | -| <a name="input_custom_image_id"></a> [custom\_image\_id](#input\_custom\_image\_id) | Absolute ID of your own Custom Image to be used for creating Panorama. If set, the `username`, `password`, `panorama_version`, `panorama_publisher`, `panorama_offer`, `panorama_sku` inputs are all ignored (these are used only for published images, not custom ones). The Custom Image is expected to contain PAN-OS software. | `string` | `null` | no | -| <a name="input_interfaces"></a> [interfaces](#input\_interfaces) | List of the network interface specifications.<br><br>NOTICE. The ORDER in which you specify the interfaces DOES MATTER.<br>Interfaces will be attached to VM in the order you define here, therefore the first should be the management interface.<br><br>Options for an interface object:<br>- `name` - (required\|string) Interface name.<br>- `subnet_id` - (required\|string) Identifier of an existing subnet to create interface in.<br>- `create_public_ip` - (optional\|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. Default is false.<br>- `private_ip_address` - (optional\|string) Static private IP to asssign to the interface. If null, dynamic one is allocated.<br>- `public_ip_name` - (optional\|string) Name of an existing public IP to associate to the interface, used only when `create_public_ip` is `false`.<br>- `public_ip_resource_group` - (optional\|string) Name of a Resource Group that contains public IP resource to associate to the interface. When not specified defaults to `var.resource_group_name`. Used only when `create_public_ip` is `false`.<br><br>Example:<pre>[<br> {<br> name = "mgmt"<br> subnet_id = azurerm_subnet.my_mgmt_subnet.id<br> public_ip_address_id = azurerm_public_ip.my_mgmt_ip.id<br> create_public_ip = true<br> }<br>]</pre> | `list(any)` | n/a | yes | -| <a name="input_logging_disks"></a> [logging\_disks](#input\_logging\_disks) | A map of objects describing the additional disk configuration. The keys of the map are the names and values are { size, zone, lun }. <br> The size value is provided in GB. The recommended size for additional (optional) disks is at least 2TB (2048 GB). Example:<pre>{<br> logs-1 = {<br> size: "2048"<br> zone: "1"<br> lun: "1"<br> }<br> logs-2 = {<br> size: "2048"<br> zone: "2"<br> lun: "2"<br> disk_type: "StandardSSD_LRS"<br> }<br>}</pre> | `map(any)` | `{}` | no | -| <a name="input_boot_diagnostic_storage_uri"></a> [boot\_diagnostic\_storage\_uri](#input\_boot\_diagnostic\_storage\_uri) | Existing diagnostic storage uri | `string` | `null` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to be associated with the resources created. | `map(any)` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_mgmt_ip_address"></a> [mgmt\_ip\_address](#output\_mgmt\_ip\_address) | Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address. | -| <a name="output_interfaces"></a> [interfaces](#output\_interfaces) | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/main.tf b/modules/panorama/main.tf index 12d0e0d8..c653765f 100644 --- a/modules/panorama/main.tf +++ b/modules/panorama/main.tf @@ -1,31 +1,29 @@ -# Create a public IP for management +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - for_each = { for v in var.interfaces : v.name => v if try(v.create_public_ip, false) } + for_each = { for v in var.interfaces : v.name => v if v.create_public_ip } location = var.location resource_group_name = var.resource_group_name - name = "${each.value.name}-pip" + name = each.value.public_ip_name allocation_method = "Static" sku = "Standard" - zones = var.enable_zones ? var.avzones : null - - tags = var.tags + zones = var.virtual_machine.zone != null ? [var.virtual_machine.zone] : null + tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "this" { - for_each = { for v in var.interfaces : v.name => v - if(!try(v.create_public_ip, false) && try(v.public_ip_name, null) != null) - } + for_each = { for v in var.interfaces : v.name => v if !v.create_public_ip && v.public_ip_name != null } name = each.value.public_ip_name - resource_group_name = try(each.value.public_ip_resource_group, null) != null ? each.value.public_ip_resource_group : var.resource_group_name + resource_group_name = coalesce(each.value.public_ip_resource_group_name, var.resource_group_name) } -# Build Panorama interface +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface resource "azurerm_network_interface" "this" { for_each = { for k, v in var.interfaces : v.name => merge(v, { index = k }) } - name = "${each.value.name}-nic" + name = each.value.name location = var.location resource_group_name = var.resource_group_name enable_accelerated_networking = false @@ -35,98 +33,106 @@ resource "azurerm_network_interface" "this" { ip_configuration { name = "primary" subnet_id = each.value.subnet_id - private_ip_address_allocation = try(each.value.private_ip_address, null) != null ? "Static" : "Dynamic" - private_ip_address = try(each.value.private_ip_address, null) - public_ip_address_id = try(azurerm_public_ip.this[each.value.name].id, data.azurerm_public_ip.this[each.value.name].id, null) + private_ip_address_allocation = each.value.private_ip_address != null ? "Static" : "Dynamic" + private_ip_address = each.value.private_ip_address + public_ip_address_id = try( + azurerm_public_ip.this[each.value.name].id, data.azurerm_public_ip.this[each.value.name].id, null + ) } } -# Build the Panorama VM -resource "azurerm_virtual_machine" "panorama" { - name = var.name - location = var.location - resource_group_name = var.resource_group_name - primary_network_interface_id = azurerm_network_interface.this[var.interfaces[0].name].id - vm_size = var.panorama_size - network_interface_ids = [for v in var.interfaces : azurerm_network_interface.this[v.name].id] - - delete_os_disk_on_termination = true - delete_data_disks_on_termination = true - - storage_image_reference { - id = var.custom_image_id - publisher = var.custom_image_id == null ? var.panorama_publisher : null - offer = var.custom_image_id == null ? var.panorama_offer : null - sku = var.custom_image_id == null ? var.panorama_sku : null - version = var.custom_image_id == null ? var.panorama_version : null - } +locals { + password = sensitive(var.authentication.password) +} - storage_os_disk { - name = coalesce(var.os_disk_name, "${var.name}-disk") - caching = "ReadWrite" - create_option = "FromImage" - managed_disk_type = var.panorama_disk_type - } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine +resource "azurerm_linux_virtual_machine" "this" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags - os_profile { - computer_name = var.name - admin_username = var.username - admin_password = var.password - } + size = var.virtual_machine.size + zone = var.virtual_machine.zone + availability_set_id = var.virtual_machine.avset_id + allow_extension_operations = var.virtual_machine.allow_extension_operations + encryption_at_host_enabled = var.virtual_machine.encryption_at_host_enabled + + network_interface_ids = [for v in var.interfaces : azurerm_network_interface.this[v.name].id] - dynamic "boot_diagnostics" { - for_each = var.boot_diagnostic_storage_uri != null ? [1] : [] + admin_username = var.authentication.username + admin_password = var.authentication.disable_password_authentication ? null : local.password + disable_password_authentication = var.authentication.disable_password_authentication + + dynamic "admin_ssh_key" { + for_each = { for k, v in var.authentication.ssh_keys : k => v } content { - enabled = true - storage_uri = var.boot_diagnostic_storage_uri + username = var.authentication.username + public_key = admin_ssh_key.value } } - os_profile_linux_config { - disable_password_authentication = var.password == null ? true : false - dynamic "ssh_keys" { - for_each = var.ssh_keys - content { - key_data = ssh_keys.value - path = "/home/${var.username}/.ssh/authorized_keys" - } + os_disk { + name = var.virtual_machine.disk_name + storage_account_type = var.virtual_machine.disk_type + caching = "ReadWrite" + disk_encryption_set_id = var.virtual_machine.disk_encryption_set_id + } + + source_image_id = var.image.custom_id + + dynamic "source_image_reference" { + for_each = var.image.custom_id == null ? [1] : [] + content { + publisher = var.image.publisher + offer = var.image.offer + sku = var.image.sku + version = var.image.version } } dynamic "plan" { - for_each = var.enable_plan ? ["one"] : [] + for_each = var.image.enable_marketplace_plan ? [1] : [] content { - name = var.panorama_sku - publisher = var.panorama_publisher - product = var.panorama_offer + name = var.image.sku + publisher = var.image.publisher + product = var.image.offer } } - zones = var.enable_zones && var.avzone != null && var.avzone != "" ? [var.avzone] : null - tags = var.tags + + # After converting to azurerm_linux_virtual_machine, an empty block boot_diagnostics {} will use managed storage. Want. + # 2.36 in required_providers per https://github.com/terraform-providers/terraform-provider-azurerm/pull/8917 + boot_diagnostics { + storage_account_uri = var.virtual_machine.diagnostics_storage_uri + } + + identity { + type = var.virtual_machine.identity_type + identity_ids = var.virtual_machine.identity_ids + } } -# Panorama managed disk +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk resource "azurerm_managed_disk" "this" { for_each = var.logging_disks - name = "${var.name}-disk-${each.key}" + name = each.value.name location = var.location resource_group_name = var.resource_group_name - storage_account_type = try(each.value.disk_type, "Standard_LRS") + storage_account_type = each.value.disk_type create_option = "Empty" - disk_size_gb = try(each.value.size, "2048") - zone = var.enable_zones ? try(var.avzone, null) : null - - tags = var.tags + disk_size_gb = each.value.size + zone = var.virtual_machine.zone != null && var.virtual_machine.zone != "" ? var.virtual_machine.zone : null + tags = var.tags } -# Attach logging disk to Panorama VM +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment resource "azurerm_virtual_machine_data_disk_attachment" "this" { for_each = azurerm_managed_disk.this managed_disk_id = each.value.id - virtual_machine_id = azurerm_virtual_machine.panorama.id + virtual_machine_id = azurerm_linux_virtual_machine.this.id lun = var.logging_disks[each.key].lun caching = "ReadWrite" } diff --git a/modules/panorama/outputs.tf b/modules/panorama/outputs.tf index 87f8673c..03f2ffd5 100644 --- a/modules/panorama/outputs.tf +++ b/modules/panorama/outputs.tf @@ -1,6 +1,9 @@ output "mgmt_ip_address" { description = "Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address." - value = try(var.interfaces[0].create_public_ip, false) ? azurerm_public_ip.this[var.interfaces[0].name].ip_address : azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address + value = try( + azurerm_public_ip.this[var.interfaces[0].name].ip_address, + azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address + ) } output "interfaces" { diff --git a/modules/panorama/variables.tf b/modules/panorama/variables.tf index 399e98fc..b4ea86f6 100644 --- a/modules/panorama/variables.tf +++ b/modules/panorama/variables.tf @@ -1,170 +1,238 @@ -# Location -variable "location" { - description = "Region to deploy Panorama into." - type = string -} - -variable "enable_zones" { - description = "If false, the input `avzone` is ignored and all created public IPs default 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 "avzone" { - description = "The availability zone to use, for example \"1\", \"2\", \"3\". Ignored if `enable_zones` is false. Use `avzone = null` to disable the use of Availability Zones." - default = null -} - -variable "avzones" { - description = <<-EOF - After provider version 3.x you need to specify in which availability zone(s) you want to place IP. - ie: for zone-redundant with 3 availability zone in current region value will be: - ```["1","2","3"]``` - EOF - default = [] - type = list(string) -} - -# Naming variable "name" { - description = "The Panorama common name." + description = "The name of the Azure Virtual Machine." type = string } -variable "os_disk_name" { - description = "The name of OS disk. The name is auto-generated when not provided." - default = null - type = string -} variable "resource_group_name" { - description = "The name of the existing resource group where to place all the resources created by this module." + description = "The name of the Resource Group to use." type = string } -# Instance settings -variable "panorama_size" { - description = "Virtual Machine size." - default = "Standard_D5_v2" -} - -variable "username" { - description = "Initial administrative username to use for Panorama. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm)." - default = "panadmin" +variable "location" { + description = "The name of the Azure region to deploy the resources in." type = string } -variable "password" { - description = "Initial administrative password to use for Panorama. If not defined the `ssh_key` variable must be specified. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm)." - default = null - type = string - sensitive = true +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(any) } -variable "ssh_keys" { +variable "authentication" { description = <<-EOF - A list of initial administrative SSH public keys that allow key-pair authentication. - - This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available: + A map defining authentication settings (including username and password). + + Following properties are available: + + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama username. + - `password` - (`string`, optional, defaults to `null`) the initial administrative Panorama password. + - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. + - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. + + **Important!** \ + The `password` property is required when `ssh_keys` is not specified. + + **Important!** \ + `ssh_keys` property is a list of strings, so each item should be the actual public key value. + If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - ``` - [ - file("/path/to/public/keys/key_1.pub"), - file("/path/to/public/keys/key_2.pub") - ] - ``` - - If the `password` variable is also set, VM-Series will accept both authentication methods. EOF - default = [] - type = list(string) + type = object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + validation { + condition = var.authentication.password != null || length(var.authentication.ssh_keys) > 0 + error_message = "Either `var.authentication.password`, `var.authentication.ssh_key` or both must be set in order to have access to the device." + } } -variable "enable_plan" { - description = "Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku \"byol\", which means \"bring your own license\", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image." - default = true - type = bool -} +variable "image" { + description = <<-EOF + Basic Azure VM configuration. -variable "panorama_disk_type" { - description = "Specifies the type of managed disk to create. Possible values are either Standard_LRS, StandardSSD_LRS, Premium_LRS or UltraSSD_LRS." - default = "StandardSSD_LRS" - type = string + Following properties are available: + - `version` - (`string`, optional, defaults to `null`) Panorama PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer panorama --all` command. + - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image + which should be deployed. + - `offer` - (`string`, optional, defaults to `panorama`) the Azure Offer identifier corresponding to a + published image. + - `sku` - (`string`, optional, defaults to `byol`) Panorama SKU; list available with + `az vm image list -o table --all --publisher paloaltonetworks` command. + - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Marketplace. + - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines. + + **Important!** \ + The `custom_id` and `version` properties are mutually exclusive. + + EOF + type = object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "panorama") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) validation { - condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS"], var.panorama_disk_type) - error_message = "Panorama disk type need to be one of list Standard_LR, StandardSSD_LRS, Premium_LRS, UltraSSD_LRS." + condition = (var.image.custom_id != null && var.image.version == null + ) || (var.image.custom_id == null && var.image.version != null) + error_message = "Either `custom_id` or `version` has to be defined." } } -variable "panorama_sku" { - description = "Panorama SKU." - default = "byol" - type = string -} +variable "virtual_machine" { + description = <<-EOF + Firewall parameters configuration. -variable "panorama_version" { - description = "Panorama PAN-OS Software version. List published images with `az vm image list -o table --all --publisher paloaltonetworks --offer panorama`" - default = "10.0.3" - type = string -} + This map contains basic, as well as some optional Firewall parameters. Both types contain sane defaults. + Nevertheless they should be at least reviewed to meet deployment requirements. -variable "panorama_publisher" { - description = "Panorama Publisher." - default = "paloaltonetworks" - type = string -} + List of either required or important properties: -variable "panorama_offer" { - description = "Panorama offer." - default = "panorama" - type = string -} + - `size` - (`string`, optional, defaults to `Standard_D5_v2`) Azure VM size (type). Consult the *Panorama Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`number`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. + - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. -variable "custom_image_id" { - description = "Absolute ID of your own Custom Image to be used for creating Panorama. If set, the `username`, `password`, `panorama_version`, `panorama_publisher`, `panorama_offer`, `panorama_sku` inputs are all ignored (these are used only for published images, not custom ones). The Custom Image is expected to contain PAN-OS software." - default = null - type = string + List of other, optional properties: + + - `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use. + - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk. + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. + - `encryption_at_host_enabled` - (`bool`, optional, defaults to `false`) should all the disks be encrypted by enabling + Encryption at Host. + - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files. + - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". + - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". + + EOF + type = object({ + size = optional(string, "Standard_D5_v2") + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + allow_extension_operations = optional(bool, false) + encryption_at_host_enabled = optional(bool, false) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + }) + validation { + condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) + error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + } + validation { + condition = contains(["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type) + error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + } + validation { + condition = var.virtual_machine.identity_type == "SystemAssigned" ? length(var.virtual_machine.identity_ids) == 0 : length(var.virtual_machine.identity_ids) >= 0 + error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + } } -# Networking variable "interfaces" { description = <<-EOF List of the network interface specifications. - NOTICE. The ORDER in which you specify the interfaces DOES MATTER. - Interfaces will be attached to VM in the order you define here, therefore the first should be the management interface. + **Note!** \ + The ORDER in which you specify the interfaces DOES MATTER. + + Interfaces will be attached to VM in the order you define here, therefore: + + - The first should be the management interface, which does not participate in data filtering. + - The remaining ones are the dataplane interfaces. - Options for an interface object: - - `name` - (required|string) Interface name. - - `subnet_id` - (required|string) Identifier of an existing subnet to create interface in. - - `create_public_ip` - (optional|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. Default is false. - - `private_ip_address` - (optional|string) Static private IP to asssign to the interface. If null, dynamic one is allocated. - - `public_ip_name` - (optional|string) Name of an existing public IP to associate to the interface, used only when `create_public_ip` is `false`. - - `public_ip_resource_group` - (optional|string) Name of a Resource Group that contains public IP resource to associate to the interface. When not specified defaults to `var.resource_group_name`. Used only when `create_public_ip` is `false`. + Following configuration options are available: + + - `name` - (`string`, required) the interface name. + - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. + - `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When + skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not + to change as long as the VM is running. Any stop/deallocate/restart operation might cause + the IP to change. + - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface. + - `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the + interface. When `create_public_ip` is set to `true` this will become a name of a newly + created Public IP interface. Otherwise this is a name of an existing interfaces that will + be sourced and attached to the interface. + - `public_ip_resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that + contains public IP that that will be associated with the interface. Used only when + `create_public_ip` is `false`. Example: - ``` + ```hcl [ + # management interface with a new public IP { - name = "mgmt" - subnet_id = azurerm_subnet.my_mgmt_subnet.id - public_ip_address_id = azurerm_public_ip.my_mgmt_ip.id - create_public_ip = true - } + name = "pano-mgmt" + subnet_id = azurerm_subnet.my_mgmt_subnet.id + public_ip_name = "pano-mgmt-pip" + create_public_ip = true + }, + # public interface reusing an existing public IP resource + { + name = "pano-public" + subnet_id = azurerm_subnet.my_pub_subnet.id + create_public_ip = false + public_ip_name = "pano-public-pip" + }, ] ``` + EOF - type = list(any) + type = list(object({ + name = string + subnet_id = string + private_ip_address = optional(string) + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + })) + validation { + condition = alltrue([ + for v in var.interfaces : v.public_ip_name != null + if v.create_public_ip + ]) + error_message = "The `public_ip_name` property is required when `create_public_ip` is `true`." + } } # Storage variable "logging_disks" { description = <<-EOF - A map of objects describing the additional disk configuration. The keys of the map are the names and values are { size, zone, lun }. - The size value is provided in GB. The recommended size for additional (optional) disks is at least 2TB (2048 GB). Example: + A map of objects describing the additional disks configuration. + + Following configuration options are available: + + - `name` - (`string`, required) the Managed Disk name. + - `size` - (`string`, optional, defaults to "2048") size of the disk in GB. The recommended size for additional disks + is at least 2TB (2048 GB). + - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. + - `disk_type` - (`string`, optional, defaults to "StandardSSD_LRS") type of Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS`, `Premium_LRS` or `UltraSSD_LRS`. + + Example: - ``` + ```hcl { logs-1 = { size: "2048" @@ -179,21 +247,30 @@ variable "logging_disks" { } } ``` - + EOF default = {} - type = map(any) -} - - -variable "boot_diagnostic_storage_uri" { - description = "Existing diagnostic storage uri" - default = null - type = string -} - -variable "tags" { - description = "A map of tags to be associated with the resources created." - default = {} - type = map(any) + nullable = false + type = map(object({ + name = string + size = optional(string, "2048") + lun = string + disk_type = optional(string, "StandardSSD_LRS") + })) + validation { + condition = alltrue([for _, v in var.logging_disks : contains(range(2048, 24577, 2048), parseint(v.size, 10))]) + error_message = "The `size` property value must be a multiple of `2048` but not higher than `24576` (24 TB)." + } + validation { + condition = alltrue([ + for _, v in var.logging_disks : (parseint(v.lun, 10) >= 0 && parseint(v.lun, 10) <= 63) if v.lun != null + ]) + error_message = "The `lun` property value must be a number between `0` and `63`." + } + validation { + condition = alltrue([ + for _, v in var.logging_disks : contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS"], v.disk_type) + ]) + error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS`, `Premium_LRS` or `UltraSSD_LRS`." + } } diff --git a/modules/panorama/versions.tf b/modules/panorama/versions.tf index 1611a7bf..9abec711 100644 --- a/modules/panorama/versions.tf +++ b/modules/panorama/versions.tf @@ -3,11 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" - } - random = { - source = "hashicorp/random" - version = "~> 3.1" + version = "~> 3.80" } } } From ad1c4d7ff2cad59a59cb1fe3bf820e65a2586b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:11:25 +0100 Subject: [PATCH 17/49] refactor: bootstrap and vmseries (#371) --- .gitignore | 4 +- examples/common_vmseries/.header.md | 167 +++ examples/common_vmseries/README.md | 1151 ++++++++++++++-- examples/common_vmseries/example.tfvars | 123 +- examples/common_vmseries/main.tf | 199 +-- examples/common_vmseries/outputs.tf | 12 +- examples/common_vmseries/variables.tf | 409 ++++-- .../common_vmseries_and_autoscale/README.md | 14 +- .../variables.tf | 42 +- examples/dedicated_vmseries/.header.md | 171 +++ examples/dedicated_vmseries/README.md | 1163 ++++++++++++++--- .../bootstrap_package/software/10.2.4.img | 1 - examples/dedicated_vmseries/example.tfvars | 193 +-- examples/dedicated_vmseries/main.tf | 203 +-- examples/dedicated_vmseries/main_test.go | 5 +- examples/dedicated_vmseries/outputs.tf | 12 +- examples/dedicated_vmseries/variables.tf | 415 ++++-- .../README.md | 14 +- .../variables.tf | 42 +- examples/gwlb_with_vmseries/README.md | 4 +- examples/gwlb_with_vmseries/example.tfvars | 2 +- examples/gwlb_with_vmseries/main.tf | 18 +- examples/gwlb_with_vmseries/variables.tf | 16 +- examples/natgw/example.tfvars | 62 - examples/natgw/main.tf | 61 - examples/natgw/variables.tf | 176 --- examples/natgw/versions.tf | 22 - examples/standalone_panorama/README.md | 4 +- examples/standalone_vmseries/.header.md | 111 ++ examples/standalone_vmseries/README.md | 1080 +++++++++++++-- examples/standalone_vmseries/example.tfvars | 24 +- examples/standalone_vmseries/main.tf | 203 +-- examples/standalone_vmseries/outputs.tf | 12 +- examples/standalone_vmseries/variables.tf | 415 ++++-- examples/virtual_network_gateway/.header.md | 5 - examples/virtual_network_gateway/README.md | 297 ----- .../virtual_network_gateway/example.tfvars | 127 -- examples/virtual_network_gateway/main.tf | 77 -- examples/virtual_network_gateway/main_test.go | 62 - examples/virtual_network_gateway/outputs.tf | 9 - examples/virtual_network_gateway/variables.tf | 192 --- examples/virtual_network_gateway/versions.tf | 22 - modules/appgw/README.md | 2 +- modules/appgw/main.tf | 2 +- modules/appgw/variables.tf | 2 +- modules/bootstrap/.header.md | 128 ++ modules/bootstrap/README.md | 474 +++++-- modules/bootstrap/main.tf | 211 ++- modules/bootstrap/outputs.tf | 20 +- modules/bootstrap/variables.tf | 288 ++-- modules/bootstrap/versions.tf | 4 - modules/loadbalancer/README.md | 38 +- modules/loadbalancer/variables.tf | 38 +- modules/ngfw_metrics/.header.md | 2 +- modules/ngfw_metrics/README.md | 4 +- modules/vmseries/.header.md | 32 + modules/vmseries/README.md | 408 ++++-- modules/vmseries/main.tf | 158 +-- modules/vmseries/outputs.tf | 9 +- modules/vmseries/variables.tf | 381 +++--- modules/vmss/.header.md | 8 +- modules/vmss/README.md | 20 +- modules/vmss/variables.tf | 12 +- 63 files changed, 6437 insertions(+), 3145 deletions(-) create mode 100644 examples/common_vmseries/.header.md create mode 100644 examples/dedicated_vmseries/.header.md delete mode 100644 examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img delete mode 100644 examples/natgw/example.tfvars delete mode 100644 examples/natgw/main.tf delete mode 100644 examples/natgw/variables.tf delete mode 100644 examples/natgw/versions.tf create mode 100644 examples/standalone_vmseries/.header.md delete mode 100644 examples/virtual_network_gateway/.header.md delete mode 100644 examples/virtual_network_gateway/README.md delete mode 100644 examples/virtual_network_gateway/example.tfvars delete mode 100644 examples/virtual_network_gateway/main.tf delete mode 100644 examples/virtual_network_gateway/main_test.go delete mode 100644 examples/virtual_network_gateway/outputs.tf delete mode 100644 examples/virtual_network_gateway/variables.tf delete mode 100644 examples/virtual_network_gateway/versions.tf create mode 100644 modules/bootstrap/.header.md create mode 100644 modules/vmseries/.header.md diff --git a/.gitignore b/.gitignore index ad65ccc2..13cd1eab 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,6 @@ terraform.tfvars.json *.tfplan # **/ *bootstrap.xml -examples/appgw/files/* \ No newline at end of file + +bootstrap_package/ +examples/appgw/files/* diff --git a/examples/common_vmseries/.header.md b/examples/common_vmseries/.header.md new file mode 100644 index 00000000..4caf7cbe --- /dev/null +++ b/examples/common_vmseries/.header.md @@ -0,0 +1,167 @@ +--- +short_title: Common Firewall Option +type: refarch +show_in_hub: true +--- +# Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture. Common NGFW Option + +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +common VM-Series for all traffic; for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +## Reference Architecture Design + +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) + +This code implements: + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *common option*, which routes all traffic flows onto a single set of VM-Series. + +## Detailed Architecture and Design + +### Centralized Design + +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in +a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, +outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. + +### Common Option + +The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource +and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation +that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments +because the number of firewalls low. However, the technical integration complexity is high. + +![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920647-c7dc77c1-d86c-42ac-ba5a-59a95439ef23.png) + +This reference architecture consists of: + +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic + - private - in front of the firewalls private interfaces, for outgoing and east-west traffic +- 2 firewalls: + - deployed in different zones + - with 3 network interfaces: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, + see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)). + +**NOTE!** +- after the deployment the firewalls remain not configured and not licensed +- this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain + `bootstrap_options` properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. + It's main purpose is to introduce the Terraform modules. + +## Usage + +### Deployment Steps + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs + (take a closer look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + Apply complete! Resources: 53 added, 0 changed, 0 destroyed. + + Outputs: + + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1" = "1.2.3.4" + } + } + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-1" = "1.2.3.4" + "fw-2" = "1.2.3.4" + } + ``` + +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output usernames + ``` + +- for password: + + ```bash + terraform output passwords + ``` + +The management public IP addresses are available in the `vmseries_mgmt_ips`: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +You can now proceed with licensing and configuring the devices. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration +(security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```sh +terraform destroy +``` diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index c8cb4559..8e919c2d 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -1,132 +1,163 @@ +<!-- BEGIN_TF_DOCS --> --- -short_title: Common Firewall Option +short\_title: Common Firewall Option type: refarch -show_in_hub: true +show\_in\_hub: true --- # Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture. Common NGFW Option -Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with common VM-Series for all traffic; for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +common VM-Series for all traffic; for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). ## Reference Architecture Design ![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) This code implements: -- a _centralized design_, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, east-west, and enterprise traffic -- the _common option_, which routes all traffic flows onto a single set of VM-Series + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *common option*, which routes all traffic flows onto a single set of VM-Series. ## Detailed Architecture and Design ### Centralized Design -This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in +a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, +outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. ### Common Option -The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments because the number of firewalls low. However, the technical integration complexity is high. +The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource +and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation +that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments +because the number of firewalls low. However, the technical integration complexity is high. ![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920647-c7dc77c1-d86c-42ac-ba5a-59a95439ef23.png) This reference architecture consists of: -* a VNET containing: - * 4 subnets: - * 3 of them dedicated to the firewalls: management, private and public - * one dedicated to an Application Gateway - * Route Tables and Network Security Groups -* 2 Load Balancers: - * public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic - * private - in front of the firewalls private interfaces, for outgoing and east-west traffic -* 2 firewalls: - * deployed in different zones - * with 3 network interfaces: management, public, private - * with public IP addresses assigned to: - * management interface - * public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic -* an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic + - private - in front of the firewalls private interfaces, for outgoing and east-west traffic +- 2 firewalls: + - deployed in different zones + - with 3 network interfaces: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, + see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)). -**NOTE:** - -* after the deployment the firewalls remain not configured and not licensed -* this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain bootstrap_options properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. +**NOTE!** +- after the deployment the firewalls remain not configured and not licensed +- this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain + `bootstrap_options` properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. + It's main purpose is to introduce the Terraform modules. ## Usage ### Deployment Steps -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs + (take a closer look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - Apply complete! Resources: 53 added, 0 changed, 0 destroyed. + ```console + Apply complete! Resources: 53 added, 0 changed, 0 destroyed. - Outputs: + Outputs: - lb_frontend_ips = { - "private" = { - "ha-ports" = "1.2.3.4" - } - "public" = { - "palo-lb-app1" = "1.2.3.4" - } - } - password = <sensitive> - username = "panadmin" - vmseries_mgmt_ips = { - "fw-1" = "1.2.3.4" - "fw-2" = "1.2.3.4" - } + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1" = "1.2.3.4" + } + } + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-1" = "1.2.3.4" + "fw-2" = "1.2.3.4" + } + ``` -* at this stage you have to wait couple of minutes for the firewalls to bootstrap. +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. ### Post deploy Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output usernames + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output passwords + ``` The management public IP addresses are available in the `vmseries_mgmt_ips`: -```sh +```bash terraform output vmseries_mgmt_ips ``` You can now login to the devices using either: -* cli - ssh client is required -* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. You can now proceed with licensing and configuring the devices. -Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration (security hardening). +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration +(security hardening). ### Cleanup @@ -136,80 +167,918 @@ To remove the deployed infrastructure run: terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_http"></a> [http](#provider\_http) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | -| <a name="provider_local"></a> [local](#provider\_local) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_natgw"></a> [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_bootstrap"></a> [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| <a name="module_bootstrap_share"></a> [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| <a name="module_vmseries"></a> [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | -| <a name="module_appgw"></a> [appgw](#module\_appgw) | ../../modules/appgw | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_availability_set.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [local_file.bootstrap_xml](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [http_http.this](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_availability_sets"></a> [availability\_sets](#input\_availability\_sets) | A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used.<br><br>Following properties are supported:<br>- `name` - name of the Application Insights.<br>- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults).<br>- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults).<br><br>Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. | `any` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_bootstrap_storage"></a> [bootstrap\_storage](#input\_bootstrap\_storage) | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property.<br><br>Following properties are supported (except for name, all are optional):<br><br>- `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`.<br>- `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account.<br>- `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist.<br>- `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`.<br>- `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly.<br>- `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account.<br><br>The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence:<br>- `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br>- `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br>- `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br>- `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). | `any` | `{}` | no | -| <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported:<br><br>- `name` : name of the VMSeries virtual machine.<br>- `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PanOS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation.<br>- `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway.<br>- `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1".<br>- `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment.<br><br>- `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage`<br>- `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported:<br> - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account<br> - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package<br> - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below.<br> - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br> - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br> - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br> - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes).<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>Example:<pre>{<br> "fw01" = {<br> name = "firewall01"<br> bootstrap_storage = {<br> name = "storageaccountname"<br> static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" }<br> template_bootstrap_xml = "templates/bootstrap_common.tmpl"<br> public_snet_key = "public"<br> private_snet_key = "private"<br> }<br> avzone = 1<br> vnet_key = "trust"<br> interfaces = [<br> {<br> name = "mgmt"<br> subnet_key = "mgmt"<br> create_pip = true<br> private_ip_address = "10.0.0.1"<br> },<br> {<br> name = "trust"<br> subnet_key = "private"<br> private_ip_address = "10.0.1.1"<br> load_balancer_key = "private_lb"<br> },<br> {<br> name = "untrust"<br> subnet_key = "public"<br> private_ip_address = "10.0.2.1"<br> load_balancer_key = "public_lb"<br> public_ip_name = "existing_public_ip"<br> }<br> ]<br> }<br>}</pre> | `any` | n/a | yes | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_natgw_public_ips"></a> [natgw\_public\_ips](#output\_natgw\_public\_ips) | Nat Gateways Public IP resources. | -| <a name="output_metrics_instrumentation_keys"></a> [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -| <a name="output_vmseries_mgmt_ips"></a> [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for the VMSeries management interface. | -| <a name="output_bootstrap_storage_urls"></a> [bootstrap\_storage\_urls](#output\_bootstrap\_storage\_urls) | n/a | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. +[`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial administrative username to use for VM-Series. +`passwords` | Initial administrative password to use for VM-Series. +`natgw_public_ips` | Nat Gateways Public IP resources. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. +`vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. +`bootstrap_storage_urls` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.2, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` +- `local` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`appgw` | - | ../../modules/appgw | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### enable_zones + +If `true`, enable zone support for resources. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### natgws + +A map defining NAT Gateways. + +Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one +explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. +For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + +Following properties are supported: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### load_balancers + +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` + + +Type: + +```hcl +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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### availability_sets + +A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + +Following properties are supported: +- `name` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). +Please verify how many update and fault domain are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### bootstrap_storages + +A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + +You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to +[module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + +- `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. +- `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. +- `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vmseries + +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + +For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + +The most basic properties are as follows: + +- `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + + +Type: + +```hcl +map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + +Following properties are supported: +- `name` - (`string`, required) name of the Application Gateway. +- `public_ip` - (`string`, required) public IP address. +- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. +- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. +- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. +- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) +- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported +- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway +- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. +- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name +- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool +- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) +- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) +- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) +- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) +- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` +- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` +- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + + +Type: + +```hcl +map(object({ + name = string + public_ip = object({ + name = string + resource_group_name = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index e74cc3fc..86769e85 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -22,7 +22,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -105,10 +105,6 @@ vnets = { network_security_group_key = "public" route_table_key = "public" } - "appgw" = { - name = "appgw-snet" - address_prefixes = ["10.0.0.48/28"] - } } } } @@ -117,10 +113,12 @@ vnets = { # --- LOAD BALANCING PART --- # load_balancers = { "public" = { - name = "public-lb" - 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 + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["0.0.0.0/0"] + } frontend_ips = { "app1" = { name = "app1" @@ -156,109 +154,66 @@ load_balancers = { } } -ngfw_metrics = { - name = "metrics" -} - - # --- VMSERIES PART --- # -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" vmseries = { "fw-1" = { - name = "firewall01" - bootstrap_options = "type=dhcp-client" - vnet_key = "transit" - avzone = 1 + name = "firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_options = "type=dhcp-client" + } interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm01-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm01-private" subnet_key = "private" load_balancer_key = "private" }, { - name = "public" + name = "vm01-public" subnet_key = "public" + create_public_ip = true load_balancer_key = "public" - create_pip = true } ] - add_to_appgw_backend = true } "fw-2" = { - name = "firewall02" - bootstrap_options = "type=dhcp-client" - vnet_key = "transit" - avzone = 2 + name = "firewall02" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_options = "type=dhcp-client" + } interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm02-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm02-private" subnet_key = "private" load_balancer_key = "private" }, { - name = "public" + name = "vm02-public" subnet_key = "public" + create_public_ip = true load_balancer_key = "public" - create_pip = true } ] - add_to_appgw_backend = true - } -} - - -# --- APPLICATION GATEWAYs --- # -appgws = { - "public" = { - name = "appgw" - public_ip = { - name = "pip" - } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 - } - listeners = { - minimum = { - name = "minimum-listener" - port = 80 - } - } - rewrites = { - minimum = { - name = "minimum-set" - rules = { - "xff-strip-port" = { - name = "minimum-xff-strip-port" - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } - } - rules = { - minimum = { - name = "minimum-rule" - priority = 1 - backend = "minimum" - listener = "minimum" - rewrite = "minimum" - } - } } } diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 07262aed..031912b2 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -1,6 +1,8 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.vmseries : v.authentication.password == null + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,14 +13,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) -} - -# Obtain Public IP address of code deployment machine - -data "http" "this" { - count = length(var.bootstrap_storage) > 0 && anytrue([for v in values(var.bootstrap_storage) : try(v.storage_acl, false)]) ? 1 : 0 - url = "https://ifconfig.me/ip" + authentication = { + for k, v in var.vmseries : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } } # Create or source the Resource Group. @@ -131,8 +135,7 @@ module "load_balancer" { - -# create the actual VMSeries VMs and resources +# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -155,39 +158,35 @@ module "ngfw_metrics" { } resource "local_file" "bootstrap_xml" { - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage.template_bootstrap_xml) } + for_each = { + for k, v in var.vmseries : + k => v.virtual_machine + if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + } - filename = "files/${each.value.name}-bootstrap.xml" + filename = "files/${each.key}-bootstrap.xml" content = templatefile( - each.value.bootstrap_storage.template_bootstrap_xml, + each.value.bootstrap_package.bootstrap_xml_template, { private_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.private_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].private_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.private_snet_key], 1 ) public_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.public_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].public_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.public_snet_key], 1 ) - ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) - - ai_update_interval = try( - each.value.bootstrap_storage.ai_update_interval, - var.bootstrap_storage[each.value.bootstrap_storage.name].ai_update_interval, - 5 + ai_instr_key = try( + module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], + null ) - private_network_cidr = try( - each.value.bootstrap_storage.intranet_cidr, - var.bootstrap_storage[each.value.bootstrap_storage.name].intranet_cidr, + ai_update_interval = each.value.bootstrap_package.ai_update_interval + + private_network_cidr = coalesce( + each.value.bootstrap_package.intranet_cidr, module.vnet[each.value.vnet_key].vnet_cidr[0] ) @@ -203,61 +202,62 @@ resource "local_file" "bootstrap_xml" { ] } -module "bootstrap" { - source = "../../modules/bootstrap" - - for_each = var.bootstrap_storage - - create_storage_account = try(each.value.create_storage, true) - name = each.value.name - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location - storage_acl = try(each.value.storage_acl, false) - storage_allow_vnet_subnet_ids = try(flatten([for v in each.value.storage_allow_vnet_subnets : [module.vnet[v.vnet_key].subnet_ids[v.subnet_key]]]), []) - storage_allow_inbound_public_ips = concat(try(each.value.storage_allow_inbound_public_ips, []), try([data.http.this[0].response_body], [])) - - tags = var.tags +locals { + bootstrap_file_shares_flat = flatten([ + for k, v in var.vmseries : + merge(v.virtual_machine.bootstrap_package, { vm_key = k }) + if v.virtual_machine.bootstrap_package != null + ]) + + bootstrap_file_shares = { for k, v in var.bootstrap_storages : k => { + for file_share in local.bootstrap_file_shares_flat : file_share.vm_key => { + name = file_share.vm_key + bootstrap_package_path = file_share.bootstrap_package_path + bootstrap_files = merge( + file_share.static_files, + file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = "config/bootstrap.xml" + } + ) + bootstrap_files_md5 = file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = local_file.bootstrap_xml[file_share.vm_key].content_md5 + } + } if file_share.bootstrap_storage_key == k } + } } -module "bootstrap_share" { +module "bootstrap" { source = "../../modules/bootstrap" - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage) } + for_each = var.bootstrap_storages - create_storage_account = false - name = module.bootstrap[each.value.bootstrap_storage.name].storage_account.name - resource_group_name = try(var.bootstrap_storage[each.value.bootstrap_storage].resource_group_name, local.resource_group.name) - location = var.location - storage_share_name = each.key - files = merge( - each.value.bootstrap_storage.static_files, - can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = "config/bootstrap.xml" - } : {} - ) + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} + storage_network_security = merge( + each.value.storage_network_security, + each.value.storage_network_security.vnet_key == null ? {} : { + allowed_subnet_ids = [ + for v in each.value.storage_network_security.allowed_subnet_keys : + module.vnet[each.value.storage_network_security.vnet_key].subnet_ids[v] + ] } + ) + file_shares_configuration = each.value.file_shares_configuration + file_shares = local.bootstrap_file_shares[each.key] tags = var.tags - - depends_on = [ - local_file.bootstrap_xml, - module.bootstrap - ] } - - resource "azurerm_availability_set" "this" { for_each = var.availability_sets name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location - platform_update_domain_count = try(each.value.update_domain_count, null) - platform_fault_domain_count = try(each.value.fault_domain_count, null) + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count tags = var.tags } @@ -267,47 +267,50 @@ module "vmseries" { for_each = var.vmseries + name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - name = "${var.name_prefix}${each.value.name}" - username = var.vmseries_username - password = local.vmseries_password - img_version = try(each.value.version, var.vmseries_version) - img_sku = var.vmseries_sku - vm_size = try(each.value.vm_size, var.vmseries_vm_size) - avset_id = try(azurerm_availability_set.this[each.value.availability_set_key].id, null) - - enable_zones = var.enable_zones - avzone = try(each.value.avzone, 1) - bootstrap_options = try( - each.value.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.name}", - "access-key=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), - "" + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + bootstrap_options = try( + coalesce( + each.value.virtual_machine.bootstrap_options, + join(",", [ + "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + ), + null + ) + } ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${each.value.name}-${v.name}" - subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) - create_public_ip = try(v.create_pip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - enable_backend_pool = can(v.load_balancer_key) ? true : false - lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) - private_ip_address = try(v.private_ip_address, null) + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address + attach_to_lb_backend_pool = v.load_balancer_key != null + lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) + }] tags = var.tags depends_on = [ module.vnet, azurerm_availability_set.this, + module.load_balancer, module.bootstrap, - module.bootstrap_share ] } diff --git a/examples/common_vmseries/outputs.tf b/examples/common_vmseries/outputs.tf index 10533a56..f946a80b 100644 --- a/examples/common_vmseries/outputs.tf +++ b/examples/common_vmseries/outputs.tf @@ -1,11 +1,11 @@ -output "username" { +output "usernames" { description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username + value = { for k, v in local.authentication : k => v.username } } -output "password" { +output "passwords" { description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password + value = { for k, v in local.authentication : k => v.password } sensitive = true } @@ -29,11 +29,11 @@ output "lb_frontend_ips" { } output "vmseries_mgmt_ips" { - description = "IP addresses for the VMSeries management interface." + description = "IP addresses for the VM-Series management interface." value = { for k, v in module.vmseries : k => v.mgmt_ip_address } } output "bootstrap_storage_urls" { - value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null + value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 56f820b2..a0bf9103 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -13,14 +13,17 @@ 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: ``` 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. + **Note!** \ + 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 @@ -28,7 +31,9 @@ variable "name_prefix" { 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 `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 @@ -243,14 +248,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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 @@ -273,36 +278,6 @@ variable "load_balancers" { } - -### GENERIC VMSERIES -variable "vmseries_version" { - description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} - -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} - -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} - variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. @@ -312,12 +287,19 @@ variable "availability_sets" { - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. + Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). + Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) } + variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -352,100 +334,273 @@ variable "ngfw_metrics" { }) } -variable "bootstrap_storage" { +variable "bootstrap_storages" { description = <<-EOF - A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property. - - Following properties are supported (except for name, all are optional): - - - `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`. - - `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account. - - `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist. - - `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`. - - `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly. - - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account. - - The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence: - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). + A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + + You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to + [module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) } variable "vmseries" { description = <<-EOF - Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported: - - - `name` : name of the VMSeries virtual machine. - - `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`. - - `version` : PanOS version, when specified overrides `var.vmseries_version`. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation. - - `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway. - - `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1". - - `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment. - - - `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage` - - `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported: - - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account - - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package - - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below. - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). - - - `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name`: string that will form the NIC name - - `subnet_key` : (string) a key of a subnet as defined in `var.vnets` - - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false` - - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface - - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name` - - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null` - - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used) + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + + For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + + The most basic properties are as follows: + + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. - Example: - ``` - { - "fw01" = { - name = "firewall01" - bootstrap_storage = { - name = "storageaccountname" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_common.tmpl" - public_snet_key = "public" - private_snet_key = "private" - } - avzone = 1 - vnet_key = "trust" - interfaces = [ - { - name = "mgmt" - subnet_key = "mgmt" - create_pip = true - private_ip_address = "10.0.0.1" - }, - { - name = "trust" - subnet_key = "private" - private_ip_address = "10.0.1.1" - load_balancer_key = "private_lb" - }, - { - name = "untrust" - subnet_key = "public" - private_ip_address = "10.0.2.1" - load_balancer_key = "public_lb" - public_ip_name = "existing_public_ip" - } - ] - } - } - ``` EOF + default = {} + nullable = false + type = map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || + v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null + ]) + error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + } + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true + if v.virtual_machine.bootstrap_package != null + ]) + error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + } } ### Application Gateway diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 9c5bca2a..23ff0882 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -8,7 +8,7 @@ show_in_hub: true Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with common VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). -Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PAN-OS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). ## Reference Architecture Design @@ -29,7 +29,7 @@ This design uses a Transit VNet. Application functions and resources are deploye The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. -![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) +![Common-VM-Series-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) This reference architecture consists of: @@ -48,7 +48,7 @@ This reference architecture consists of: * 2 Load Balancers: * public - with a public IP address assigned, in front of the public interfaces of the firewalls in VMSS, for incoming traffic * private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic -* an Application Insights, used to store the custom PanOS metrics sent from firewalls in scale set +* an Application Insights, used to store the custom PAN-OS metrics sent from firewalls in scale set * an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly _DISCLAIMER_ - Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private Panorama connectivity in place and Panorama Software Firewall License plugin you can bootstrap the firewalls without public IPs assigned to the management interfaces. You should also enable [Automatically push content when software device registers to Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-new-features/panorama-features/automatic-content-push-for-vm-series-and-cn-series-firewalls) on the template stack and [schedule content updates using Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-upgrade/upgrade-panorama/deploy-updates-to-firewalls-log-collectors-and-wildfire-appliances-using-panorama/schedule-a-content-update-using-panorama). Alternatively content updates can be configured to be fetched via data plane interfaces with service routes. @@ -190,15 +190,15 @@ terraform destroy | <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | | <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | | <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | +| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | +| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | | <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | | <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | | <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | | <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | | <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VMSeries virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PanOS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | +| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VM-Series virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PAN-OS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | +| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VM-Series interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | ### Outputs diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index b95ec4fe..1d451e72 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -13,14 +13,16 @@ 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: ``` 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. + 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 @@ -28,8 +30,9 @@ variable "name_prefix" { 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. + 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 @@ -237,14 +240,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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 @@ -329,8 +332,8 @@ variable "scale_sets" { The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: - - `version` - (`string`) describes the PanOS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PanOS image + - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). @@ -349,7 +352,8 @@ variable "scale_sets" { - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series instance + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series + instance - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not the scaling profiles (metrics thresholds, etc) @@ -357,9 +361,9 @@ variable "scale_sets" { Below we present only the most important properties, for the rest please refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in the - scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the - metrics to the thresholds + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in + the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare + the metrics to the thresholds - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> interface should be the management one. Following properties are available: @@ -369,7 +373,7 @@ variable "scale_sets" { - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancee's backend pool + added to the Load Balancer's backend pool - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the `var.appgws`, network interface that has this property defined will be added to the Application Gateways's backend pool diff --git a/examples/dedicated_vmseries/.header.md b/examples/dedicated_vmseries/.header.md new file mode 100644 index 00000000..0b6f6397 --- /dev/null +++ b/examples/dedicated_vmseries/.header.md @@ -0,0 +1,171 @@ +--- +short_title: Dedicated Firewall Option +type: refarch +show_in_hub: true +--- +# Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Dedicated Inbound NGFW Option + +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +dedicated-inbound VM-Series; for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +## Reference Architecture Design + +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) + +This code implements: + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *dedicated inbound option*, which separates inbound traffic flows onto a separate set of VM-Series. + +## Detailed Architecture and Design + +### Centralized Design + +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in +a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, +outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. + +### Dedicated Inbound Option + +The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series +firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. +The second set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment +choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic +flows affecting other traffic flows within the deployment. + +![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920818-44e4082d-b445-4ffc-b0cb-174ef1e3c2ae.png) + +This reference architecture consists of: + +- a VNET containing: + - 3 subnets dedicated to the firewalls: management, private and public + - Route Tables and Network Security Groups +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic + - private - in front of the firewalls private interfaces, for outgoing and east-west traffic +- a Storage Account used to keep bootstrap packages containing `DAY0` configuration for the firewalls +- 4 firewalls: + - deployed in different zones + - 2 pairs, one for inbound, the other for outbound and east-west traffic + - with 3 network interfaces each: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) + +**Note!** +- after the deployment the firewalls remain not licensed, they do however contain minimum `DAY0` configuration (required NIC, VR, + routes configuration). +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. + +## Usage + +### Deployment Steps + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers) +- copy the [`init-cfg.sample.txt`](./files/init-cfg.sample.txt) to `init-cfg.txt` and fill it out with required bootstrap + parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components#id07933d91-15be-414d-bc8d-f2a5f3d8df6b) for details) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + bootstrap_storage_urls = <sensitive> + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-in-1" = "1.2.3.4" + "fw-in-2" = "1.2.3.4" + "fw-obew-1" = "1.2.3.4" + "fw-obew-2" = "1.2.3.4" + } + ``` + +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output username + ``` + +- for password: + + ```bash + terraform output password + ``` + +The management public IP addresses are available in the `vmseries_mgmt_ips`: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +As mentioned, the devices already contain `DAY0` configuration, so all network interfaces should be configured and Azure Load +Balancer should already report that the devices are healthy. + +You can now proceed with licensing the devices and configuring your first rules. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration +(security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index a5ddeec6..319978e2 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -1,217 +1,1088 @@ +<!-- BEGIN_TF_DOCS --> --- -short_title: Dedicated Firewall Option +short\_title: Dedicated Firewall Option type: refarch -show_in_hub: true +show\_in\_hub: true --- # Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Dedicated Inbound NGFW Option -Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with dedicated-inbound VM-Series; for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +dedicated-inbound VM-Series; for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). ## Reference Architecture Design ![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) This code implements: -- a _centralized design_, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, east-west, and enterprise traffic -- the _dedicated inbound option_, which separates inbound traffic flows onto a separate set of VM-Series + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *dedicated inbound option*, which separates inbound traffic flows onto a separate set of VM-Series. ## Detailed Architecture and Design ### Centralized Design -This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in +a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, +outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. ### Dedicated Inbound Option -The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. The second set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. +The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series +firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. +The second set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment +choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic +flows affecting other traffic flows within the deployment. ![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920818-44e4082d-b445-4ffc-b0cb-174ef1e3c2ae.png) This reference architecture consists of: -* a VNET containing: - * 3 subnets dedicated to the firewalls: management, private and public - * Route Tables and Network Security Groups -* 2 Load Balancers: - * public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic - * private - in front of the firewalls private interfaces, for outgoing and east-west traffic -* a Storage Account used to keep bootstrap packages containing `DAY0` configuration for the firewalls -* 4 firewalls: - * deployed in different zones - * 2 pairs, one for inbound, the other for outbound and east-west traffic - * with 3 network interfaces each: management, public, private - * with public IP addresses assigned to: - * management interface - * public interface +- a VNET containing: + - 3 subnets dedicated to the firewalls: management, private and public + - Route Tables and Network Security Groups +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the firewalls public interfaces, for incoming traffic + - private - in front of the firewalls private interfaces, for outgoing and east-west traffic +- a Storage Account used to keep bootstrap packages containing `DAY0` configuration for the firewalls +- 4 firewalls: + - deployed in different zones + - 2 pairs, one for inbound, the other for outbound and east-west traffic + - with 3 network interfaces each: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) - - -**NOTE:** +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) -* after the deployment the firewalls remain not licensed, they do however contain minimum `DAY0` configuration (required NIC, VR, routes configuration). -* this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. +**Note!** +- after the deployment the firewalls remain not licensed, they do however contain minimum `DAY0` configuration (required NIC, VR, + routes configuration). +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. ## Usage ### Deployment Steps -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -* copy the [`init-cfg.sample.txt`](./files/init-cfg.sample.txt) to `init-cfg.txt` and fill it out with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components#id07933d91-15be-414d-bc8d-f2a5f3d8df6b) for details) -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers) +- copy the [`init-cfg.sample.txt`](./files/init-cfg.sample.txt) to `init-cfg.txt` and fill it out with required bootstrap + parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components#id07933d91-15be-414d-bc8d-f2a5f3d8df6b) for details) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - bootstrap_storage_urls = <sensitive> - lb_frontend_ips = { - "private" = { - "ha-ports" = "1.2.3.4" - } - "public" = { - "palo-lb-app1-pip" = "1.2.3.4" - } - } - password = <sensitive> - username = "panadmin" - vmseries_mgmt_ips = { - "fw-in-1" = "1.2.3.4" - "fw-in-2" = "1.2.3.4" - "fw-obew-1" = "1.2.3.4" - "fw-obew-2" = "1.2.3.4" - } - -* at this stage you have to wait couple of minutes for the firewalls to bootstrap. + ```console + bootstrap_storage_urls = <sensitive> + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-in-1" = "1.2.3.4" + "fw-in-2" = "1.2.3.4" + "fw-obew-1" = "1.2.3.4" + "fw-obew-2" = "1.2.3.4" + } + ``` + +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. ### Post deploy Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output username + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output password + ``` The management public IP addresses are available in the `vmseries_mgmt_ips`: -```sh +```bash terraform output vmseries_mgmt_ips ``` You can now login to the devices using either: -* cli - ssh client is required -* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. -As mentioned, the devices already contain `DAY0` configuration, so all network interfaces should be configured and Azure Load Balancer should already report that the devices are healthy. +As mentioned, the devices already contain `DAY0` configuration, so all network interfaces should be configured and Azure Load +Balancer should already report that the devices are healthy. You can now proceed with licensing the devices and configuring your first rules. -Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration (security hardening). +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration +(security hardening). ### Cleanup To remove the deployed infrastructure run: -```sh +```bash terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_http"></a> [http](#provider\_http) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | -| <a name="provider_local"></a> [local](#provider\_local) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_natgw"></a> [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_bootstrap"></a> [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| <a name="module_bootstrap_share"></a> [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| <a name="module_vmseries"></a> [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | -| <a name="module_appgw"></a> [appgw](#module\_appgw) | ../../modules/appgw | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_availability_set.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [local_file.bootstrap_xml](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [http_http.this](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_availability_sets"></a> [availability\_sets](#input\_availability\_sets) | A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used.<br><br>Following properties are supported:<br>- `name` - name of the Application Insights.<br>- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults).<br>- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults).<br><br>Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. | `any` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_bootstrap_storage"></a> [bootstrap\_storage](#input\_bootstrap\_storage) | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property.<br><br>Following properties are supported (except for name, all are optional):<br><br>- `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`.<br>- `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account.<br>- `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist.<br>- `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`.<br>- `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly.<br>- `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tried to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files are successfully uploaded to the Storage Account.<br><br><br>The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence:<br>- `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br>- `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br>- `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br>- `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). | `any` | `{}` | no | -| <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported:<br><br>- `name` : name of the VMSeries virtual machine.<br>- `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PanOS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation.<br>- `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway.<br>- `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1".<br>- `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment.<br><br>- `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage`<br>- `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported:<br> - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account<br> - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package<br> - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below.<br> - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br> - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br> - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br> - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes).<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>Example:<pre>{<br> "fw01" = {<br> name = "firewall01"<br> bootstrap_storage = {<br> name = "storageaccountname"<br> static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" }<br> template_bootstrap_xml = "templates/bootstrap_common.tmpl"<br> public_snet_key = "public"<br> private_snet_key = "private"<br> }<br> avzone = 1<br> vnet_key = "trust"<br> interfaces = [<br> {<br> name = "mgmt"<br> subnet_key = "mgmt"<br> create_pip = true<br> private_ip_address = "10.0.0.1"<br> },<br> {<br> name = "trust"<br> subnet_key = "private"<br> private_ip_address = "10.0.1.1"<br> load_balancer_key = "private_lb"<br> },<br> {<br> name = "untrust"<br> subnet_key = "public"<br> private_ip_address = "10.0.2.1"<br> load_balancer_key = "public_lb"<br> public_ip_name = "existing_public_ip"<br> }<br> ]<br> }<br>}</pre> | `any` | n/a | yes | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_natgw_public_ips"></a> [natgw\_public\_ips](#output\_natgw\_public\_ips) | Nat Gateways Public IP resources. | -| <a name="output_metrics_instrumentation_keys"></a> [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -| <a name="output_vmseries_mgmt_ips"></a> [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for the VMSeries management interface. | -| <a name="output_bootstrap_storage_urls"></a> [bootstrap\_storage\_urls](#output\_bootstrap\_storage\_urls) | n/a | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. +[`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial administrative username to use for VM-Series. +`passwords` | Initial administrative password to use for VM-Series. +`natgw_public_ips` | Nat Gateways Public IP resources. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. +`vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. +`bootstrap_storage_urls` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.2, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` +- `local` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`appgw` | - | ../../modules/appgw | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### enable_zones + +If `true`, enable zone support for resources. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### natgws + +A map defining NAT Gateways. + +Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one +explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. +For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + +Following properties are supported: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### load_balancers + +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` + + +Type: + +```hcl +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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### availability_sets + +A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + +Following properties are supported: +- `name` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). +Please verify how many update and fault domain are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### bootstrap_storages + +A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + +You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to +[module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + +- `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. +- `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. +- `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vmseries + +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + +For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + +The most basic properties are as follows: + +- `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + + +Type: + +```hcl +map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + +Following properties are supported: +- `name` - (`string`, required) name of the Application Gateway. +- `public_ip` - (`string`, required) public IP address. +- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. +- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. +- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. +- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) +- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported +- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway +- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. +- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name +- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool +- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) +- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) +- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) +- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) +- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` +- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` +- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + + +Type: + +```hcl +map(object({ + name = string + public_ip = object({ + name = string + resource_group_name = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img b/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img deleted file mode 100644 index 6b584e8e..00000000 --- a/examples/dedicated_vmseries/bootstrap_package/software/10.2.4.img +++ /dev/null @@ -1 +0,0 @@ -content \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 74796a10..1a9ae056 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -7,7 +7,6 @@ tags = { "CreatedWith" = "Terraform" } - # --- VNET PART --- # vnets = { "transit" = { @@ -23,7 +22,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] source_port_range = "*" destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] @@ -48,6 +47,11 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } + "appgw_blackhole" = { + name = "appgw-blackhole-udr" + address_prefix = "10.0.0.48/28" + next_hop_type = "None" + } } } "private" = { @@ -69,6 +73,11 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } + "appgw_blackhole" = { + name = "appgw-blackhole-udr" + address_prefix = "10.0.0.48/28" + next_hop_type = "None" + } } } "public" = { @@ -118,10 +127,12 @@ vnets = { # --- LOAD BALANCING PART --- # load_balancers = { "public" = { - name = "public-lb" - 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 + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["0.0.0.0/0"] + } frontend_ips = { "app1" = { name = "app1" @@ -207,138 +218,158 @@ appgws = { # --- VMSERIES PART --- # +ngfw_metrics = { + name = "metrics" +} -bootstrap_storage = { - bootstrap = { - name = "fosixbootstrap" - public_snet_key = "public" - private_snet_key = "private" - storage_acl = true - intranet_cidr = "10.100.0.0/16" - storage_allow_vnet_subnets = { - management = { - vnet_key = "transit" - subnet_key = "management" - } +bootstrap_storages = { + "bootstrap" = { + name = "smplngfwbtstrp" + storage_network_security = { + vnet_key = "transit" + allowed_subnet_keys = ["management"] + allowed_public_ips = ["134.238.135.14", "134.238.135.140"] } - storage_allow_inbound_public_ips = ["134.238.135.14", "134.238.135.140"] } } -ngfw_metrics = { - name = "metrics" -} - -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" vmseries = { "fw-in-1" = { - name = "inbound-firewall-01" - add_to_appgw_backend = true - bootstrap_storage = { - name = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_inbound.tmpl" + name = "inbound-firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap_inbound.tmpl" + private_snet_key = "private" + public_snet_key = "public" + } } - vnet_key = "transit" - avzone = 1 interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm-in-01-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm-in-01-private" subnet_key = "private" }, { - name = "public" + name = "vm-in-01-public" subnet_key = "public" + create_public_ip = true load_balancer_key = "public" - create_pip = true } ] } "fw-in-2" = { - name = "inbound-firewall-02" - add_to_appgw_backend = true - bootstrap_storage = { - name = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_inbound.tmpl" + name = "inbound-firewall02" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap_inbound.tmpl" + private_snet_key = "private" + public_snet_key = "public" + } } - vnet_key = "transit" - avzone = 2 interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm-in-02-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm-in-02-private" subnet_key = "private" }, { - name = "public" + name = "vm-in-02-public" subnet_key = "public" load_balancer_key = "public" - create_pip = true } ] } "fw-obew-1" = { - name = "obew-firewall-01" - bootstrap_storage = { - name = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_obew.tmpl" + name = "obew-firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap_obew.tmpl" + private_snet_key = "private" + public_snet_key = "public" + } } - vnet_key = "transit" - avzone = 1 interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm-obew-01-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm-obew-01-private" subnet_key = "private" load_balancer_key = "private" }, { - name = "public" - subnet_key = "public" - create_pip = true + name = "vm-obew-01-public" + subnet_key = "public" + create_public_ip = true } ] } "fw-obew-2" = { - name = "obew-firewall-02" - bootstrap_storage = { - name = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_obew.tmpl" + name = "obew-firewall02" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap_obew.tmpl" + private_snet_key = "private" + public_snet_key = "public" + } } - vnet_key = "transit" - avzone = 2 interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true + name = "vm-obew-02-mgmt" + subnet_key = "management" + create_public_ip = true }, { - name = "private" + name = "vm-obew-02-private" subnet_key = "private" load_balancer_key = "private" }, { - name = "public" - subnet_key = "public" - create_pip = true + name = "vm-obew-02-public" + subnet_key = "public" + create_public_ip = true } ] } diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 2100f7e2..031912b2 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -1,6 +1,8 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.vmseries : v.authentication.password == null + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,14 +13,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) -} - -# Obtain Public IP address of code deployment machine - -data "http" "this" { - count = length(var.bootstrap_storage) > 0 && anytrue([for v in values(var.bootstrap_storage) : try(v.storage_acl, false)]) ? 1 : 0 - url = "https://ifconfig.me/ip" + authentication = { + for k, v in var.vmseries : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } } # Create or source the Resource Group. @@ -131,8 +135,7 @@ module "load_balancer" { - -# create the actual VMSeries VMs and resources +# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -155,39 +158,35 @@ module "ngfw_metrics" { } resource "local_file" "bootstrap_xml" { - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage.template_bootstrap_xml) } + for_each = { + for k, v in var.vmseries : + k => v.virtual_machine + if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + } - filename = "files/${each.value.name}-bootstrap.xml" + filename = "files/${each.key}-bootstrap.xml" content = templatefile( - each.value.bootstrap_storage.template_bootstrap_xml, + each.value.bootstrap_package.bootstrap_xml_template, { private_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.private_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].private_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.private_snet_key], 1 ) public_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.public_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].public_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.public_snet_key], 1 ) - ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) - - ai_update_interval = try( - each.value.bootstrap_storage.ai_update_interval, - var.bootstrap_storage[each.value.bootstrap_storage.name].ai_update_interval, - 5 + ai_instr_key = try( + module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], + null ) - private_network_cidr = try( - each.value.bootstrap_storage.intranet_cidr, - var.bootstrap_storage[each.value.bootstrap_storage.name].intranet_cidr, + ai_update_interval = each.value.bootstrap_package.ai_update_interval + + private_network_cidr = coalesce( + each.value.bootstrap_package.intranet_cidr, module.vnet[each.value.vnet_key].vnet_cidr[0] ) @@ -203,61 +202,62 @@ resource "local_file" "bootstrap_xml" { ] } -module "bootstrap" { - source = "../../modules/bootstrap" - - for_each = var.bootstrap_storage - - create_storage_account = try(each.value.create_storage, true) - name = each.value.name - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location - storage_acl = try(each.value.storage_acl, false) - storage_allow_vnet_subnet_ids = try(flatten([for v in each.value.storage_allow_vnet_subnets : [module.vnet[v.vnet_key].subnet_ids[v.subnet_key]]]), []) - storage_allow_inbound_public_ips = concat(try(each.value.storage_allow_inbound_public_ips, []), try([data.http.this[0].response_body], [])) - - tags = var.tags +locals { + bootstrap_file_shares_flat = flatten([ + for k, v in var.vmseries : + merge(v.virtual_machine.bootstrap_package, { vm_key = k }) + if v.virtual_machine.bootstrap_package != null + ]) + + bootstrap_file_shares = { for k, v in var.bootstrap_storages : k => { + for file_share in local.bootstrap_file_shares_flat : file_share.vm_key => { + name = file_share.vm_key + bootstrap_package_path = file_share.bootstrap_package_path + bootstrap_files = merge( + file_share.static_files, + file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = "config/bootstrap.xml" + } + ) + bootstrap_files_md5 = file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = local_file.bootstrap_xml[file_share.vm_key].content_md5 + } + } if file_share.bootstrap_storage_key == k } + } } -module "bootstrap_share" { +module "bootstrap" { source = "../../modules/bootstrap" - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage) } + for_each = var.bootstrap_storages - create_storage_account = false - name = module.bootstrap[each.value.bootstrap_storage.name].storage_account.name - resource_group_name = try(var.bootstrap_storage[each.value.bootstrap_storage].resource_group_name, local.resource_group.name) - location = var.location - storage_share_name = each.key - files = merge( - each.value.bootstrap_storage.static_files, - can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = "config/bootstrap.xml" - } : {} - ) + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} + storage_network_security = merge( + each.value.storage_network_security, + each.value.storage_network_security.vnet_key == null ? {} : { + allowed_subnet_ids = [ + for v in each.value.storage_network_security.allowed_subnet_keys : + module.vnet[each.value.storage_network_security.vnet_key].subnet_ids[v] + ] } + ) + file_shares_configuration = each.value.file_shares_configuration + file_shares = local.bootstrap_file_shares[each.key] tags = var.tags - - depends_on = [ - local_file.bootstrap_xml, - module.bootstrap - ] } - - resource "azurerm_availability_set" "this" { for_each = var.availability_sets name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location - platform_update_domain_count = try(each.value.update_domain_count, null) - platform_fault_domain_count = try(each.value.fault_domain_count, null) + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count tags = var.tags } @@ -267,47 +267,50 @@ module "vmseries" { for_each = var.vmseries + name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - name = "${var.name_prefix}${each.value.name}" - username = var.vmseries_username - password = local.vmseries_password - img_version = try(each.value.version, var.vmseries_version) - img_sku = var.vmseries_sku - vm_size = try(each.value.vm_size, var.vmseries_vm_size) - avset_id = try(azurerm_availability_set.this[each.value.availability_set_key].id, null) - - enable_zones = var.enable_zones - avzone = try(each.value.avzone, 1) - bootstrap_options = try( - each.value.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.name}", - "access-key=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), - "" + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + bootstrap_options = try( + coalesce( + each.value.virtual_machine.bootstrap_options, + join(",", [ + "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + ), + null + ) + } ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${each.value.name}-${v.name}" - subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) - create_public_ip = try(v.create_pip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - enable_backend_pool = can(v.load_balancer_key) ? true : false - lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) - private_ip_address = try(v.private_ip_address, null) + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address + attach_to_lb_backend_pool = v.load_balancer_key != null + lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) + }] tags = var.tags depends_on = [ module.vnet, azurerm_availability_set.this, + module.load_balancer, module.bootstrap, - module.bootstrap_share ] } @@ -334,7 +337,7 @@ module "appgw" { name = "vmseries" vmseries_ips = [ for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name ].private_ip_address if try(v.add_to_appgw_backend, false) ] } @@ -350,4 +353,4 @@ module "appgw" { tags = var.tags depends_on = [module.vnet] -} +} \ No newline at end of file diff --git a/examples/dedicated_vmseries/main_test.go b/examples/dedicated_vmseries/main_test.go index 53c7e0f0..db87a3dd 100644 --- a/examples/dedicated_vmseries/main_test.go +++ b/examples/dedicated_vmseries/main_test.go @@ -14,7 +14,8 @@ import ( func CreateTerraformOptions(t *testing.T) *terraform.Options { // prepare random prefix randomNames, _ := testskeleton.GenerateTerraformVarsInfo("azure") - storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\", public_snet_key = \"public\", private_snet_key = \"private\", intranet_cidr = \"10.0.0.0/25\"} }", randomNames.AzureStorageAccountName) + // storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\", public_snet_key = \"public\", private_snet_key = \"private\", intranet_cidr = \"10.0.0.0/25\"} }", randomNames.AzureStorageAccountName) + storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\" }}", randomNames.AzureStorageAccountName) // copy the init-cfg.sample.txt file to init-cfg.txt for test purposes _, err := os.Stat("files/init-cfg.txt") @@ -36,7 +37,7 @@ func CreateTerraformOptions(t *testing.T) *terraform.Options { Vars: map[string]interface{}{ "name_prefix": randomNames.NamePrefix, "resource_group_name": randomNames.AzureResourceGroupName, - "bootstrap_storage": storageDefinition, + "bootstrap_storages": storageDefinition, }, Logger: logger.Default, Lock: true, diff --git a/examples/dedicated_vmseries/outputs.tf b/examples/dedicated_vmseries/outputs.tf index 10533a56..f946a80b 100644 --- a/examples/dedicated_vmseries/outputs.tf +++ b/examples/dedicated_vmseries/outputs.tf @@ -1,11 +1,11 @@ -output "username" { +output "usernames" { description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username + value = { for k, v in local.authentication : k => v.username } } -output "password" { +output "passwords" { description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password + value = { for k, v in local.authentication : k => v.password } sensitive = true } @@ -29,11 +29,11 @@ output "lb_frontend_ips" { } output "vmseries_mgmt_ips" { - description = "IP addresses for the VMSeries management interface." + description = "IP addresses for the VM-Series management interface." value = { for k, v in module.vmseries : k => v.mgmt_ip_address } } output "bootstrap_storage_urls" { - value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null + value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index afeeb471..a0bf9103 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -13,14 +13,17 @@ 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: ``` 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. + **Note!** \ + 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 @@ -28,7 +31,9 @@ variable "name_prefix" { 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 `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 @@ -79,8 +84,7 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -98,7 +102,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -243,14 +248,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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 @@ -273,36 +278,6 @@ variable "load_balancers" { } - -### GENERIC VMSERIES -variable "vmseries_version" { - description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} - -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} - -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} - variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. @@ -312,12 +287,19 @@ variable "availability_sets" { - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. + Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). + Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) } + variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -352,100 +334,273 @@ variable "ngfw_metrics" { }) } -variable "bootstrap_storage" { +variable "bootstrap_storages" { description = <<-EOF - A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property. - - Following properties are supported (except for name, all are optional): - - - `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`. - - `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account. - - `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist. - - `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`. - - `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly. - - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account. - - The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence: - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). + A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + + You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to + [module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) } variable "vmseries" { description = <<-EOF - Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported: - - - `name` : name of the VMSeries virtual machine. - - `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`. - - `version` : PanOS version, when specified overrides `var.vmseries_version`. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation. - - `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway. - - `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1". - - `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment. - - - `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage` - - `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported: - - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account - - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package - - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below. - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). - - - `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name`: string that will form the NIC name - - `subnet_key` : (string) a key of a subnet as defined in `var.vnets` - - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false` - - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface - - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name` - - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null` - - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used) + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + + For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + + The most basic properties are as follows: + + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. - Example: - ``` - { - "fw01" = { - name = "firewall01" - bootstrap_storage = { - name = "storageaccountname" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_common.tmpl" - public_snet_key = "public" - private_snet_key = "private" - } - avzone = 1 - vnet_key = "trust" - interfaces = [ - { - name = "mgmt" - subnet_key = "mgmt" - create_pip = true - private_ip_address = "10.0.0.1" - }, - { - name = "trust" - subnet_key = "private" - private_ip_address = "10.0.1.1" - load_balancer_key = "private_lb" - }, - { - name = "untrust" - subnet_key = "public" - private_ip_address = "10.0.2.1" - load_balancer_key = "public_lb" - public_ip_name = "existing_public_ip" - } - ] - } - } - ``` EOF + default = {} + nullable = false + type = map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || + v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null + ]) + error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + } + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true + if v.virtual_machine.bootstrap_package != null + ]) + error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + } } ### Application Gateway diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 7eb43a14..165370dd 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -8,7 +8,7 @@ show_in_hub: true Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with dedicated-inbound VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). -Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. Therefore they are not assigned with public IP addresses. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PAN-OS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. Therefore they are not assigned with public IP addresses. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). ## Reference Architecture Design @@ -29,7 +29,7 @@ This design uses a Transit VNet. Application functions and resources are deploye The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. The second set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) +![Dedicated-VM-Series-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) This reference architecture consists of: @@ -46,7 +46,7 @@ This reference architecture consists of: * public - with a public IP address assigned, in front of the public interfaces of the inbound VMSS, for incoming traffic * private - in front of the firewalls private interfaces of the OBEW VMSS, for outgoing and east-west traffic * a NAT Gateway responsible for handling the outgoing traffic for the management (updates) and public (outbound traffic in OBEW firewalls mainly) interfaces -* 2 Application Insights, one per each scale set, used to store the custom PanOS metrics +* 2 Application Insights, one per each scale set, used to store the custom PAN-OS metrics * an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's default mechanism). @@ -188,15 +188,15 @@ terraform destroy | <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | | <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | | <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | +| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | +| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | | <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | | <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | | <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | | <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | | <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VMSeries virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PanOS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | +| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VM-Series virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PAN-OS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | +| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VM-Series interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | ### Outputs diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index b95ec4fe..1d451e72 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -13,14 +13,16 @@ 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: ``` 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. + 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 @@ -28,8 +30,9 @@ variable "name_prefix" { 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. + 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 @@ -237,14 +240,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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 @@ -329,8 +332,8 @@ variable "scale_sets" { The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: - - `version` - (`string`) describes the PanOS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PanOS image + - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). @@ -349,7 +352,8 @@ variable "scale_sets" { - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series instance + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series + instance - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not the scaling profiles (metrics thresholds, etc) @@ -357,9 +361,9 @@ variable "scale_sets" { Below we present only the most important properties, for the rest please refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in the - scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the - metrics to the thresholds + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in + the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare + the metrics to the thresholds - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> interface should be the management one. Following properties are available: @@ -369,7 +373,7 @@ variable "scale_sets" { - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancee's backend pool + added to the Load Balancer's backend pool - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the `var.appgws`, network interface that has this property defined will be added to the Application Gateways's backend pool diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index bee9d4a3..2e30f781 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -114,12 +114,12 @@ terraform destroy | <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to all of the created resources. | `map(string)` | `{}` | no | | <a name="input_vnets"></a> [vnets](#input\_vnets) | Map with VNet definitions. Each item supports following inputs for `vnet` module:<br>- `name` - (required\|string) VNet name.<br>- `create_virtual_network` - (optional\|bool) Whether to create a new or source an existing VNet, defaults to `true`.<br>- `address_space` - (optional\|list) List of CIDRs for the new VNet.<br>- `resource_group_name` - (optional\|string) VNet's Resource Group, by default the one specified by `var.resource_group_name`.<br>- `create_subnets` - (optional\|bool) Whether to create or source items from `subnets`, defaults to `true`.<br>- `subnets` - (required\|map) Subnet definitions.<br>- `network_security_groups` - (optional\|map) NSGs to create.<br>- `route_tables` - (optional\|map) Route Tables to create.<br><br>Please consult [module documentation](../../modules/vnet/README.md) for details. | `any` | n/a | yes | | <a name="input_gateway_load_balancers"></a> [gateway\_load\_balancers](#input\_gateway\_load\_balancers) | Map with Gateway Load Balancer definitions. Following settings are supported:<br>- `name` - (required\|string) Gateway Load Balancer name.<br>- `vnet_key` - (required\|string) Key of a VNet from `var.vnets` that contains target Subnet for LB's frontned. Used to get Subnet ID in combination with `subnet_key` below.<br>- `subnet_key` - (required\|string) Key of a Subnet from `var.vnets[vnet_key]`.<br>- `frontend_ip_config` - (optional\|map) Remaining Frontned IP configuration.<br>- `resource_group_name` - (optional\|string) LB's Resource Group, by default the one specified by `var.resource_group_name`.<br>- `backends` - (optional\|map) LB's backend configurations.<br>- `heatlh_probe` - (optional\|map) Health probe configuration.<br><br>Please consult [module documentation](../../modules/gwlb/README.md) for details. | `any` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [module documentation](../modules/application\_insights/README.md)):<br><br>- `name` - (optional\|string) Name of a single AI instance<br>- `workspace_mode` - (optional\|bool) Use AI Workspace mode instead of the Classical (deprecated), defaults to `true`.<br>- `workspace_name` - (optional\|string) Name of the Log Analytics Workspace created when AI is deployed in Workspace mode, defaults to AI name suffixed with `-wrkspc`.<br>- `workspace_sku` - (optional\|string) SKU used by WAL, see module documentation for details, defaults to PerGB2018.<br>- `metrics_retention_in_days` - (optional\|number) Defaults to current Azure default value, see module documentation for details.<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | +| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [module documentation](../modules/application\_insights/README.md)):<br><br>- `name` - (optional\|string) Name of a single AI instance<br>- `workspace_mode` - (optional\|bool) Use AI Workspace mode instead of the Classical (deprecated), defaults to `true`.<br>- `workspace_name` - (optional\|string) Name of the Log Analytics Workspace created when AI is deployed in Workspace mode, defaults to AI name suffixed with `-wrkspc`.<br>- `workspace_sku` - (optional\|string) SKU used by WAL, see module documentation for details, defaults to PerGB2018.<br>- `metrics_retention_in_days` - (optional\|number) Defaults to current Azure default value, see module documentation for details.<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | | <a name="input_bootstrap_storages"></a> [bootstrap\_storages](#input\_bootstrap\_storages) | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property.<br>Following properties are supported:<br>- `name` - (required\|string) Name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`.<br>- `create_storage_account` - (optional\|bool) Whether to create or source an existing Storage Account, defaults to `true`.<br>- `resource_group_name` - (optional\|string) Name of the Resource Group hosting the Storage Account, defaults to `var.resource_group_name`.<br>- `storage_acl` - (optional\|bool) Allows to enable network ACLs on the Storage Account. If set to `true`, `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. Defaults to `false`.<br>- `storage_allow_vnet_subnets` - (optional\|map) Map with objects that contains `vnet_key`/`subnet_key` used to identify subnets allowed to access the Storage Account. Note that `enable_storage_service_endpoint` has to be set to `true` in the corresponding subnet configuration.<br>- `storage_allow_inbound_public_ips` - (optional\|list) Whitelist that contains public IPs/ranges allowed to access the Storage Account. Note that the code automatically to queries https://ifcondif.me to obtain the public IP address of the machine executing the code to enable bootstrap files upload. | `any` | `{}` | no | | <a name="input_vmseries_common"></a> [vmseries\_common](#input\_vmseries\_common) | Configuration common for all firewall instances. Following settings can be specified:<br>- `username` - (required\|string)<br>- `password` - (optional\|string)<br>- `ssh_keys` - (optional\|string)<br>- `img_version` - (optional\|string)<br>- `img_sku` - (optional\|string)<br>- `vm_size` - (optional\|string)<br>- `bootstrap_options` - (optional\|string)<br>- `vnet_key` - (optional\|string)<br>- `interfaces` - (optional\|list(object))<br>- `ai_update_interval` - (optional\|number)<br><br>All are used directly as inputs for `vmseries` module (please see [documentation](../../modules/vmseries/README.md) for details), except for the last three:<br>- `vnet_key` - (required\|string) Used to identify VNet in which subnets for interfaces exist.<br>- `ai_update_interval` - (optional\|number) If Application Insights are used this property can override the default metrics update interval (in minutes). | `any` | n/a | yes | | <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | Map with VM-Series instance specific configuration. Following properties are supported:<br>- `name` - (required\|string) Instance name.<br>- `avzone` - (optional\|string) AZ to deploy instance in, defaults to "1".<br>- `availability_set_key` - (optional\|string) Key from `var.availability_sets`, used to determine Availabbility Set ID.<br>- `bootstrap_storage` - (optional\|map) Map that contains bootstrap package contents definition, when present triggers creation of a File Share in an existing Storage Account. Following properties supported:<br> - `key` - (required\|string) Identifies Storage Account to use from `var.bootstrap_storages`.<br> - `static_files` - (optional\|map) Map where keys are local file paths, values determine destination in the bootstrap package (file share) where the file will be copied.<br> - `template_bootstrap_xml` - (optional\|string) Path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and it's upload to the boostrap package. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in `var.vmseries_common`). The properties are listed below.<br>- `interfaces` - List of objects with interface definitions. Utilizes all properties of `interfaces` input (see [documantation](../../modules/vmseries/README.md#inputs)), expect for `subnet_id` and `lb_backend_pool_id`, which are determined based on the following new items:<br> - `subnet_key` - (optional\|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with.<br> - `gwlb_key` - (optional\|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`.<br> - `gwlb_backend_key` - (optional\|string) Key that identifies a backend from the GWLB selected by `gwlb_key` to associate th interface with, required when `enable_backend_pool` is `true`.<br><br>Additionally, it's possible to override following settings from `var.vmseries_common`:<br>- `bootstrap_options` - When defined, it not only takes precedence over `var.vmseries_common.bootstrap_options`, but also over `bootstrap_storage` described below.<br>- `img_version`<br>- `img_sku`<br>- `vm_size`<br>- `ai_update_interval` | `map(any)` | n/a | yes | | <a name="input_availability_sets"></a> [availability\_sets](#input\_availability\_sets) | A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used.<br><br>Following properties are supported:<br>- `name` - (required\|string) Name of the Application Insights.<br>- `update_domain_count` - (optional\|int) Specifies the number of update domains that are used, defaults to 5 (Azure defaults).<br>- `fault_domain_count` - (optional\|int) Specifies the number of fault domains that are used, defaults to 3 (Azure defaults).<br><br>Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br>Following properties are available (for details refer to module's documentation):<br>- `name` - (required\|string) Name of the Load Balancer resource.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips` - (optional\|string) Public LB only - list of IP addresses that will be allowed in the ingress rules.<br>- `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).<br>- `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:<br> - `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<br> - `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<br> - `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<br> - `private_ip_address` - (optional\|string) Private LB only - defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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.<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "internal_app_lb"<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "internal_app_vnet"<br> subnet_key = "internal_app_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | +| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br>Following properties are available (for details refer to module's documentation):<br>- `name` - (required\|string) Name of the Load Balancer resource.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips` - (optional\|string) Public LB only - list of IP addresses that will be allowed in the ingress rules.<br>- `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).<br>- `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:<br> - `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<br> - `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<br> - `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<br> - `private_ip_address` - (optional\|string) Private LB only - defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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.<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "internal_app_lb"<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "internal_app_vnet"<br> subnet_key = "internal_app_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | | <a name="input_appvms_common"></a> [appvms\_common](#input\_appvms\_common) | Common settings for sample applications:<br>- `username` - (required\|string)<br>- `password` - (optional\|string)<br>- `ssh_keys` - (optional\|list(string)<br>- `vm_size` - (optional\|string)<br>- `disk_type` - (optional\|string)<br>- `accelerated_networking` - (optional\|bool)<br><br>At least one of `password` or `ssh_keys` has to be provided. | `any` | n/a | yes | | <a name="input_appvms"></a> [appvms](#input\_appvms) | Configuration for sample application VMs. Available settings:<br>- `name` - (required\|string) Instance name.<br>- `avzone` - (optional\|string) AZ to deploy instance in, defaults to "1".<br>- `vnet_key` - (required\|string) Used to identify VNet in which subnets for interfaces exist.<br>- `subnet_key` - (required\|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with.<br>- `load_balancer_key` - (optional\|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`. | `any` | `{}` | no | diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index d431473f..8a6d426f 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -132,7 +132,7 @@ gateway_load_balancers = { } } -# VMseries +# VM-Series bootstrap_storages = { bootstrap = { name = "vmseriesgwlbboostrap" diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 3c9c188a..ca3ab0da 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -222,15 +222,15 @@ module "vmseries" { interfaces = [for interface in each.value.interfaces : { - name = "${var.name_prefix}${each.value.name}-${interface.name}" - subnet_id = module.vnet[var.vmseries_common.vnet_key].subnet_ids[interface.subnet_key] - create_public_ip = try(interface.create_public_ip, false) - public_ip_name = try(interface.public_ip_name, null) - public_ip_resource_group = try(interface.public_ip_resource_group, null) - private_ip_address = try(interface.private_ip_address, null) - enable_backend_pool = try(interface.enable_backend_pool, false) - lb_backend_pool_id = try(interface.enable_backend_pool, false) ? module.gwlb[interface.gwlb_key].backend_pool_ids[interface.gwlb_backend_key] : null - tags = try(interface.tags, null) + name = "${var.name_prefix}${each.value.name}-${interface.name}" + subnet_id = module.vnet[var.vmseries_common.vnet_key].subnet_ids[interface.subnet_key] + create_public_ip = try(interface.create_public_ip, false) + public_ip_name = try(interface.public_ip_name, null) + public_ip_resource_group_name = try(interface.public_ip_resource_group_name, null) + private_ip_address = try(interface.private_ip_address, null) + enable_backend_pool = try(interface.enable_backend_pool, false) + lb_backend_pool_id = try(interface.enable_backend_pool, false) ? module.gwlb[interface.gwlb_key].backend_pool_ids[interface.gwlb_backend_key] : null + tags = try(interface.tags, null) } ] diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index e2b1a841..475bf0ae 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -299,14 +299,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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/natgw/example.tfvars b/examples/natgw/example.tfvars deleted file mode 100644 index d3d6a122..00000000 --- a/examples/natgw/example.tfvars +++ /dev/null @@ -1,62 +0,0 @@ -# --- GENERAL --- # -location = "North Europe" -resource_group_name = "transit-vnet-common" -name_prefix = "ac-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - - -# --- VNET PART --- # -vnets = { - "transit" = { - name = "transit" - address_space = ["10.0.0.0/25"] - network_security_groups = { - "management" = { - name = "mgmt-nsg" - rules = { - mgmt_inbound = { - name = "vmseries-management-allow-inbound" - priority = 100 - direction = "Inbound" - access = "Allow" - protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] # 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"] - } - } - } - } - subnets = { - "management" = { - name = "mgmt-snet" - address_prefixes = ["10.0.0.0/28"] - network_security_group_key = "management" - } - } - } -} - - -# --- NATGW PART --- # -natgws = { - "natgw" = { - create_natgw = true - name = "natgw" - vnet_key = "transit" - subnet_keys = ["management"] - public_ip = { - create = true - name = "natgw-pip" - } - public_ip_prefix = { - create = true - name = "natgw-pip-prefix" - length = 31 - } - } -} \ No newline at end of file diff --git a/examples/natgw/main.tf b/examples/natgw/main.tf deleted file mode 100644 index 6adf2680..00000000 --- a/examples/natgw/main.tf +++ /dev/null @@ -1,61 +0,0 @@ -# Create or source the Resource Group. -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - name = var.resource_group_name -} - -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - tags = var.tags -} - -module "natgw" { - source = "../../modules/natgw" - - for_each = var.natgws - - create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - zone = try(each.value.zone, null) - idle_timeout = each.value.idle_timeout - subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) - - tags = var.tags - depends_on = [module.vnet] -} \ No newline at end of file diff --git a/examples/natgw/variables.tf b/examples/natgw/variables.tf deleted file mode 100644 index e5478074..00000000 --- a/examples/natgw/variables.tf +++ /dev/null @@ -1,176 +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 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 its 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 `true`) 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`, optional, 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 - resource_group_name = optional(string) - create_virtual_network = optional(bool, true) - address_space = optional(list(string)) - network_security_groups = optional(map(object({ - name = 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 - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_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) - })), {}) - })) -} - - -### NATGW -variable "natgws" { - description = <<-EOF - A map defining NAT Gateways. - - Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one - explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. - For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). - - Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. - - Example: - ``` - natgws = { - "natgw" = { - name = "natgw" - vnet_key = "transit-vnet" - subnet_keys = ["management"] - public_ip = { - create = true - name = "natgw-pip" - } - } - } - ``` - EOF - default = {} - type = map(object({ - create_natgw = optional(bool, true) - name = string - resource_group_name = optional(string) - zone = optional(string) - idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) - public_ip = optional(object({ - create = bool - name = string - resource_group_name = optional(string) - })) - public_ip_prefix = optional(object({ - create = bool - name = string - resource_group_name = optional(string) - length = optional(number) - })) - })) -} \ No newline at end of file diff --git a/examples/natgw/versions.tf b/examples/natgw/versions.tf deleted file mode 100644 index 50ff584a..00000000 --- a/examples/natgw/versions.tf +++ /dev/null @@ -1,22 +0,0 @@ -terraform { - required_version = ">= 1.3, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - random = { - source = "hashicorp/random" - } - http = { - source = "hashicorp/http" - } - } -} - -provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index eeacb597..585e0bdb 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -142,10 +142,10 @@ terraform destroy | <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs. A key is the VNET name, value is a set of properties like described below.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : a name of a Virtual Network<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | | <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | | <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_panorama_version"></a> [panorama\_version](#input\_panorama\_version) | Panorama PanOS version. It's also possible to specify the Pan-OS version per Panorama (in case you would like to deploy more than one), see `var.panoramas` variable. | `string` | n/a | yes | +| <a name="input_panorama_version"></a> [panorama\_version](#input\_panorama\_version) | Panorama PAN-OS version. It's also possible to specify the Pan-OS version per Panorama (in case you would like to deploy more than one), see `var.panoramas` variable. | `string` | n/a | yes | | <a name="input_panorama_sku"></a> [panorama\_sku](#input\_panorama\_sku) | Panorama SKU, basically a type of licensing used in Azure. | `string` | `"byol"` | no | | <a name="input_panorama_size"></a> [panorama\_size](#input\_panorama\_size) | A size of a VM that will run Panorama. It's also possible to specify the the VM size per Panorama, see `var.panoramas` variable. | `string` | `"Standard_D5_v2"` | no | -| <a name="input_panoramas"></a> [panoramas](#input\_panoramas) | A map containing Panorama definitions.<br><br>All definitions share a VM size, SKU and PanOS version (`panorama_size`, `panorama_sku`, `panorama_version` respectively). Defining more than one Panorama makes sense when creating for example HA pairs. <br><br>Following properties are available:<br><br>- `name` : a name of a Panorama VM<br>- `size` : size of the Panorama virtual machine, when specified overrides `var.panorama_size`.<br>- `version` : PanOS version, when specified overrides `var.panorama_version`.<br>- `vnet_key`: a VNET used to host Panorama VM, this is a key from a VNET definition stored in `vnets` variable<br>- `subnet_key`: a Subnet inside a VNET used to host Panorama VM, this is a key from a Subnet definition stored inside a VNET definition references by the `vnet_key` property<br>- `avzone`: when `enable_zones` is `true` this specifies the zone in which Panorama will be deployed<br>- `avzones`: when `enable_zones` is `true` these are availability zones used by Panorama's public IPs<br>- `custom_image_id`: a custom build of Panorama to use, overrides the stock image version.<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>- `logging_disks` : a map containing configuration of additional disks that should be attached to a Panorama appliance. Following properties are available:<br> - `size` : size of a disk, 2TB by default<br> - `lun` : slot to which the disk should be attached<br> - `disk_type` : type of a disk, determines throughput, `Standard_LRS` by default.<br><br>Example:<pre>{<br> "pn-1" = {<br> name = "panorama01"<br> vnet_key = "vnet"<br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "panorama"<br> private_ip_address = "10.1.0.10"<br> create_pip = true<br> },<br> ]<br> }<br> }</pre> | `any` | `{}` | no | +| <a name="input_panoramas"></a> [panoramas](#input\_panoramas) | A map containing Panorama definitions.<br><br>All definitions share a VM size, SKU and PAN-OS version (`panorama_size`, `panorama_sku`, `panorama_version` respectively). Defining more than one Panorama makes sense when creating for example HA pairs. <br><br>Following properties are available:<br><br>- `name` : a name of a Panorama VM<br>- `size` : size of the Panorama virtual machine, when specified overrides `var.panorama_size`.<br>- `version` : PAN-OS version, when specified overrides `var.panorama_version`.<br>- `vnet_key`: a VNET used to host Panorama VM, this is a key from a VNET definition stored in `vnets` variable<br>- `subnet_key`: a Subnet inside a VNET used to host Panorama VM, this is a key from a Subnet definition stored inside a VNET definition references by the `vnet_key` property<br>- `avzone`: when `enable_zones` is `true` this specifies the zone in which Panorama will be deployed<br>- `avzones`: when `enable_zones` is `true` these are availability zones used by Panorama's public IPs<br>- `custom_image_id`: a custom build of Panorama to use, overrides the stock image version.<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>- `logging_disks` : a map containing configuration of additional disks that should be attached to a Panorama appliance. Following properties are available:<br> - `size` : size of a disk, 2TB by default<br> - `lun` : slot to which the disk should be attached<br> - `disk_type` : type of a disk, determines throughput, `Standard_LRS` by default.<br><br>Example:<pre>{<br> "pn-1" = {<br> name = "panorama01"<br> vnet_key = "vnet"<br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "panorama"<br> private_ip_address = "10.1.0.10"<br> create_pip = true<br> },<br> ]<br> }<br> }</pre> | `any` | `{}` | no | ### Outputs diff --git a/examples/standalone_vmseries/.header.md b/examples/standalone_vmseries/.header.md new file mode 100644 index 00000000..c625483b --- /dev/null +++ b/examples/standalone_vmseries/.header.md @@ -0,0 +1,111 @@ +--- +show_in_hub: false +--- +# Palo Alto Networks Next Generation deployment example + +An example of a Terraform module that deploys a Next Generation Firewall appliance in Azure. + +**NOTE:** + +- after the deployment firewall remains not licensed and not configured +- this example contains some **files*- that **can contain sensitive data**, namely the `TFVARS` file can contain bootstrap_options + properties in `var.vmseries` definition. Keep in mind that **this code*- is **only an example**. It's main purpose is to + introduce the Terraform modules. It's not meant to be run on production in this form. + +## Topology and resources + +This is a non zonal deployment. The deployed infrastructure consists of: + +- a VNET containing: + - one subnet dedicated to the management interface of the deployed firewall + - a Network Security Group to give access to firewalls's public interface +- a firewall appliance with a public IP assigned to the management interface + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) + +## Deploy the infrastructure + +Steps to deploy the infrastructure are as following: + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs + (take a closer look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + Apply complete! Resources: 10 added, 0 changed, 0 destroyed. + + Outputs: + + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-1" = "1.2.3.4" + } + ``` + +- at this stage you have to wait couple of minutes for the firewall to bootstrap. + +## Post deploy + +Firewall in this example is configured with password authentication. To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output username + ``` + +- for password: + + ```bash + terraform output password + ``` + +The management public IP addresses are available in the `vmseries_mgmt_ips`: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the device using either: + +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +You can now proceed with licensing and configuring the device. + +## Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index f507aeff..63d639fe 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -1,5 +1,6 @@ +<!-- BEGIN_TF_DOCS --> --- -show_in_hub: false +show\_in\_hub: false --- # Palo Alto Networks Next Generation deployment example @@ -7,81 +8,98 @@ An example of a Terraform module that deploys a Next Generation Firewall applian **NOTE:** -* after the deployment firewall remains not licensed and not configured -* this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain bootstrap_options properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. It's not meant to be run on production in this form. +- after the deployment firewall remains not licensed and not configured +- this example contains some **files*- that **can contain sensitive data**, namely the `TFVARS` file can contain bootstrap\_options + properties in `var.vmseries` definition. Keep in mind that **this code*- is **only an example**. It's main purpose is to + introduce the Terraform modules. It's not meant to be run on production in this form. ## Topology and resources This is a non zonal deployment. The deployed infrastructure consists of: -* a VNET containing: - * one subnet dedicated to the management interface of the deployed firewall - * a Network Security Group to give access to firewalls's public interface -* a firewall appliance with a public IP assigned to the management interface +- a VNET containing: + - one subnet dedicated to the management interface of the deployed firewall + - a Network Security Group to give access to firewalls's public interface +- a firewall appliance with a public IP assigned to the management interface ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) ## Deploy the infrastructure Steps to deploy the infrastructure are as following: -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs + (take a closer look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - Apply complete! Resources: 10 added, 0 changed, 0 destroyed. + ```console + Apply complete! Resources: 10 added, 0 changed, 0 destroyed. - Outputs: + Outputs: - password = <sensitive> - username = "panadmin" - vmseries_mgmt_ips = { - "fw-1" = "1.2.3.4" - } + password = <sensitive> + username = "panadmin" + vmseries_mgmt_ips = { + "fw-1" = "1.2.3.4" + } + ``` -* at this stage you have to wait couple of minutes for the firewall to bootstrap. +- at this stage you have to wait couple of minutes for the firewall to bootstrap. ## Post deploy Firewall in this example is configured with password authentication. To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output username + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output password + ``` The management public IP addresses are available in the `vmseries_mgmt_ips`: -```sh +```bash terraform output vmseries_mgmt_ips ``` You can now login to the device using either: -* cli - ssh client is required -* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. You can now proceed with licensing and configuring the device. @@ -89,84 +107,922 @@ You can now proceed with licensing and configuring the device. To remove the deployed infrastructure run: -```sh +```bash terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_http"></a> [http](#provider\_http) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | -| <a name="provider_local"></a> [local](#provider\_local) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_natgw"></a> [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_bootstrap"></a> [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| <a name="module_bootstrap_share"></a> [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| <a name="module_vmseries"></a> [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | -| <a name="module_appgw"></a> [appgw](#module\_appgw) | ../../modules/appgw | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_availability_set.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [local_file.bootstrap_xml](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [http_http.this](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable. | `string` | n/a | yes | -| <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_availability_sets"></a> [availability\_sets](#input\_availability\_sets) | A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used.<br><br>Following properties are supported:<br>- `name` - name of the Application Insights.<br>- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults).<br>- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults).<br><br>Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. | `any` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VMSeries VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_bootstrap_storage"></a> [bootstrap\_storage](#input\_bootstrap\_storage) | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property.<br><br>Following properties are supported (except for name, all are optional):<br><br>- `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`.<br>- `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account.<br>- `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist.<br>- `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`.<br>- `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly.<br>- `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tried to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files are succuessfuly uploaded to the Storage Account.<br><br>The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence:<br>- `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br>- `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br>- `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br>- `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). | `any` | `{}` | no | -| <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported:<br><br>- `name` : name of the VMSeries virtual machine.<br>- `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PanOS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation.<br>- `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway.<br>- `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1".<br>- `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment.<br><br>- `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage`<br>- `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported:<br> - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account<br> - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package<br> - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below.<br> - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet.<br> - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet.<br> - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used.<br> - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes).<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>Example:<pre>{<br> "fw01" = {<br> name = "firewall01"<br> bootstrap_storage = {<br> name = "storageaccountname"<br> static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" }<br> template_bootstrap_xml = "templates/bootstrap_common.tmpl"<br> public_snet_key = "public"<br> private_snet_key = "private"<br> }<br> avzone = 1<br> vnet_key = "trust"<br> interfaces = [<br> {<br> name = "mgmt"<br> subnet_key = "mgmt"<br> create_pip = true<br> private_ip_address = "10.0.0.1"<br> },<br> {<br> name = "trust"<br> subnet_key = "private"<br> private_ip_address = "10.0.1.1"<br> load_balancer_key = "private_lb"<br> },<br> {<br> name = "untrust"<br> subnet_key = "public"<br> private_ip_address = "10.0.2.1"<br> load_balancer_key = "public_lb"<br> public_ip_name = "existing_public_ip"<br> }<br> ]<br> }<br>}</pre> | `any` | n/a | yes | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VMSeries interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_natgw_public_ips"></a> [natgw\_public\_ips](#output\_natgw\_public\_ips) | Nat Gateways Public IP resources. | -| <a name="output_metrics_instrumentation_keys"></a> [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -| <a name="output_vmseries_mgmt_ips"></a> [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for the VMSeries management interface. | -| <a name="output_bootstrap_storage_urls"></a> [bootstrap\_storage\_urls](#output\_bootstrap\_storage\_urls) | n/a | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. +[`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial administrative username to use for VM-Series. +`passwords` | Initial administrative password to use for VM-Series. +`natgw_public_ips` | Nat Gateways Public IP resources. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. +`vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. +`bootstrap_storage_urls` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.2, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` +- `local` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`appgw` | - | ../../modules/appgw | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### enable_zones + +If `true`, enable zone support for resources. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### natgws + +A map defining NAT Gateways. + +Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one +explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. +For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + +Following properties are supported: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### load_balancers + +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` + + +Type: + +```hcl +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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### availability_sets + +A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + +Following properties are supported: +- `name` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). +Please verify how many update and fault domain are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### bootstrap_storages + +A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + +You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to +[module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + +- `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. +- `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. +- `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vmseries + +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + +For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + +The most basic properties are as follows: + +- `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + + +Type: + +```hcl +map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + +Following properties are supported: +- `name` - (`string`, required) name of the Application Gateway. +- `public_ip` - (`string`, required) public IP address. +- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. +- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. +- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. +- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) +- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported +- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway +- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. +- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name +- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool +- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) +- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) +- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) +- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) +- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` +- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` +- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + + +Type: + +```hcl +map(object({ + name = string + public_ip = object({ + name = string + resource_group_name = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index f2900c71..2fe69507 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -6,8 +6,6 @@ tags = { "CreatedBy" = "Palo Alto Networks" "CreatedWith" = "Terraform" } -enable_zones = false - # --- VNET PART --- # vnets = { @@ -44,19 +42,23 @@ vnets = { # --- VMSERIES PART --- # -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" vmseries = { "fw-1" = { - name = "firewall01" - bootstrap_options = "type=dhcp-client" - vnet_key = "transit" + name = "firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + bootstrap_options = "type=dhcp-client" + vnet_key = "transit" + zone = null + } interfaces = [ { - name = "mgmt" - subnet_key = "management" - create_pip = true - }, + name = "vm-mgmt" + subnet_key = "management" + create_public_ip = true + } ] } } diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 2100f7e2..031912b2 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -1,6 +1,8 @@ # Generate a random password. resource "random_password" "this" { - count = var.vmseries_password == null ? 1 : 0 + count = anytrue([ + for _, v in var.vmseries : v.authentication.password == null + ]) ? 1 : 0 length = 16 min_lower = 16 - 4 @@ -11,14 +13,16 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) -} - -# Obtain Public IP address of code deployment machine - -data "http" "this" { - count = length(var.bootstrap_storage) > 0 && anytrue([for v in values(var.bootstrap_storage) : try(v.storage_acl, false)]) ? 1 : 0 - url = "https://ifconfig.me/ip" + authentication = { + for k, v in var.vmseries : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } } # Create or source the Resource Group. @@ -131,8 +135,7 @@ module "load_balancer" { - -# create the actual VMSeries VMs and resources +# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -155,39 +158,35 @@ module "ngfw_metrics" { } resource "local_file" "bootstrap_xml" { - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage.template_bootstrap_xml) } + for_each = { + for k, v in var.vmseries : + k => v.virtual_machine + if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + } - filename = "files/${each.value.name}-bootstrap.xml" + filename = "files/${each.key}-bootstrap.xml" content = templatefile( - each.value.bootstrap_storage.template_bootstrap_xml, + each.value.bootstrap_package.bootstrap_xml_template, { private_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.private_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].private_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.private_snet_key], 1 ) public_azure_router_ip = cidrhost( - try( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_storage.public_snet_key], - module.vnet[each.value.vnet_key].subnet_cidrs[var.bootstrap_storage[each.value.bootstrap_storage.name].public_snet_key] - ), + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.public_snet_key], 1 ) - ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) - - ai_update_interval = try( - each.value.bootstrap_storage.ai_update_interval, - var.bootstrap_storage[each.value.bootstrap_storage.name].ai_update_interval, - 5 + ai_instr_key = try( + module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], + null ) - private_network_cidr = try( - each.value.bootstrap_storage.intranet_cidr, - var.bootstrap_storage[each.value.bootstrap_storage.name].intranet_cidr, + ai_update_interval = each.value.bootstrap_package.ai_update_interval + + private_network_cidr = coalesce( + each.value.bootstrap_package.intranet_cidr, module.vnet[each.value.vnet_key].vnet_cidr[0] ) @@ -203,61 +202,62 @@ resource "local_file" "bootstrap_xml" { ] } -module "bootstrap" { - source = "../../modules/bootstrap" - - for_each = var.bootstrap_storage - - create_storage_account = try(each.value.create_storage, true) - name = each.value.name - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location - storage_acl = try(each.value.storage_acl, false) - storage_allow_vnet_subnet_ids = try(flatten([for v in each.value.storage_allow_vnet_subnets : [module.vnet[v.vnet_key].subnet_ids[v.subnet_key]]]), []) - storage_allow_inbound_public_ips = concat(try(each.value.storage_allow_inbound_public_ips, []), try([data.http.this[0].response_body], [])) - - tags = var.tags +locals { + bootstrap_file_shares_flat = flatten([ + for k, v in var.vmseries : + merge(v.virtual_machine.bootstrap_package, { vm_key = k }) + if v.virtual_machine.bootstrap_package != null + ]) + + bootstrap_file_shares = { for k, v in var.bootstrap_storages : k => { + for file_share in local.bootstrap_file_shares_flat : file_share.vm_key => { + name = file_share.vm_key + bootstrap_package_path = file_share.bootstrap_package_path + bootstrap_files = merge( + file_share.static_files, + file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = "config/bootstrap.xml" + } + ) + bootstrap_files_md5 = file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = local_file.bootstrap_xml[file_share.vm_key].content_md5 + } + } if file_share.bootstrap_storage_key == k } + } } -module "bootstrap_share" { +module "bootstrap" { source = "../../modules/bootstrap" - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage) } + for_each = var.bootstrap_storages - create_storage_account = false - name = module.bootstrap[each.value.bootstrap_storage.name].storage_account.name - resource_group_name = try(var.bootstrap_storage[each.value.bootstrap_storage].resource_group_name, local.resource_group.name) - location = var.location - storage_share_name = each.key - files = merge( - each.value.bootstrap_storage.static_files, - can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = "config/bootstrap.xml" - } : {} - ) + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} + storage_network_security = merge( + each.value.storage_network_security, + each.value.storage_network_security.vnet_key == null ? {} : { + allowed_subnet_ids = [ + for v in each.value.storage_network_security.allowed_subnet_keys : + module.vnet[each.value.storage_network_security.vnet_key].subnet_ids[v] + ] } + ) + file_shares_configuration = each.value.file_shares_configuration + file_shares = local.bootstrap_file_shares[each.key] tags = var.tags - - depends_on = [ - local_file.bootstrap_xml, - module.bootstrap - ] } - - resource "azurerm_availability_set" "this" { for_each = var.availability_sets name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location - platform_update_domain_count = try(each.value.update_domain_count, null) - platform_fault_domain_count = try(each.value.fault_domain_count, null) + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count tags = var.tags } @@ -267,47 +267,50 @@ module "vmseries" { for_each = var.vmseries + name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - name = "${var.name_prefix}${each.value.name}" - username = var.vmseries_username - password = local.vmseries_password - img_version = try(each.value.version, var.vmseries_version) - img_sku = var.vmseries_sku - vm_size = try(each.value.vm_size, var.vmseries_vm_size) - avset_id = try(azurerm_availability_set.this[each.value.availability_set_key].id, null) - - enable_zones = var.enable_zones - avzone = try(each.value.avzone, 1) - bootstrap_options = try( - each.value.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.name}", - "access-key=${module.bootstrap[each.value.bootstrap_storage.name].storage_account.primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), - "" + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + bootstrap_options = try( + coalesce( + each.value.virtual_machine.bootstrap_options, + join(",", [ + "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + ), + null + ) + } ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${each.value.name}-${v.name}" - subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) - create_public_ip = try(v.create_pip, false) - public_ip_name = try(v.public_ip_name, null) - public_ip_resource_group = try(v.public_ip_resource_group, null) - enable_backend_pool = can(v.load_balancer_key) ? true : false - lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) - private_ip_address = try(v.private_ip_address, null) + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address + attach_to_lb_backend_pool = v.load_balancer_key != null + lb_backend_pool_id = try(module.load_balancer[v.load_balancer_key].backend_pool_id, null) + }] tags = var.tags depends_on = [ module.vnet, azurerm_availability_set.this, + module.load_balancer, module.bootstrap, - module.bootstrap_share ] } @@ -334,7 +337,7 @@ module "appgw" { name = "vmseries" vmseries_ips = [ for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name ].private_ip_address if try(v.add_to_appgw_backend, false) ] } @@ -350,4 +353,4 @@ module "appgw" { tags = var.tags depends_on = [module.vnet] -} +} \ No newline at end of file diff --git a/examples/standalone_vmseries/outputs.tf b/examples/standalone_vmseries/outputs.tf index 10533a56..f946a80b 100644 --- a/examples/standalone_vmseries/outputs.tf +++ b/examples/standalone_vmseries/outputs.tf @@ -1,11 +1,11 @@ -output "username" { +output "usernames" { description = "Initial administrative username to use for VM-Series." - value = var.vmseries_username + value = { for k, v in local.authentication : k => v.username } } -output "password" { +output "passwords" { description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password + value = { for k, v in local.authentication : k => v.password } sensitive = true } @@ -29,11 +29,11 @@ output "lb_frontend_ips" { } output "vmseries_mgmt_ips" { - description = "IP addresses for the VMSeries management interface." + description = "IP addresses for the VM-Series management interface." value = { for k, v in module.vmseries : k => v.mgmt_ip_address } } output "bootstrap_storage_urls" { - value = length(var.bootstrap_storage) > 0 ? { for k, v in module.bootstrap_share : k => v.storage_share.url } : null + value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index afeeb471..a0bf9103 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -13,14 +13,17 @@ 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: ``` 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. + **Note!** \ + 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 @@ -28,7 +31,9 @@ variable "name_prefix" { 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 `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 @@ -79,8 +84,7 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -98,7 +102,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -243,14 +248,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, false) - public_ip_resource_group = optional(string) - vnet_key = optional(string) - subnet_key = optional(string) - private_ip_address = optional(string) - gwlb_key = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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 @@ -273,36 +278,6 @@ variable "load_balancers" { } - -### GENERIC VMSERIES -variable "vmseries_version" { - description = "VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per firewall, see `var.vmseries` variable." - type = string -} - -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} - -variable "vmseries_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - type = string -} - -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} - variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. @@ -312,12 +287,19 @@ variable "availability_sets" { - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. + Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). + Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) } + variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -352,100 +334,273 @@ variable "ngfw_metrics" { }) } -variable "bootstrap_storage" { +variable "bootstrap_storages" { description = <<-EOF - A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property. - - Following properties are supported (except for name, all are optional): - - - `name` : name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`. - - `create_storage_account` : (defaults to `true`) create or source (when `false`) an existing Storage Account. - - `resource_group_name` : (defaults to `var.resource_group_name`) name of the Resource Group hosting the Storage Account (existing or newly created). The RG has to exist. - - `storage_acl` : (defaults to `false`) enables network ACLs on the Storage Account. If this is enabled - `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. The ACL defaults to default `Deny`. - - `storage_allow_vnet_subnets` : (defaults to `[]`) whitelist containing the allowed vnet and associated subnets that are allowed to access the Storage Account. Note that the respective subnets require `enable_storage_service_endpoint` set to `true` to work properly. - - `storage_allow_inbound_public_ips` : (defaults to `[]`) whitelist containing the allowed public IP subnets that can access the Storage Account. Note that the code automatically tries to query https://ifconfig.me/ip to obtain the public IP address of the machine executing the code so that the bootstrap files can be successfully uploaded to the Storage Account. - - The properties below do not directly change anything in the Storage Account settings. They can be used to control common parts of the `DAY0` configuration (used only when full bootstrap is used). These properties can also be specified per firewall, but when specified here they tak higher precedence: - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). + A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + + You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to + [module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). EOF default = {} - type = any + nullable = false + type = map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) } variable "vmseries" { description = <<-EOF - Map of virtual machines to create to run VM-Series - inbound firewalls. Following properties are supported: - - - `name` : name of the VMSeries virtual machine. - - `vm_size` : size of the VMSeries virtual machine, when specified overrides `var.vmseries_vm_size`. - - `version` : PanOS version, when specified overrides `var.vmseries_version`. - - `vnet_key` : a key of a VNET defined in the `var.vnets` map. This value will be used during network interfaces creation. - - `add_to_appgw_backend` : bool, `false` by default, set this to `true` to add this backend to an Application Gateway. - - `avzone`: the Azure Availability Zone identifier ("1", "2", "3"). Default is "1". - - `availability_set_key` : a key of an Availability Set as declared in `availability_sets` property. Specify when HA is required but cannot go for zonal deployment. - - - `bootstrap_options` : string, optional bootstrap options to pass to VM-Series instances, semicolon separated values. When defined this precedence over `bootstrap_storage` - - `bootstrap_storage` : a map containing definition of the bootstrap package content. When present triggers a creation of a File Share in an existing Storage Account, following properties supported: - - `name` : a name of a key in `var.bootstrap_storage` variable defining a Storage Account - - `static_files` : a map where key is a path to a file, value is the location of the file in the bootstrap package (file share). All files in this map are copied 1:1 to the bootstrap package - - `template_bootstrap_xml` : path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and the file will be uploaded to the storage account. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in this case place them in the bootstrap storage definition). The properties are listed below. - - `public_snet_key` : required, name of the key in `var.vnets` map defining a public subnet, required to calculate the Azure router IP for the public subnet. - - `private_snet_key` : required, name of the key in `var.vnets` map defining a private subnet, required to calculate the Azure router IP for the private subnet. - - `intranet_cidr` : optional, CIDR of the private networks required to build a general static route to resources protected by this firewall, when skipped the 1st CIDR from `vnet_name` address space will be used. - - `ai_update_interval` : if Application Insights are used this property can override the default metrics update interval (in minutes). - - - `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available: - - `name`: string that will form the NIC name - - `subnet_key` : (string) a key of a subnet as defined in `var.vnets` - - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false` - - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface - - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name` - - `load_balancer_key` : (string) key of a Load Balancer defined in the `var.loadbalancers` variable, defaults to `null` - - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used) + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + + For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + + The most basic properties are as follows: + + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. - Example: - ``` - { - "fw01" = { - name = "firewall01" - bootstrap_storage = { - name = "storageaccountname" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap_common.tmpl" - public_snet_key = "public" - private_snet_key = "private" - } - avzone = 1 - vnet_key = "trust" - interfaces = [ - { - name = "mgmt" - subnet_key = "mgmt" - create_pip = true - private_ip_address = "10.0.0.1" - }, - { - name = "trust" - subnet_key = "private" - private_ip_address = "10.0.1.1" - load_balancer_key = "private_lb" - }, - { - name = "untrust" - subnet_key = "public" - private_ip_address = "10.0.2.1" - load_balancer_key = "public_lb" - public_ip_name = "existing_public_ip" - } - ] - } - } - ``` EOF + default = {} + nullable = false + type = map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || + v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null + ]) + error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + } + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true + if v.virtual_machine.bootstrap_package != null + ]) + error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + } } ### Application Gateway diff --git a/examples/virtual_network_gateway/.header.md b/examples/virtual_network_gateway/.header.md deleted file mode 100644 index 190b9594..00000000 --- a/examples/virtual_network_gateway/.header.md +++ /dev/null @@ -1,5 +0,0 @@ -# VNG module sample - -A sample of using a VNG module with the new variables layout and usage of `optional` keyword. - -The `README` is also in new, document-style format. diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md deleted file mode 100644 index 586f1605..00000000 --- a/examples/virtual_network_gateway/README.md +++ /dev/null @@ -1,297 +0,0 @@ -<!-- BEGIN_TF_DOCS --> -# VNG module sample - -A sample of using a VNG 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. -[`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of virtual_network_gateways to create. - - -## 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 Outputs - -Name | Description ---- | --- -`vng_public_ips` | IP Addresses of the VNGs. -`vng_ipsec_policy` | IPsec policy used for Virtual Network Gateway connection - -## 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. -`vng` | - | ../../modules/virtual_network_gateway | Create virtual network gateway - - -Resources used in this module: - -- `resource_group` (managed) -- `resource_group` (data) - -## Inputs/Outpus details - -### Required Inputs - - - -#### location - -The Azure region to use. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - - - -#### resource_group_name - -Name of the Resource Group. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### 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) - })), {}) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - -#### virtual_network_gateways - -Map of virtual_network_gateways to create - -Type: - -```hcl -map(object({ - name = string - zones = optional(list(string)) - type = optional(string) - vpn_type = optional(string) - sku = optional(string) - - active_active = optional(bool) - default_local_network_gateway_id = optional(string) - edge_zone = optional(string) - enable_bgp = optional(bool) - generation = optional(string) - private_ip_address_enabled = optional(bool) - - ip_configuration = list(object({ - name = optional(string) - create_public_ip = bool - public_ip_name = optional(string) - private_ip_address_allocation = optional(string, "Dynamic") - vnet_key = string - subnet_name = string - })) - - vpn_client_configuration = optional(list(object({ - address_space = string - aad_tenant = optional(string) - aad_audience = optional(string) - aad_issuer = optional(string) - root_certificate = optional(object({ - name = string - public_cert_data = string - })) - revoked_certificate = optional(object({ - name = string - thumbprint = string - })) - radius_server_address = optional(string) - radius_server_secret = optional(string) - vpn_client_protocols = optional(list(string)) - vpn_auth_types = optional(list(string)) - })), []) - azure_bgp_peers_addresses = map(string) - local_bgp_settings = object({ - asn = string - peering_addresses = map(object({ - apipa_addresses = list(string) - default_addresses = optional(list(string)) - })) - peer_weight = optional(number) - }) - custom_route = optional(list(object({ - address_prefixes = optional(list(string)) - })), []) - ipsec_shared_key = optional(string) - local_network_gateways = map(object({ - local_ng_name = string - connection_name = string - remote_bgp_settings = optional(list(object({ - asn = string - bgp_peering_address = string - peer_weight = optional(number) - }))) - gateway_address = optional(string) - address_space = optional(list(string)) - custom_bgp_addresses = optional(list(object({ - primary = string - secondary = optional(string) - }))) - })) - connection_mode = optional(string) - ipsec_policies = list(object({ - dh_group = string - ike_encryption = string - ike_integrity = string - ipsec_encryption = string - ipsec_integrity = string - pfs_group = string - sa_datasize = optional(string) - sa_lifetime = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - - - -### Optional Inputs - - -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -#### 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: `` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### 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` - -<sup>[back to list](#modules-optional-inputs)</sup> - - - - - -<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars deleted file mode 100644 index 88b54484..00000000 --- a/examples/virtual_network_gateway/example.tfvars +++ /dev/null @@ -1,127 +0,0 @@ -# --- GENERAL --- # -location = "North Europe" -resource_group_name = "vng-example" -name_prefix = "sczech-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - - -# --- VNET PART --- # -vnets = { - transit = { - name = "transit" - address_space = ["10.0.0.0/24"] - network_security_groups = {} - route_tables = { - "rt" = { - name = "rt" - routes = { - "udr" = { - name = "udr" - address_prefix = "10.0.0.0/8" - next_hop_type = "None" - } - } - } - } - subnets = { - "GatewaySubnet" = { - name = "GatewaySubnet" - address_prefixes = ["10.0.0.0/25"] - route_table_key = "rt" - } - } - } -} - -# --- VNG PART --- # -virtual_network_gateways = { - "vng" = { - name = "vng" - type = "Vpn" - sku = "VpnGw2AZ" - generation = "Generation2" - active_active = true - enable_bgp = true - zones = ["1", "2", "3"] - ip_configuration = [ - { - name = "001" - create_public_ip = true - public_ip_name = "pip1" - vnet_key = "transit" - subnet_name = "GatewaySubnet" - }, - { - name = "002" - create_public_ip = true - public_ip_name = "pip2" - vnet_key = "transit" - subnet_name = "GatewaySubnet" - } - ] - ipsec_shared_key = "test123" - azure_bgp_peers_addresses = { - primary_1 = "169.254.21.2" - secondary_1 = "169.254.22.2" - } - local_bgp_settings = { - asn = "65002" - peering_addresses = { - "001" = { - apipa_addresses = ["primary_1"] - }, - "002" = { - apipa_addresses = ["secondary_1"] - } - } - } - local_network_gateways = { - "lg1" = { - local_ng_name = "lg1" - connection_name = "cn1" - gateway_address = "8.8.8.8" - remote_bgp_settings = [{ - asn = "65000" - bgp_peering_address = "169.254.21.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - }, - "lg2" = { - local_ng_name = "lg2" - connection_name = "cn2" - gateway_address = "4.4.4.4" - remote_bgp_settings = [{ - asn = "65000" - bgp_peering_address = "169.254.22.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - } - } - connection_mode = "InitiatorOnly" - ipsec_policies = [ - { - dh_group = "ECP384" - ike_encryption = "AES256" - ike_integrity = "SHA256" - ipsec_encryption = "AES256" - ipsec_integrity = "SHA256" - pfs_group = "ECP384" - sa_datasize = "102400000" - sa_lifetime = "14400" - } - ] - } -} \ No newline at end of file diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf deleted file mode 100644 index 4bbcdee8..00000000 --- a/examples/virtual_network_gateway/main.tf +++ /dev/null @@ -1,77 +0,0 @@ -# Create or source the Resource Group. -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - name = var.resource_group_name -} - -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - network_security_groups = each.value.network_security_groups - route_tables = each.value.route_tables - - tags = var.tags -} - -# Create virtual network gateway -module "vng" { - source = "../../modules/virtual_network_gateway" - - for_each = var.virtual_network_gateways - - location = var.location - resource_group_name = local.resource_group.name - name = each.value.name - zones = each.value.zones - - type = each.value.type - vpn_type = each.value.vpn_type - sku = each.value.sku - - active_active = each.value.active_active - default_local_network_gateway_id = each.value.default_local_network_gateway_id - edge_zone = each.value.edge_zone - enable_bgp = each.value.enable_bgp - generation = each.value.generation - private_ip_address_enabled = each.value.private_ip_address_enabled - - ip_configuration = [ - for ip_configuration in each.value.ip_configuration : - merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) - ] - - vpn_client_configuration = each.value.vpn_client_configuration - azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses - local_bgp_settings = each.value.local_bgp_settings - custom_route = each.value.custom_route - ipsec_shared_key = each.value.ipsec_shared_key - local_network_gateways = each.value.local_network_gateways - connection_mode = each.value.connection_mode - ipsec_policies = each.value.ipsec_policies - - tags = var.tags -} \ No newline at end of file diff --git a/examples/virtual_network_gateway/main_test.go b/examples/virtual_network_gateway/main_test.go deleted file mode 100644 index 97d2facd..00000000 --- a/examples/virtual_network_gateway/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package vng - -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/virtual_network_gateway/outputs.tf b/examples/virtual_network_gateway/outputs.tf deleted file mode 100644 index 469cc0fd..00000000 --- a/examples/virtual_network_gateway/outputs.tf +++ /dev/null @@ -1,9 +0,0 @@ -output "vng_public_ips" { - description = "IP Addresses of the VNGs." - value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.public_ip } : null -} - -output "vng_ipsec_policy" { - description = "IPsec policy used for Virtual Network Gateway connection" - value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.ipsec_policy } : null -} \ No newline at end of file diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf deleted file mode 100644 index 9e1d6392..00000000 --- a/examples/virtual_network_gateway/variables.tf +++ /dev/null @@ -1,192 +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) - })), {}) - })) -} - -### Virtual Network Gateway -variable "virtual_network_gateways" { - description = "Map of virtual_network_gateways to create" - type = map(object({ - name = string - zones = optional(list(string)) - type = optional(string) - vpn_type = optional(string) - sku = optional(string) - - active_active = optional(bool) - default_local_network_gateway_id = optional(string) - edge_zone = optional(string) - enable_bgp = optional(bool) - generation = optional(string) - private_ip_address_enabled = optional(bool) - - ip_configuration = list(object({ - name = optional(string) - create_public_ip = bool - public_ip_name = optional(string) - private_ip_address_allocation = optional(string, "Dynamic") - vnet_key = string - subnet_name = string - })) - - vpn_client_configuration = optional(list(object({ - address_space = string - aad_tenant = optional(string) - aad_audience = optional(string) - aad_issuer = optional(string) - root_certificate = optional(object({ - name = string - public_cert_data = string - })) - revoked_certificate = optional(object({ - name = string - thumbprint = string - })) - radius_server_address = optional(string) - radius_server_secret = optional(string) - vpn_client_protocols = optional(list(string)) - vpn_auth_types = optional(list(string)) - })), []) - azure_bgp_peers_addresses = map(string) - local_bgp_settings = object({ - asn = string - peering_addresses = map(object({ - apipa_addresses = list(string) - default_addresses = optional(list(string)) - })) - peer_weight = optional(number) - }) - custom_route = optional(list(object({ - address_prefixes = optional(list(string)) - })), []) - ipsec_shared_key = optional(string) - local_network_gateways = map(object({ - local_ng_name = string - connection_name = string - remote_bgp_settings = optional(list(object({ - asn = string - bgp_peering_address = string - peer_weight = optional(number) - }))) - gateway_address = optional(string) - address_space = optional(list(string)) - custom_bgp_addresses = optional(list(object({ - primary = string - secondary = optional(string) - }))) - })) - connection_mode = optional(string) - ipsec_policies = list(object({ - dh_group = string - ike_encryption = string - ike_integrity = string - ipsec_encryption = string - ipsec_integrity = string - pfs_group = string - sa_datasize = optional(string) - sa_lifetime = optional(string) - })) - })) -} diff --git a/examples/virtual_network_gateway/versions.tf b/examples/virtual_network_gateway/versions.tf deleted file mode 100644 index 95b07f02..00000000 --- a/examples/virtual_network_gateway/versions.tf +++ /dev/null @@ -1,22 +0,0 @@ -terraform { - required_version = ">= 1.2, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - random = { - source = "hashicorp/random" - } - http = { - source = "hashicorp/http" - } - } -} - -provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} diff --git a/modules/appgw/README.md b/modules/appgw/README.md index e80bb504..daa2d5fd 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -1387,7 +1387,7 @@ Backend pool. Object contains attributes: - `name` - (`string`, required) name of the backend pool. -- `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VMSeries' interfaces that will serve as backends +- `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backends for the Application Gateway. diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index a9bc9430..e520ab16 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -89,7 +89,7 @@ resource "azurerm_application_gateway" "this" { public_ip_address_id = var.public_ip.create ? azurerm_public_ip.this[0].id : data.azurerm_public_ip.this[0].id } - # There is only a single backend - the VMSeries private IPs assigned to untrusted NICs + # There is only a single backend - the VM-Series private IPs assigned to untrusted NICs backend_address_pool { name = var.backend_pool.name ip_addresses = var.backend_pool.vmseries_ips diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index f08e6a68..d18e7f85 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -384,7 +384,7 @@ variable "backend_pool" { Object contains attributes: - `name` - (`string`, required) name of the backend pool. - - `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VMSeries' interfaces that will serve as backends + - `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backends for the Application Gateway. EOF default = { diff --git a/modules/bootstrap/.header.md b/modules/bootstrap/.header.md new file mode 100644 index 00000000..f2891779 --- /dev/null +++ b/modules/bootstrap/.header.md @@ -0,0 +1,128 @@ +# Palo Alto Networks Bootstrap Module for Azure + +A terraform module for deploying a storage account and the dependencies required to +[bootstrap a VM-Series firewalls in Azure](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-the-vm-series-firewall-in-azure.html#idd51f75b8-e579-44d6-a809-2fafcfe4b3b6). + +It can create (or source an existing) Azure Storage Account and it can create (or source) multiple File Shares withing the Storage +Account and upload files to them. When creating File Shares each share will contain a folder structure required by the bootstrap +package. When sourcing existing shares, you can disable the folder structure creation, but keep in mind that the folders have to +present on the share before you try to upload any files to them. + +The file uploading can be done in two ways: + +1. either by specifying single files or +2. by providing a path to a local bootstrap package. + +Keep in mind that if you provide both, the former takes precedence by the latter, meaning that when uploaded, each single file +specification will override files from the local bootstrap package. + +## Usage + +For more *real life* code please check [examples folder](../../examples/). +The examples below are just showing 3 typical use cases. + +### Empty Storage account + +The module is used only to create a Storage Account with module defaults where possible. + +```hcl +module "empty_storage" { + source = "../../modules/bootstrap" + + name = "someemptystorage" + resource_group_name = "rg-name" + location = "North Europe" +} +``` + +### Full bootstrap storage + +This code will create a storage account for 3 NGFWs. Please **note** that: + +- we will override the default access tier from `Cool` to `Hot` and increase the default quota to 20GB +- we will lower the default TLS to 1.1 and limit access to the Storage Account to one public IP +- `vm01` and `vm02` will use a full bootstrap package stored locally under the `bootstrap_package` path +- for `vm01` we will additionally overwrite some files from the bootstrap package +- `vm03` will not use a full bootstrap package, we will upload just a single file to the Storage Account. Additionally we will + override the `access_tier` for this File Share to `Cool` and the quota to 1GB. + +```hcl +module "bootstrap" { + source = "../../modules/bootstrap" + + name = "samplebootstrapstorage" + resource_group_name = "rg-name" + location = "North Europe" + + file_shares_configuration = { + access_tier = "Hot" + quota = 20 + } + storage_network_security = { + min_tls_version = "TLS1_1" + allowed_public_ips = ["1.2.3.4"] + } + file_shares = { + "vm01" = { + name = "vm01" + bootstrap_package_path = "bootstrap_package" + bootstrap_files = { + "files/init-cfg.txt" = "config/init-cfg.txt" + "files/nested/bootstrap.xml" = "config/bootstrap.xml" + } + } + "vm02" = { + name = "vm02" + bootstrap_package_path = "./bootstrap_package/" + } + "vm03" = { + name = "vm03" + access_tier = "Cool" + quota = 1 + bootstrap_files = { + "files/init-cfg.txt" = "config/init-cfg.txt" + } + } + } +} +``` + +### Source existing Storage Account and File Share + +The sample below shows how to source an existing Storage Account with an existing File Share. + +Please **note** that we will also skip bootstrap package folder structure creation. The sourced File Share should have this folder +structure already present. + +```hcl +module "existing_storage" { + source = "../../modules/bootstrap" + + storage_account = { + create = false + } + name = "sampleexistingstorage" + resource_group_name = "rg-name" + + file_shares_configuration = { + create_file_shares = false + disable_package_dirs_creation = true + } + file_shares = { + existing_share = { + name = "bootstrap" + bootstrap_package_path = "bootstrap_package" + } + } +} +``` + +## MD5 file hashes + +This module uses MD5 hashes to verify file content change. This means that any file modification done between Terraform runs will +be discovered and the remote file will be overwritten. This has some implications though. + +The module can calculate hashes for the existing files - any files that were present before Terraform run. + +If however you are creating some files on the fly (templating for instance) you have to provide the MD5 hashes yourself. For more +details refer to the [var.file_shares](#file_shares) variable documentation. diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index b1bf3e3b..744187cc 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -1,106 +1,410 @@ +<!-- BEGIN_TF_DOCS --> # Palo Alto Networks Bootstrap Module for Azure -A terraform module for deploying a storage account and the dependencies required -to [bootstrap a VM-Series firewalls in Azure](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-the-vm-series-firewall-in-azure.html#idd51f75b8-e579-44d6-a809-2fafcfe4b3b6). +A terraform module for deploying a storage account and the dependencies required to +[bootstrap a VM-Series firewalls in Azure](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-the-vm-series-firewall-in-azure.html#idd51f75b8-e579-44d6-a809-2fafcfe4b3b6). -The module does *not* configure the bootstrap images, licenses, or configurations. +It can create (or source an existing) Azure Storage Account and it can create (or source) multiple File Shares withing the Storage +Account and upload files to them. When creating File Shares each share will contain a folder structure required by the bootstrap +package. When sourcing existing shares, you can disable the folder structure creation, but keep in mind that the folders have to +present on the share before you try to upload any files to them. + +The file uploading can be done in two ways: + +1. either by specifying single files or +2. by providing a path to a local bootstrap package. + +Keep in mind that if you provide both, the former takes precedence by the latter, meaning that when uploaded, each single file +specification will override files from the local bootstrap package. ## Usage -Simple example usage is shown below. For more *real life* code please check [examples folder](../../examples/). +For more *real life* code please check [examples folder](../../examples/). +The examples below are just showing 3 typical use cases. -```hcl -module "bootstrap" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/bootstrap" +### Empty Storage account - storage_account_name = "accountname" - resource_group_name = "rg-name" - location = "West US" +The module is used only to create a Storage Account with module defaults where possible. - storage_share_name = "vm_bootstrap" +```hcl +module "empty_storage" { + source = "../../modules/bootstrap" - files = { - "files/init-cfg.txt" = "config/init-cfg.txt" - "files/bootstrap.xml" = "config/bootstrap.xml" - } + name = "someemptystorage" + resource_group_name = "rg-name" + location = "North Europe" } ``` -## Known Limitations +### Full bootstrap storage -If a file does not exist because it is supposed to be generated by the same Terraform run, add a hash of its contents to -the input `files_md5` as a workaround. For example: +This code will create a storage account for 3 NGFWs. Please **note** that: -```hcl2 +- we will override the default access tier from `Cool` to `Hot` and increase the default quota to 20GB +- we will lower the default TLS to 1.1 and limit access to the Storage Account to one public IP +- `vm01` and `vm02` will use a full bootstrap package stored locally under the `bootstrap_package` path +- for `vm01` we will additionally overwrite some files from the bootstrap package +- `vm03` will not use a full bootstrap package, we will upload just a single file to the Storage Account. Additionally we will + override the `access_tier` for this File Share to `Cool` and the quota to 1GB. + +```hcl module "bootstrap" { - # ... - files = { (local_file.this.filename) = "config/dynamic-content-test.txt" } - files_md5 = { (local_file.this.filename) = md5(local_file.this.content) } + source = "../../modules/bootstrap" + + name = "samplebootstrapstorage" + resource_group_name = "rg-name" + location = "North Europe" + + file_shares_configuration = { + access_tier = "Hot" + quota = 20 + } + storage_network_security = { + min_tls_version = "TLS1_1" + allowed_public_ips = ["1.2.3.4"] + } + file_shares = { + "vm01" = { + name = "vm01" + bootstrap_package_path = "bootstrap_package" + bootstrap_files = { + "files/init-cfg.txt" = "config/init-cfg.txt" + "files/nested/bootstrap.xml" = "config/bootstrap.xml" + } + } + "vm02" = { + name = "vm02" + bootstrap_package_path = "./bootstrap_package/" + } + "vm03" = { + name = "vm03" + access_tier = "Cool" + quota = 1 + bootstrap_files = { + "files/init-cfg.txt" = "config/init-cfg.txt" + } + } + } } +``` + +### Source existing Storage Account and File Share + +The sample below shows how to source an existing Storage Account with an existing File Share. + +Please **note** that we will also skip bootstrap package folder structure creation. The sourced File Share should have this folder +structure already present. -resource "local_file" "this" { - filename = "test.txt" - content = "hello world" +```hcl +module "existing_storage" { + source = "../../modules/bootstrap" + + storage_account = { + create = false + } + name = "sampleexistingstorage" + resource_group_name = "rg-name" + + file_shares_configuration = { + create_file_shares = false + disable_package_dirs_creation = true + } + file_shares = { + existing_share = { + name = "bootstrap" + bootstrap_package_path = "bootstrap_package" + } + } } ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.1 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_storage_account.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource | -| [azurerm_storage_share.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share) | resource | -| [azurerm_storage_share_directory.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share_directory) | resource | -| [azurerm_storage_share_file.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share_file) | resource | -| [azurerm_storage_account.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/storage_account) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_create_storage_account"></a> [create\_storage\_account](#input\_create\_storage\_account) | If `true`, create a Storage Account. | `bool` | `true` | no | -| <a name="input_name"></a> [name](#input\_name) | Name of the Storage Account, either a new or an existing one (depending on the value of `create_storage_account`).<br><br>The name you choose must be unique across Azure. The name also must be between 3 and 24 characters in length, and may include only numbers and lowercase letters. | `string` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to use. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Region to deploy bootstrap resources. Ignored when `create_storage_account` is set to `false`. | `string` | `null` | no | -| <a name="input_min_tls_version"></a> [min\_tls\_version](#input\_min\_tls\_version) | The minimum supported TLS version for the storage account. | `string` | `"TLS1_2"` | no | -| <a name="input_files"></a> [files](#input\_files) | Map of all files to copy to bucket. The keys are local paths, the values are remote paths.<br>Always use slash `/` as directory separator (unix-like), not the backslash `\`.<br>Example:<pre>files = {<br> "dir/my.txt" = "config/init-cfg.txt"<br>}</pre> | `map(string)` | `{}` | no | -| <a name="input_bootstrap_files_dir"></a> [bootstrap\_files\_dir](#input\_bootstrap\_files\_dir) | Bootstrap file directory. If the variable has a value of `null` (default) - then it will not upload any other files other than the ones specified in the `files` variable. More information can be found at https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package. | `string` | `null` | no | -| <a name="input_files_md5"></a> [files\_md5](#input\_files\_md5) | Optional map of MD5 hashes of file contents.<br>Normally the map could be empty, because all the files that exist before the `terraform apply` will have their hashes auto-calculated.<br>This input is necessary only for the selected files which are created/modified within the same Terraform run as this module.<br>The keys of the map should be identical with selected keys of the `files` input, while the values should be MD5 hashes of the contents of that file.<br><br>Example:<pre>files_md5 = {<br> "dir/my.txt" = "6f7ce3191b50a58cc13e751a8f7ae3fd"<br>}</pre> | `map(string)` | `{}` | no | -| <a name="input_storage_share_name"></a> [storage\_share\_name](#input\_storage\_share\_name) | Name of a storage File Share to be created that will hold `files` used for bootstrapping.<br>For rules defining a valid name see [Microsoft documentation](https://docs.microsoft.com/en-us/rest/api/storageservices/Naming-and-Referencing-Shares--Directories--Files--and-Metadata#share-names). | `string` | `null` | no | -| <a name="input_storage_share_quota"></a> [storage\_share\_quota](#input\_storage\_share\_quota) | Maximum size of a File Share. | `number` | `50` | no | -| <a name="input_storage_share_access_tier"></a> [storage\_share\_access\_tier](#input\_storage\_share\_access\_tier) | Access tier for the File Share. | `string` | `"Cool"` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to be associated with the resources created. | `map(string)` | `{}` | no | -| <a name="input_retention_policy_days"></a> [retention\_policy\_days](#input\_retention\_policy\_days) | Log retention policy in days | `number` | `7` | no | -| <a name="input_blob_delete_retention_policy_days"></a> [blob\_delete\_retention\_policy\_days](#input\_blob\_delete\_retention\_policy\_days) | Specifies the number of days that the blob should be retained | `number` | `7` | no | -| <a name="input_storage_allow_inbound_public_ips"></a> [storage\_allow\_inbound\_public\_ips](#input\_storage\_allow\_inbound\_public\_ips) | List of IP CIDR ranges (like `["23.23.23.23"]`) that are allowed to access the Storage Account.<br>Only public IPs are allowed - RFC1918 address space is not permitted. | `list(string)` | `[]` | no | -| <a name="input_storage_allow_vnet_subnet_ids"></a> [storage\_allow\_vnet\_subnet\_ids](#input\_storage\_allow\_vnet\_subnet\_ids) | List of the allowed VNet subnet ids.<br>Note that this option requires network service endpoint enabled for Microsoft Storage for the specified subnets.<br>If you are using [vnet module](../vnet/README.md) - set `storage_private_access` to true for the specific subnet.<br>Example:<pre>[<br> module.vnet.subnet_ids["subnet-mgmt"],<br> module.vnet.subnet_ids["subnet-pub"],<br> module.vnet.subnet_ids["subnet-priv"]<br>]</pre> | `list(string)` | `[]` | no | -| <a name="input_storage_acl"></a> [storage\_acl](#input\_storage\_acl) | If `true`, storage account network rules will be activated with `Deny` as the default action. In such case, at least one of `storage_allow_inbound_public_ips` or `storage_allow_vnet_subnet_ids` must be a non-empty list. | `bool` | `true` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_storage_account"></a> [storage\_account](#output\_storage\_account) | The Azure Storage Account object used for the Bootstrap. | -| <a name="output_storage_share"></a> [storage\_share](#output\_storage\_share) | The File Share object within Azure Storage used for the Bootstrap. | -| <a name="output_primary_access_key"></a> [primary\_access\_key](#output\_primary\_access\_key) | The primary access key for the Azure Storage Account. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## MD5 file hashes + +This module uses MD5 hashes to verify file content change. This means that any file modification done between Terraform runs will +be discovered and the remote file will be overwritten. This has some implications though. + +The module can calculate hashes for the existing files - any files that were present before Terraform run. + +If however you are creating some files on the fly (templating for instance) you have to provide the MD5 hashes yourself. For more +details refer to the [var.file\_shares](#file\_shares) variable documentation. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | Name of the Storage Account. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`storage_account`](#storage_account) | `object` | A map controlling basic Storage Account configuration. +[`storage_network_security`](#storage_network_security) | `object` | A map defining network security settings for a new storage account. +[`file_shares_configuration`](#file_shares_configuration) | `object` | A map defining common File Share setting. +[`file_shares`](#file_shares) | `map` | Definition of File Shares. + + + +## Module's Outputs + +Name | Description +--- | --- +`storage_account_name` | The Azure Storage Account name. For either created or sourced +`storage_account_primary_access_key` | The primary access key for the Azure Storage Account. For either created or sourced +`file_share_urls` | The File Shares' share URL used for bootstrap configuration. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.25 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.25 + + + + +Resources used in this module: + +- `storage_account` (managed) +- `storage_account_network_rules` (managed) +- `storage_share` (managed) +- `storage_share_directory` (managed) +- `storage_share_file` (managed) +- `storage_account` (data) +- `storage_share` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +Name of the Storage Account. +Either a new or an existing one (depending on the value of `storage_account.create`). + +The name you choose must be unique across Azure. The name also must be between 3 and 24 characters in length, and may include +only numbers and lowercase letters. + + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + +### Optional Inputs + + + + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### storage_account + +A map controlling basic Storage Account configuration. + +Following properties are available: + +- `create` - (`bool`, optional, defaults to `true`) controls if the Storage Account is created or sourced. +- `replication_type` - (`string`, optional, defaults to `LRS`) only for newly created Storage Accounts, defines the replication + type used. Can be one of the following values: `LRS`, `GRS`, `RAGRS`, `ZRS`, `GZRS` or `RAGZRS`. +- `kind` - (`string`, optional, defaults to `StorageV2`) only for newly created Storage Accounts, defines the + account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` or + `StorageV2`. +- `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account + tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or + `FileStorage` the `tier` can only be set to `Premium`. + + + +Type: + +```hcl +object({ + create = optional(bool, true) + replication_type = optional(string, "LRS") + kind = optional(string, "StorageV2") + tier = optional(string, "Standard") + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### storage_network_security + +A map defining network security settings for a new storage account. + +When not set or set to `null` it will disable any network security setting. + +When you decide define this setting, at least one of `allowed_public_ips` or `allowed_subnet_ids` has to be defined. +Otherwise you will cut anyone off the storage account. This will have implications on this Terraform code as it operates on +File Shares. Files Shares API comes under this networks restrictions. + +Following properties are available: + +- `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version +- `allowed_public_ips` - (`list`, optional, defaults to `[]`) list of IP CIDR ranges that are allowed to access the Storage + Account. Only public IPs are allowed, RFC1918 address space is not permitted. +- `allowed_subnet_ids` - (`list`, optional, defaults to `[]`) list of the allowed VNet subnet ids. Note that this option + requires network service endpoint enabled for Microsoft Storage for the specified subnets. + If you are using [vnet module](../vnet/README.md), set `storage_private_access` to true for the + specific subnet. + + + +Type: + +```hcl +object({ + min_tls_version = optional(string, "TLS1_2") + allowed_public_ips = optional(list(string), []) + allowed_subnet_ids = optional(list(string), []) + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### file_shares_configuration + +A map defining common File Share setting. + +Any of this can be overridden in a particular File Share definition. See [`file_shares`](#file_shares) variable for details. + +Following options are available: + +- `create_file_shares` - (`bool`, optional, defaults to `true`) controls if the File Shares specified in the + `file_shares` variable are created or sourced, if the latter, the storage account also + has to be sourced. +- `disable_package_dirs_creation` - (`bool`, optional, defaults to `false`) for sourced File Shares, controls if the bootstrap + package folder structure is created +- `quota` - (`number`, optional, defaults to `10`) maximum size of a File Share in GB, a value between + 1 and 5120 (5TB) +- `access_tier` - (`string`, optional, defaults to `Cool`) access tier for a File Share, can be one of: + "Cool", "Hot", "Premium", "TransactionOptimized". + + +Type: + +```hcl +object({ + create_file_shares = optional(bool, true) + disable_package_dirs_creation = optional(bool, false) + quota = optional(number, 10) + access_tier = optional(string, "Cool") + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### file_shares + +Definition of File Shares. + +This is a map of objects where each object is a File Share definition. There are situations where every firewall can use the +same bootstrap package. But there are also situations where each firewall (or a group of firewalls) need a separate one. + +This configuration parameter can help you to create multiple File Shares, per your needs, w/o multiplying Storage Accounts +at the same time. + +Following properties are available per each File Share definition: + +- `name` - (`string`, required) name of the File Share +- `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap package. + For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package) +- `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and build + the bootstrap package. + + Keys are local paths, values - remote. Only Unix like directory separator (`/`) is supported. If `bootstrap_package_path` + is also specified, these files will overwrite any file uploaded from that path. + +- `bootstrap_files_md5` - (`map`, optional, defaults to `{}`) a map of MD5 hashes for files specified in `bootstrap_files`. + + For static files (present and/or not modified before Terraform plan kicks in) this map can be empty. The MD5 hashes are + calculated automatically. It's only required for files modified/created by Terraform. You can use `md5` or `filemd5` + Terraform functions to calculate MD5 hashes dynamically. + + Keys in this map are local paths, variables - MD5 hashes. For files for which you would like to provide MD5 hashes, + keys in this map should match keys in `bootstrap_files` property. + + +Additionally you can override the default `quota` and `access_tier` properties per File Share (same restrictions apply): + +- `quota` - (`number`, optional, defaults to `var.file_shares_configuration.quota`) maximum size of a File Share in GB, + a value between 1 and 5120 (5TB) +- `access_tier` - (`string`, optional, defaults to `var.file_shares_configuration.access_tier`) access tier for a File Share, + can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". + + + +Type: + +```hcl +map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string), {}) + bootstrap_files_md5 = optional(map(string), {}) + quota = optional(number) + access_tier = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/main.tf b/modules/bootstrap/main.tf index d537604f..55b833e8 100644 --- a/modules/bootstrap/main.tf +++ b/modules/bootstrap/main.tf @@ -1,107 +1,186 @@ -locals { - bootstrap_filenames = { for f in try(fileset(var.bootstrap_files_dir, "**"), {}) : f => "${var.bootstrap_files_dir}/${f}" } - # invert var.files map - inverted_files = { for k, v in var.files : v => k } - inverted_filenames = merge(local.bootstrap_filenames, local.inverted_files) - # invert local.filenames map - filenames = { for k, v in local.inverted_filenames : v => k } -} - +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account resource "azurerm_storage_account" "this" { - count = var.create_storage_account ? 1 : 0 + count = var.storage_account.create ? 1 : 0 name = var.name location = var.location resource_group_name = var.resource_group_name - min_tls_version = var.min_tls_version - account_replication_type = "LRS" - account_tier = "Standard" + min_tls_version = var.storage_network_security.min_tls_version + account_replication_type = var.storage_account.replication_type + account_tier = var.storage_account.tier + account_kind = var.storage_account.kind tags = var.tags - queue_properties { - logging { - delete = true - read = true - write = true - version = "1.0" - retention_policy_days = var.retention_policy_days - } - } - blob_properties { - delete_retention_policy { - days = var.blob_delete_retention_policy_days - } - } - network_rules { - default_action = var.storage_acl == true ? "Deny" : "Allow" - ip_rules = var.storage_acl == true ? var.storage_allow_inbound_public_ips : null - virtual_network_subnet_ids = var.storage_acl == true ? var.storage_allow_vnet_subnet_ids : null - } - lifecycle { precondition { - condition = var.storage_acl == true ? (length(var.storage_allow_vnet_subnet_ids) > 0 || length(var.storage_allow_inbound_public_ips) > 0) : true - error_message = "If 'storage_acl' is set to true, at least on of 'storage_allow_vnet_subnet_ids' or 'storage_allow_inbound_public_ips' must be a non-empty list." - } - precondition { - condition = (length(var.storage_allow_vnet_subnet_ids) > 0 || length(var.storage_allow_inbound_public_ips) > 0) ? var.storage_acl == true : true - error_message = "If either 'storage_allow_vnet_subnet_ids' or 'storage_allow_inbound_public_ips' is a non-empty list, 'storage_acl' must be set to true." + condition = var.location != null + error_message = "When creating a storage account the `location` variable cannot be null." } } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account_network_rules +resource "azurerm_storage_account_network_rules" "this" { + count = var.storage_account.create ? 1 : 0 + + storage_account_id = azurerm_storage_account.this[0].id + default_action = length(var.storage_network_security.allowed_public_ips) > 0 || length(var.storage_network_security.allowed_subnet_ids) > 0 ? "Deny" : "Allow" + ip_rules = var.storage_network_security.allowed_public_ips + virtual_network_subnet_ids = var.storage_network_security.allowed_subnet_ids +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/storage_account data "azurerm_storage_account" "this" { - count = var.create_storage_account ? 0 : 1 + count = var.storage_account.create ? 0 : 1 name = var.name resource_group_name = var.resource_group_name } locals { - storage_account = var.create_storage_account ? azurerm_storage_account.this[0] : data.azurerm_storage_account.this[0] + storage_account = var.storage_account.create ? azurerm_storage_account.this[0] : data.azurerm_storage_account.this[0] } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share resource "azurerm_storage_share" "this" { - count = var.storage_share_name != null ? 1 : 0 + for_each = var.file_shares_configuration.create_file_shares ? var.file_shares : {} + + name = each.value.name + storage_account_name = local.storage_account.name + quota = coalesce(each.value.quota, var.file_shares_configuration.quota) + access_tier = coalesce(each.value.access_tier, var.file_shares_configuration.access_tier) +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/storage_share +data "azurerm_storage_share" "this" { + for_each = var.file_shares_configuration.create_file_shares ? {} : var.file_shares - name = var.storage_share_name + name = each.value.name storage_account_name = local.storage_account.name - quota = var.storage_share_quota - access_tier = var.storage_share_access_tier lifecycle { precondition { - condition = var.storage_share_name != null ? alltrue([ - can(regex("^[a-z0-9](-?[a-z0-9])+$", var.storage_share_name)), - can(regex("^([a-z0-9-]){3,63}$", var.storage_share_name)) - ]) : true - error_message = "A File Share name must be between 3 and 63 characters, all lowercase numbers, letters or a dash, it must follow a valid URL schema." + condition = !var.file_shares_configuration.create_file_shares && !var.storage_account.create + error_message = "You cannot source File Shares from a newly created Storage Account." } } } +locals { + file_shares = var.file_shares_configuration.create_file_shares ? azurerm_storage_share.this : data.azurerm_storage_share.this + package_folders = ["content", "config", "software", "plugins", "license"] +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share_directory resource "azurerm_storage_share_directory" "this" { - for_each = var.storage_share_name != null ? toset([ - "content", - "config", - "software", - "plugins", - "license" - ]) : toset([]) - - name = each.key - share_name = azurerm_storage_share.this[0].name + for_each = { + for v in setproduct(keys(var.file_shares), local.package_folders) : + join("-", v) => { + share_key = v[0] + folder_name = v[1] + } + if !var.file_shares_configuration.disable_package_dirs_creation + } + + name = each.value.folder_name + share_name = local.file_shares[each.value.share_key].name storage_account_name = local.storage_account.name } + +locals { + # This locals section is responsible for handling the bootstrap files. + # The files that will be uploaded to File Shares can come from 2 places: + # - bootstrap_package_path - a local bootstrap package (a folder structure + bootstrap files, static) + # - bootstrap_files - a map defining a source file and a destination where this file should be places in the bootstrap package + # on the File Share (static, but can be dynamic) + # + # Since these are two different locations, we need to merge them. The `bootstrap_files` property has a higher precedence as + # as it can contain files created dynamically during Terraform run. Hence, in a situation where a file is present in both + # locations, the one from `bootstrap_files` will be used. + # + # This operation is done by comparing destination paths from both sources. But before we do that we need to perform some + # operations - the information about the bootstrap files is stored in different formats for both locations. + + # 1. Load information about the files present in the local bootstrap package - `bootstrap_package_path`. + # We will receive a map where keys will be paths pointing to where the file should be placed on the File Share and values + # will be paths to local files. + # Assuming that the bootstrap package is stored under `bootstrap` folder we will get a map like this: + # + # ``` + # bootstrap_filenames = { + # "config/bootstrap.xml" = "bootstrap/config/bootstrap.xml" + # ... + # } + # ``` + bootstrap_filenames = { + for k, v in var.file_shares : k => { + for f in try(fileset(v.bootstrap_package_path, "**"), {}) : f => "${v.bootstrap_package_path}/${f}" + } + } + + # 2. Invert the `bootstrap_files`. This map has keys pointing to local files and values specifying the destination. To be able + # to compare destinations we need to swap keys with values. + inverted_files = { + for k, v in var.file_shares : k => { + for k, v in v.bootstrap_files : v => k + } + } + + # 3. Compare both packages using destinations. There is no real comparison being made. We simply merge both maps, the latter one + # takes precedence (we simply use the mechanism of the `merge` function). + inverted_filenames = { + for k, _ in var.file_shares : k => merge(local.bootstrap_filenames[k], local.inverted_files[k]) + } + + # 4. Go back to the old format. We want to have the local path as key and the remote destination as value - this is a natural + # way of interacting with the `azurerm_storage_share_file` resource. Therefore we swap keys with values again. + filenames = { + for k, _ in var.file_shares : k => { for _k, _v in local.inverted_filenames[k] : _v => _k } + } + # 5. Build a flat map with unique keys describing all files across all File Shares. + # NOTE. Up to this point all maps we were iterating over had two levels: + # 1. keys were File Share names + # 2. keys were file names (either source or destination) + # The `azurerm_storage_share_file` resource that will be used to upload all files for all file shares runs over a flat map. + # Therefore we need to flatten the `local.filenames` map and introduce unique keys. Since there is no mechanism built into + # Terraform that would allow map flattening we do it in two steps: + # + # a. turn the map into a flat list + filenames_across_fileshares_flat = flatten([ + for file_share, share_files in local.filenames : [ + for source_path, dest_path in share_files : { + file_share = file_share + source_path = source_path + remote_path = regex("^(.*)/", dest_path)[0] + remote_filename = regex("[^/]+$", dest_path) + } + ] + ]) + # b. turn the flat list into a map. Note, that the key is a combination of the File Share name and the file's source path. + filenames_across_fileshares = { + for v in local.filenames_across_fileshares_flat : + replace("${v.file_share}-${v.source_path}", "/[./]+/", "-") => v + } +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share_file resource "azurerm_storage_share_file" "this" { - for_each = var.storage_share_name != null ? local.filenames : {} + for_each = local.filenames_across_fileshares - name = regex("[^/]*$", each.value) - path = replace(each.value, "/[/]*[^/]*$/", "") - storage_share_id = azurerm_storage_share.this[0].id - source = each.key - content_md5 = try(var.files_md5[each.key], filemd5(each.key)) + # When creating files inside of a File Share we need to specify the path and filename separately + # regardless that the provider's documentation states that `name` can be also a path. + # When this resource is used that way it errors out with the following message: + # `... unexpected new value: Root object was present, but now absent.` + # The file is being created but state is not updated. + name = each.value.remote_filename + path = each.value.remote_path + storage_share_id = local.file_shares[each.value.file_share].id + source = each.value.source_path + content_md5 = try( + var.file_shares[each.value.file_share].bootstrap_files_md5[each.value.source_path], + filemd5(each.value.source_path) + ) depends_on = [azurerm_storage_share_directory.this] } diff --git a/modules/bootstrap/outputs.tf b/modules/bootstrap/outputs.tf index c6d7a04b..4fc139cc 100644 --- a/modules/bootstrap/outputs.tf +++ b/modules/bootstrap/outputs.tf @@ -1,15 +1,15 @@ -output "storage_account" { - description = "The Azure Storage Account object used for the Bootstrap." - value = local.storage_account +output "storage_account_name" { + description = "The Azure Storage Account name. For either created or sourced" + value = local.storage_account.name } -output "storage_share" { - description = "The File Share object within Azure Storage used for the Bootstrap." - value = try(azurerm_storage_share.this[0], null) -} - -output "primary_access_key" { - description = "The primary access key for the Azure Storage Account." +output "storage_account_primary_access_key" { + description = "The primary access key for the Azure Storage Account. For either created or sourced" value = local.storage_account.primary_access_key sensitive = true } + +output "file_share_urls" { + description = "The File Shares' share URL used for bootstrap configuration." + value = { for k, v in azurerm_storage_share.this : k => v.url } +} diff --git a/modules/bootstrap/variables.tf b/modules/bootstrap/variables.tf index 9e8cd884..feff85dc 100644 --- a/modules/bootstrap/variables.tf +++ b/modules/bootstrap/variables.tf @@ -1,14 +1,10 @@ -variable "create_storage_account" { - description = "If `true`, create a Storage Account." - default = true - type = bool -} - variable "name" { description = <<-EOF - Name of the Storage Account, either a new or an existing one (depending on the value of `create_storage_account`). + Name of the Storage Account. + Either a new or an existing one (depending on the value of `storage_account.create`). - The name you choose must be unique across Azure. The name also must be between 3 and 24 characters in length, and may include only numbers and lowercase letters. + The name you choose must be unique across Azure. The name also must be between 3 and 24 characters in length, and may include + only numbers and lowercase letters. EOF type = string validation { @@ -18,139 +14,211 @@ variable "name" { } variable "resource_group_name" { - description = "Name of the Resource Group to use." + description = "The name of the Resource Group to use." type = string } variable "location" { - description = "Region to deploy bootstrap resources. Ignored when `create_storage_account` is set to `false`." + description = "The name of the Azure region to deploy the resources in." default = null type = string } -variable "min_tls_version" { - description = "The minimum supported TLS version for the storage account." - default = "TLS1_2" - type = string -} - -variable "files" { - description = <<-EOF - Map of all files to copy to bucket. The keys are local paths, the values are remote paths. - Always use slash `/` as directory separator (unix-like), not the backslash `\`. - Example: - ``` - files = { - "dir/my.txt" = "config/init-cfg.txt" - } - ``` - EOF +variable "tags" { + description = "The map of tags to assign to all created resources." default = {} type = map(string) } -variable "bootstrap_files_dir" { - description = "Bootstrap file directory. If the variable has a value of `null` (default) - then it will not upload any other files other than the ones specified in the `files` variable. More information can be found at https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package." - default = null - type = string -} - - -variable "files_md5" { +variable "storage_account" { description = <<-EOF - Optional map of MD5 hashes of file contents. - Normally the map could be empty, because all the files that exist before the `terraform apply` will have their hashes auto-calculated. - This input is necessary only for the selected files which are created/modified within the same Terraform run as this module. - The keys of the map should be identical with selected keys of the `files` input, while the values should be MD5 hashes of the contents of that file. - - Example: - ``` - files_md5 = { - "dir/my.txt" = "6f7ce3191b50a58cc13e751a8f7ae3fd" - } - ``` + A map controlling basic Storage Account configuration. + + Following properties are available: + + - `create` - (`bool`, optional, defaults to `true`) controls if the Storage Account is created or sourced. + - `replication_type` - (`string`, optional, defaults to `LRS`) only for newly created Storage Accounts, defines the replication + type used. Can be one of the following values: `LRS`, `GRS`, `RAGRS`, `ZRS`, `GZRS` or `RAGZRS`. + - `kind` - (`string`, optional, defaults to `StorageV2`) only for newly created Storage Accounts, defines the + account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` or + `StorageV2`. + - `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account + tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or + `FileStorage` the `tier` can only be set to `Premium`. + EOF default = {} - type = map(string) + nullable = false + type = object({ + create = optional(bool, true) + replication_type = optional(string, "LRS") + kind = optional(string, "StorageV2") + tier = optional(string, "Standard") + }) + validation { # replication_type + condition = contains(["LRS", "GRS", "RAGRS", "ZRS", "GZRS", "RAGZRS"], var.storage_account.replication_type) + error_message = "The `replication_type` property can be one of: \"LRS\", \"GRS\", \"RAGRS\", \"ZRS\", \"GZRS\" or \"RAGZRS\"." + } + validation { # kind + condition = contains(["BlobStorage", "BlockBlobStorage", "FileStorage", "Storage", "StorageV2"], var.storage_account.kind) + error_message = <<-EOF + The `kind` property can be one of: \"BlobStorage\", \"BlockBlobStorage\", \"FileStorage\", \"Storage\" + or \"StorageV2\"." + EOF + } + validation { # tier + condition = contains(["Standard", "Premium"], var.storage_account.tier) + error_message = "The `tier` property can be one of: \"Standard\" or \"Premium\"." + } + validation { # tier && kind + condition = contains(["BlockBlobStorage", "FileStorage"], var.storage_account.kind) ? var.storage_account.tier == "Premium" : true + error_message = "If the `kind` property is set to either \"BlockBlobStorage\" or \"FileStorage\", the `tier` has to be set to \"Premium\"." + } } -variable "storage_share_name" { +variable "storage_network_security" { description = <<-EOF - Name of a storage File Share to be created that will hold `files` used for bootstrapping. - For rules defining a valid name see [Microsoft documentation](https://docs.microsoft.com/en-us/rest/api/storageservices/Naming-and-Referencing-Shares--Directories--Files--and-Metadata#share-names). - EOF - default = null - type = string - nullable = true -} + A map defining network security settings for a new storage account. -variable "storage_share_quota" { - description = "Maximum size of a File Share." - default = 50 - type = number -} + When not set or set to `null` it will disable any network security setting. -variable "storage_share_access_tier" { - description = "Access tier for the File Share." - default = "Cool" - type = string -} + When you decide define this setting, at least one of `allowed_public_ips` or `allowed_subnet_ids` has to be defined. + Otherwise you will cut anyone off the storage account. This will have implications on this Terraform code as it operates on + File Shares. Files Shares API comes under this networks restrictions. -variable "tags" { - description = "A map of tags to be associated with the resources created." - default = {} - type = map(string) -} + Following properties are available: -variable "retention_policy_days" { - description = "Log retention policy in days" - type = number - default = 7 - validation { - condition = var.retention_policy_days > 0 && var.retention_policy_days < 365 - error_message = "Enter a value between 1 and 365." - } -} + - `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version + - `allowed_public_ips` - (`list`, optional, defaults to `[]`) list of IP CIDR ranges that are allowed to access the Storage + Account. Only public IPs are allowed, RFC1918 address space is not permitted. + - `allowed_subnet_ids` - (`list`, optional, defaults to `[]`) list of the allowed VNet subnet ids. Note that this option + requires network service endpoint enabled for Microsoft Storage for the specified subnets. + If you are using [vnet module](../vnet/README.md), set `storage_private_access` to true for the + specific subnet. -variable "blob_delete_retention_policy_days" { - description = "Specifies the number of days that the blob should be retained" - type = number - default = 7 + EOF + default = {} + nullable = false + type = object({ + min_tls_version = optional(string, "TLS1_2") + allowed_public_ips = optional(list(string), []) + allowed_subnet_ids = optional(list(string), []) + }) validation { - condition = var.blob_delete_retention_policy_days > 0 && var.blob_delete_retention_policy_days < 365 - error_message = "Enter a value between 1 and 365." + condition = contains(["TLS1_0", "TLS1_1", "TLS1_2"], var.storage_network_security.min_tls_version) + error_message = "The `min_tls_version` property can be one of: \"TLS1_0\", \"TLS1_1\", \"TLS1_2\"." } } -variable "storage_allow_inbound_public_ips" { +variable "file_shares_configuration" { description = <<-EOF - List of IP CIDR ranges (like `["23.23.23.23"]`) that are allowed to access the Storage Account. - Only public IPs are allowed - RFC1918 address space is not permitted. + A map defining common File Share setting. + + Any of this can be overridden in a particular File Share definition. See [`file_shares`](#file_shares) variable for details. + + Following options are available: + + - `create_file_shares` - (`bool`, optional, defaults to `true`) controls if the File Shares specified in the + `file_shares` variable are created or sourced, if the latter, the storage account also + has to be sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to `false`) for sourced File Shares, controls if the bootstrap + package folder structure is created + - `quota` - (`number`, optional, defaults to `10`) maximum size of a File Share in GB, a value between + 1 and 5120 (5TB) + - `access_tier` - (`string`, optional, defaults to `Cool`) access tier for a File Share, can be one of: + "Cool", "Hot", "Premium", "TransactionOptimized". EOF - type = list(string) - default = [] + default = {} + nullable = false + type = object({ + create_file_shares = optional(bool, true) + disable_package_dirs_creation = optional(bool, false) + quota = optional(number, 10) + access_tier = optional(string, "Cool") + }) + validation { + condition = var.file_shares_configuration.quota >= 1 && var.file_shares_configuration.quota <= 5120 + error_message = "The `quota` property can take values between 1 and 5120." + } + validation { + condition = contains(["Cool", "Hot", "Premium", "TransactionOptimized"], var.file_shares_configuration.access_tier) + error_message = "The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\"." + } + validation { + condition = var.file_shares_configuration.create_file_shares ? !var.file_shares_configuration.disable_package_dirs_creation : true + error_message = "The `disable_package_dirs_creation` cannot be set to true for newly created File Shares." + } } -variable "storage_allow_vnet_subnet_ids" { +variable "file_shares" { description = <<-EOF - List of the allowed VNet subnet ids. - Note that this option requires network service endpoint enabled for Microsoft Storage for the specified subnets. - If you are using [vnet module](../vnet/README.md) - set `storage_private_access` to true for the specific subnet. - Example: - ``` - [ - module.vnet.subnet_ids["subnet-mgmt"], - module.vnet.subnet_ids["subnet-pub"], - module.vnet.subnet_ids["subnet-priv"] - ] - ``` + Definition of File Shares. + + This is a map of objects where each object is a File Share definition. There are situations where every firewall can use the + same bootstrap package. But there are also situations where each firewall (or a group of firewalls) need a separate one. + + This configuration parameter can help you to create multiple File Shares, per your needs, w/o multiplying Storage Accounts + at the same time. + + Following properties are available per each File Share definition: + + - `name` - (`string`, required) name of the File Share + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap package. + For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package) + - `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and build + the bootstrap package. + + Keys are local paths, values - remote. Only Unix like directory separator (`/`) is supported. If `bootstrap_package_path` + is also specified, these files will overwrite any file uploaded from that path. + + - `bootstrap_files_md5` - (`map`, optional, defaults to `{}`) a map of MD5 hashes for files specified in `bootstrap_files`. + + For static files (present and/or not modified before Terraform plan kicks in) this map can be empty. The MD5 hashes are + calculated automatically. It's only required for files modified/created by Terraform. You can use `md5` or `filemd5` + Terraform functions to calculate MD5 hashes dynamically. + + Keys in this map are local paths, variables - MD5 hashes. For files for which you would like to provide MD5 hashes, + keys in this map should match keys in `bootstrap_files` property. + + + Additionally you can override the default `quota` and `access_tier` properties per File Share (same restrictions apply): + + - `quota` - (`number`, optional, defaults to `var.file_shares_configuration.quota`) maximum size of a File Share in GB, + a value between 1 and 5120 (5TB) + - `access_tier` - (`string`, optional, defaults to `var.file_shares_configuration.access_tier`) access tier for a File Share, + can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". + EOF - type = list(string) - default = [] + default = {} + nullable = false + type = map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string), {}) + bootstrap_files_md5 = optional(map(string), {}) + quota = optional(number) + access_tier = optional(string) + })) + validation { + condition = alltrue([ + for _, v in var.file_shares : + alltrue([ + can(regex("^[a-z0-9](-?[a-z0-9])+$", v.name)), + can(regex("^([a-z0-9-]){3,63}$", v.name)) + ]) + ]) + error_message = "A File Share name must be between 3 and 63 characters, all lowercase numbers, letters or a dash, it must follow a valid URL schema." + } + validation { + condition = alltrue([for _, v in var.file_shares : v.quota >= 1 && v.quota <= 5120 if v.quota != null]) + error_message = "The `quota` property can take values between 1 and 5120." + } + validation { + condition = alltrue([ + for _, v in var.file_shares : + contains(["Cool", "Hot", "Premium", "TransactionOptimized"], v.access_tier) + if v.access_tier != null + ]) + error_message = "The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\"." + } } - -variable "storage_acl" { - description = "If `true`, storage account network rules will be activated with `Deny` as the default action. In such case, at least one of `storage_allow_inbound_public_ips` or `storage_allow_vnet_subnet_ids` must be a non-empty list." - default = true - type = bool -} \ No newline at end of file diff --git a/modules/bootstrap/versions.tf b/modules/bootstrap/versions.tf index 1611a7bf..2c796798 100644 --- a/modules/bootstrap/versions.tf +++ b/modules/bootstrap/versions.tf @@ -5,9 +5,5 @@ terraform { source = "hashicorp/azurerm" version = "~> 3.25" } - random = { - source = "hashicorp/random" - version = "~> 3.1" - } } } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index ed2cea1e..a9c0d2fe 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -192,15 +192,15 @@ Private 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 +- `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 controlled by `public_ip_name` property +- `public_ip_resource_group_name` - (`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: @@ -247,9 +247,9 @@ Examples # 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" + create_public_ip = false + public_ip_name = "my_ip" + public_ip_resource_group_name = "my_rg_name" in_rules = { HTTP = { port = 80 @@ -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) - gwlb_fip_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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/variables.tf b/modules/loadbalancer/variables.tf index bb65542b..9ca3d2e8 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -64,15 +64,15 @@ variable "frontend_ips" { 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 + - `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 controlled by `public_ip_name` property + - `public_ip_resource_group_name` - (`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: @@ -119,9 +119,9 @@ variable "frontend_ips" { # 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" + create_public_ip = false + public_ip_name = "my_ip" + public_ip_resource_group_name = "my_rg_name" in_rules = { HTTP = { port = 80 @@ -170,13 +170,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) - gwlb_fip_id = optional(string) + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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/ngfw_metrics/.header.md b/modules/ngfw_metrics/.header.md index 35639b3e..53ab9b60 100644 --- a/modules/ngfw_metrics/.header.md +++ b/modules/ngfw_metrics/.header.md @@ -2,7 +2,7 @@ A Terraform module deploying Azure Application Insights (Log Analytics Workspace mode). -The main purpose of this module is to deploy Application Insights that can be used to monitor internal PanOS metrics. +The main purpose of this module is to deploy Application Insights that can be used to monitor internal PAN-OS metrics. It will work with both a standalone Next Generation Firewall and ones deployed inside a Virtual Machine Scale Set. In both situations the instrumentation key for the Application Insights has to be provided in the firewall's configuration. For more information please refer to [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall). diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index a01477ea..a43f7726 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -3,7 +3,7 @@ A Terraform module deploying Azure Application Insights (Log Analytics Workspace mode). -The main purpose of this module is to deploy Application Insights that can be used to monitor internal PanOS metrics. +The main purpose of this module is to deploy Application Insights that can be used to monitor internal PAN-OS metrics. It will work with both a standalone Next Generation Firewall and ones deployed inside a Virtual Machine Scale Set. In both situations the instrumentation key for the Application Insights has to be provided in the firewall's configuration. For more information please refer to [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall). @@ -87,7 +87,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.80 diff --git a/modules/vmseries/.header.md b/modules/vmseries/.header.md new file mode 100644 index 00000000..e65e7692 --- /dev/null +++ b/modules/vmseries/.header.md @@ -0,0 +1,32 @@ +# Palo Alto Networks VM-Series Module for Azure + +A Terraform module for deploying a VM-Series firewall in Azure cloud. +The module is not intended for use with Scale Sets. + +## Usage + +For usage please refer to any reference architecture example. + +## Accept Azure Marketplace Terms + +Accept the Azure Marketplace terms for the VM-Series images. In a typical situation use these commands: + +```sh +az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan byol --subscription MySubscription +az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan bundle1 --subscription MySubscription +az vm image terms accept --publisher paloaltonetworks --offer vmseries-flex --plan bundle2 --subscription MySubscription +``` + +You can revoke the acceptance later with the `az vm image terms cancel` command. +The acceptance applies to the entirety of your Azure Subscription. + +## Caveat Regarding Region + +By default, the VM-Series is placed into an Availability Zone "1". Hence, it can only deploy +successfully in the [Regions that support Zones](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). +If your Region doesn't, use an alternative mechanism of Availability Set, which is inferior but universally supported: + +```hcl + avset_id = azurerm_availability_set.this.id + avzone = null +``` diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index e352bb40..31970013 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -1,3 +1,4 @@ +<!-- BEGIN_TF_DOCS --> # Palo Alto Networks VM-Series Module for Azure A Terraform module for deploying a VM-Series firewall in Azure cloud. @@ -31,82 +32,331 @@ If your Region doesn't, use an alternative mechanism of Availability Set, which avzone = null ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | - -### Modules - -No modules. - -### Resources - -| Name | Type | -|------|------| -| [azurerm_network_interface.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource | -| [azurerm_network_interface_backend_address_pool_association.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_backend_address_pool_association) | resource | -| [azurerm_public_ip.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_virtual_machine.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine) | 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 | -|------|-------------|------|---------|:--------:| -| <a name="input_location"></a> [location](#input\_location) | Region where to deploy VM-Series and dependencies. | `string` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the existing resource group where to place the resources created. | `string` | n/a | yes | -| <a name="input_name"></a> [name](#input\_name) | VM-Series instance name. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If false, the input `avzone` is ignored 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 | -| <a name="input_avzone"></a> [avzone](#input\_avzone) | The availability zone to use, for example "1", "2", "3". Ignored if `enable_zones` is false. Conflicts with `avset_id`, in which case use `avzone = null`. | `string` | `"1"` | no | -| <a name="input_avzones"></a> [avzones](#input\_avzones) | After provider version 3.x you need to specify in which availability zone(s) you want to place IP.<br>ie: for zone-redundant with 3 availability zone in current region value will be:<pre>["1","2","3"]</pre> | `list(string)` | `[]` | no | -| <a name="input_avset_id"></a> [avset\_id](#input\_avset\_id) | The identifier of the Availability Set to use. When using this variable, set `avzone = null`. | `string` | `null` | no | -| <a name="input_interfaces"></a> [interfaces](#input\_interfaces) | List of the network interface specifications.<br><br>NOTICE. The ORDER in which you specify the interfaces DOES MATTER.<br>Interfaces will be attached to VM in the order you define here, therefore:<br>* The first should be the management interface, which does not participate in data filtering.<br>* The remaining ones are the dataplane interfaces.<br><br>Options for an interface object:<br>- `name` - (required\|string) Interface name.<br>- `subnet_id` - (required\|string) Identifier of an existing subnet to create interface in.<br>- `create_public_ip` - (optional\|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. Default is false.<br>- `private_ip_address` - (optional\|string) Static private IP to asssign to the interface. If null, dynamic one is allocated.<br>- `public_ip_name` - (optional\|string) Name of an existing public IP to associate to the interface, used only when `create_public_ip` is `false`.<br>- `public_ip_resource_group` - (optional\|string) Name of a Resource Group that contains public IP resource to associate to the interface. When not specified defaults to `var.resource_group_name`. Used only when `create_public_ip` is `false`.<br>- `availability_zone` - (optional\|string) Availability zone to create public IP in. If not specified, set based on `avzone` and `enable_zones`.<br>- `enable_ip_forwarding` - (optional\|bool) If true, the network interface will not discard packets sent to an IP address other than the one assigned. If false, the network interface only accepts traffic destined to its IP address.<br>- `enable_backend_pool` - (optional\|bool) If true, associate interface with backend pool specified with `lb_backend_pool_id`. Default is false.<br>- `lb_backend_pool_id` - (optional\|string) Identifier of an existing backend pool to associate interface with. Required if `enable_backend_pool` is true.<br>- `tags` - (optional\|map) Tags to assign to the interface and public IP (if created). Overrides contents of `tags` variable.<br><br>Example:<pre>[<br> {<br> name = "fw-mgmt"<br> subnet_id = azurerm_subnet.my_mgmt_subnet.id<br> public_ip_address_id = azurerm_public_ip.my_mgmt_ip.id<br> create_public_ip = true<br> },<br> {<br> name = "fw-public"<br> subnet_id = azurerm_subnet.my_pub_subnet.id<br> lb_backend_pool_id = module.inbound_lb.backend_pool_id<br> enable_backend_pool = true<br> create_public_ip = false<br> public_ip_name = "fw-public-ip"<br> },<br>]</pre> | `list(any)` | n/a | yes | -| <a name="input_username"></a> [username](#input\_username) | Initial administrative username to use for VM-Series. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm). | `string` | n/a | yes | -| <a name="input_password"></a> [password](#input\_password) | Initial administrative password to use for VM-Series. If not defined the `ssh_key` variable must be specified. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm). | `string` | `null` | no | -| <a name="input_ssh_keys"></a> [ssh\_keys](#input\_ssh\_keys) | A list of initial administrative SSH public keys that allow key-pair authentication.<br><br>This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available:<pre>[<br> file("/path/to/public/keys/key_1.pub"),<br> file("/path/to/public/keys/key_2.pub")<br>]</pre>If the `password` variable is also set, VM-Series will accept both authentication methods. | `list(string)` | `[]` | no | -| <a name="input_managed_disk_type"></a> [managed\_disk\_type](#input\_managed\_disk\_type) | Type of OS Managed Disk to create for the virtual machine. Possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs. | `string` | `"StandardSSD_LRS"` | no | -| <a name="input_os_disk_name"></a> [os\_disk\_name](#input\_os\_disk\_name) | Optional name of the OS disk to create for the virtual machine. If empty, the name is auto-generated. | `string` | `null` | no | -| <a name="input_vm_size"></a> [vm\_size](#input\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. | `string` | `"Standard_D3_v2"` | no | -| <a name="input_custom_image_id"></a> [custom\_image\_id](#input\_custom\_image\_id) | Absolute ID of your own Custom Image to be used for creating new VM-Series. If set, the `username`, `password`, `img_version`, `img_publisher`, `img_offer`, `img_sku` inputs are all ignored (these are used only for published images, not custom ones). The Custom Image is expected to contain PAN-OS software. | `string` | `null` | no | -| <a name="input_enable_plan"></a> [enable\_plan](#input\_enable\_plan) | Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku "byol", which means "bring your own license", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image. | `bool` | `true` | no | -| <a name="input_img_publisher"></a> [img\_publisher](#input\_img\_publisher) | The Azure Publisher identifier for a image which should be deployed. | `string` | `"paloaltonetworks"` | no | -| <a name="input_img_offer"></a> [img\_offer](#input\_img\_offer) | The Azure Offer identifier corresponding to a published image. For `img_version` 9.1.1 or above, use "vmseries-flex"; for 9.1.0 or below use "vmseries1". | `string` | `"vmseries-flex"` | no | -| <a name="input_img_sku"></a> [img\_sku](#input\_img\_sku) | VM-series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_img_version"></a> [img\_version](#input\_img\_version) | VM-series PAN-OS version - list available for a default `img_offer` with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` | `string` | `"10.1.0"` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to be associated with the resources created. | `map(any)` | `{}` | no | -| <a name="input_identity_type"></a> [identity\_type](#input\_identity\_type) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_type). | `string` | `"SystemAssigned"` | no | -| <a name="input_identity_ids"></a> [identity\_ids](#input\_identity\_ids) | See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_ids). | `list(string)` | `null` | no | -| <a name="input_accelerated_networking"></a> [accelerated\_networking](#input\_accelerated\_networking) | Enable Azure accelerated networking (SR-IOV) for all network interfaces except the primary one (it is the PAN-OS management interface, which [does not support](https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-new-features/virtualization-features/support-for-azure-accelerated-networking-sriov) acceleration). | `bool` | `true` | no | -| <a name="input_bootstrap_options"></a> [bootstrap\_options](#input\_bootstrap\_options) | Bootstrap options to pass to VM-Series instance.<br><br>Proper syntax is a string of semicolon separated properties.<br>Example:<br> bootstrap\_options = "type=dhcp-client;panorama-server=1.2.3.4"<br><br>A list of available properties: storage-account, access-key, file-share, share-directory, type, ip-address, default-gateway, netmask, ipv6-address, ipv6-default-gateway, hostname, panorama-server, panorama-server-2, tplname, dgname, dns-primary, dns-secondary, vm-auth-key, op-command-modes, op-cmd-dpdk-pkt-io, plugin-op-commands, dhcp-send-hostname, dhcp-send-client-id, dhcp-accept-server-hostname, dhcp-accept-server-domain, auth-key, vm-series-auto-registration-pin-value, vm-series-auto-registration-pin-id.<br><br>For more details on bootstrapping see documentation: https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components | `string` | `""` | no | -| <a name="input_diagnostics_storage_uri"></a> [diagnostics\_storage\_uri](#input\_diagnostics\_storage\_uri) | The storage account's blob endpoint to hold diagnostic files. | `string` | `null` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_mgmt_ip_address"></a> [mgmt\_ip\_address](#output\_mgmt\_ip\_address) | VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address. | -| <a name="output_interfaces"></a> [interfaces](#output\_interfaces) | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. | -| <a name="output_principal_id"></a> [principal\_id](#output\_principal\_id) | The oid of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> - -## Custom Metrics - -**(Optional)** Firewalls can publish custom metrics (for example `panSessionUtilization`) to Azure Application Insights. -This however requires a manual initialization: copy the output `metrics_instrumentation_key` and paste it into your -PAN-OS webUI -> Device -> VM-Series -> Azure. The module automatically completes the Step 1 of the -[official procedure](https://docs.paloaltonetworks.com/vm-series/10-0/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/enable-azure-application-insights-on-the-vm-series-firewall.html). - -The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain -back a result for solely a single firewall. Thus for example if three firewalls use the same Instrumentation Key and report -their respective session utilizations as 90%, 20%, 10%, it is possible to see in Azure the average of 40%, the sum of 120%, the max of 90%, but it is *not possible* to know which of the firewalls reported the 90% utilization. +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Virtual Machine. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). +[`image`](#image) | `object` | Basic Azure VM image configuration. +[`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. +[`interfaces`](#interfaces) | `list` | List of the network interface specifications. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. + + + +## Module's Outputs + +Name | Description +--- | --- +`mgmt_ip_address` | VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address. +`interfaces` | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. +`principal_id` | The ID of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.25 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.25 + + + + +Resources used in this module: + +- `linux_virtual_machine` (managed) +- `network_interface` (managed) +- `network_interface_backend_address_pool_association` (managed) +- `public_ip` (managed) +- `public_ip` (data) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Virtual Machine. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### authentication + +A map defining authentication settings (including username and password). + +Following properties are available: + +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series + username. +- `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password. +- `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. +- `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. + +**Important!** \ +The `password` property is required when `ssh_keys` is not specified. + +**Important!** \ +`ssh_keys` property is a list of strings, so each item should be the actual public key value. +If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. + + + +Type: + +```hcl +object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### image + +Basic Azure VM image configuration. + +Following properties are available: + +- `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` +- `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for a image + which should be deployed +- `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a + published image +- `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU; list available with + `az vm image list -o table --all --publisher paloaltonetworks` +- `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Market Place +- `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines + +**Important!** \ +`custom_id` and `version` properties are mutually exclusive. + + + +Type: + +```hcl +object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "vmseries-flex") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### virtual_machine + +Firewall parameters configuration. + +This map contains basic, as well as some optional Firewall parameters. Both types contain sane defaults. +Nevertheless they should be at least reviewed to meet deployment requirements. + +List of either required or important properties: + +- `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported +- `zone` - (`string`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. +- `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) +- `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk +- `bootstrap_options` - bootstrap options to pass to VM-Series instance. + + Proper syntax is a string of semicolon separated properties, for example: + + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` + + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + +List of other, optional properties: + +- `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use +- `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled) +- `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk +- `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted + by enabling Encryption at Host +- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files +- `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". +- `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned" +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM + + + +Type: + +```hcl +object({ + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### interfaces + +List of the network interface specifications. + +**Note!** \ +The ORDER in which you specify the interfaces DOES MATTER. + +Interfaces will be attached to VM in the order you define here, therefore: + +- The first should be the management interface, which does not participate in data filtering. +- The remaining ones are the dataplane interfaces. + +Following configuration options are available: + +- `name` - (`string`, required) the interface name +- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in +- `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When + skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not + to change as long as the VM is running. Any stop/deallocate/restart operation might cause + the IP to change. +- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface +- `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the + interface. When `create_public_ip` is set to `true` this will become a name of a newly + created Public IP interface. Otherwise this is a name of an existing interfaces that will + be sourced and attached to the interface. +- `public_ip_resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that + contains public IP that that will be associated with the interface. Used only when + `create_public_ip` is `false`. +- `attach_to_lb_backend_pool` - (`bool`, optional, defaults to `false`) set to `true` if you would like to associate this + interface with a Load Balancer backend pool. +- `lb_backend_pool_id` - (`string`, optional, defaults to `null`) ID of an existing backend pool to associate the + interface with. + +Example: + +```hcl +[ + # management interface with a new public IP + { + name = "fw-mgmt" + subnet_id = azurerm_subnet.my_mgmt_subnet.id + public_ip_name = "fw-mgmt-pip" + create_public_ip = true + }, + # public interface reusing an existing public IP resource + { + name = "fw-public" + subnet_id = azurerm_subnet.my_pub_subnet.id + attach_to_lb_backend_pool = true + lb_backend_pool_id = module.inbound_lb.backend_pool_id + create_public_ip = false + public_ip_name = "fw-public-pip" + }, +] +``` + + + +Type: + +```hcl +list(object({ + name = string + subnet_id = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + lb_backend_pool_id = optional(string) + attach_to_lb_backend_pool = optional(bool, false) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/main.tf b/modules/vmseries/main.tf index 794fac89..ec13f2b2 100644 --- a/modules/vmseries/main.tf +++ b/modules/vmseries/main.tf @@ -1,128 +1,130 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - for_each = { for v in var.interfaces : v.name => v if try(v.create_public_ip, false) } + for_each = { for v in var.interfaces : v.name => v if v.create_public_ip } location = var.location resource_group_name = var.resource_group_name - name = "${each.value.name}-pip" + name = each.value.public_ip_name allocation_method = "Static" sku = "Standard" - zones = var.enable_zones ? var.avzones : null - tags = try(each.value.tags, var.tags) + zones = var.virtual_machine.zone != null ? [var.virtual_machine.zone] : null + tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "this" { - for_each = { for v in var.interfaces : v.name => v - if(!try(v.create_public_ip, false) && try(v.public_ip_name, null) != null) + for_each = { for v in var.interfaces : v.name => v if !v.create_public_ip && v.public_ip_name != null } name = each.value.public_ip_name - resource_group_name = try(each.value.public_ip_resource_group, null) != null ? each.value.public_ip_resource_group : var.resource_group_name + resource_group_name = coalesce(each.value.public_ip_resource_group_name, var.resource_group_name) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface resource "azurerm_network_interface" "this" { for_each = { for k, v in var.interfaces : v.name => merge(v, { index = k }) } - name = "${each.value.name}-nic" + name = each.value.name location = var.location resource_group_name = var.resource_group_name - enable_accelerated_networking = each.value.index == 0 ? false : var.accelerated_networking # for interface 0 it is unsupported by PAN-OS - enable_ip_forwarding = try(each.value.enable_ip_forwarding, each.value.index == 0 ? false : true) # for interface 0 use false per Reference Arch - tags = try(each.value.tags, var.tags) + enable_accelerated_networking = each.value.index == 0 ? false : var.virtual_machine.accelerated_networking + enable_ip_forwarding = each.value.index == 0 ? false : true + tags = var.tags ip_configuration { name = "primary" subnet_id = each.value.subnet_id - private_ip_address_allocation = try(each.value.private_ip_address, null) != null ? "Static" : "Dynamic" - private_ip_address = try(each.value.private_ip_address, null) - public_ip_address_id = try(azurerm_public_ip.this[each.value.name].id, data.azurerm_public_ip.this[each.value.name].id, null) + private_ip_address_allocation = each.value.private_ip_address != null ? "Static" : "Dynamic" + private_ip_address = each.value.private_ip_address + public_ip_address_id = try( + azurerm_public_ip.this[each.value.name].id, + data.azurerm_public_ip.this[each.value.name].id, + null + ) } } -resource "azurerm_network_interface_backend_address_pool_association" "this" { - for_each = { for v in var.interfaces : v.name => v if try(v.enable_backend_pool, false) } - - backend_address_pool_id = each.value.lb_backend_pool_id - ip_configuration_name = azurerm_network_interface.this[each.key].ip_configuration[0].name - network_interface_id = azurerm_network_interface.this[each.key].id - - depends_on = [ - azurerm_network_interface.this, - azurerm_virtual_machine.this - ] +locals { + password = sensitive(var.authentication.password) } -resource "azurerm_virtual_machine" "this" { - name = var.name - location = var.location - resource_group_name = var.resource_group_name - tags = var.tags - vm_size = var.vm_size - zones = var.enable_zones && var.avzone != null && var.avzone != "" ? [var.avzone] : null - availability_set_id = var.avset_id - primary_network_interface_id = azurerm_network_interface.this[var.interfaces[0].name].id +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine +resource "azurerm_linux_virtual_machine" "this" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + tags = var.tags - network_interface_ids = [for v in var.interfaces : azurerm_network_interface.this[v.name].id] + size = var.virtual_machine.size + zone = var.virtual_machine.zone + availability_set_id = var.virtual_machine.avset_id + encryption_at_host_enabled = var.virtual_machine.encryption_at_host_enabled - storage_image_reference { - id = var.custom_image_id - publisher = var.custom_image_id == null ? var.img_publisher : null - offer = var.custom_image_id == null ? var.img_offer : null - sku = var.custom_image_id == null ? var.img_sku : null - version = var.custom_image_id == null ? var.img_version : null - } + network_interface_ids = [for v in var.interfaces : azurerm_network_interface.this[v.name].id] - dynamic "plan" { - for_each = var.enable_plan ? ["one"] : [] + admin_username = var.authentication.username + admin_password = var.authentication.disable_password_authentication ? null : local.password + disable_password_authentication = var.authentication.disable_password_authentication + dynamic "admin_ssh_key" { + for_each = { for k, v in var.authentication.ssh_keys : k => v } content { - name = var.img_sku - publisher = var.img_publisher - product = var.img_offer + username = var.authentication.username + public_key = admin_ssh_key.value } } - storage_os_disk { - create_option = "FromImage" - name = coalesce(var.os_disk_name, "${var.name}-disk") - managed_disk_type = var.managed_disk_type - os_type = "Linux" - caching = "ReadWrite" + os_disk { + name = var.virtual_machine.disk_name + storage_account_type = var.virtual_machine.disk_type + caching = "ReadWrite" + disk_encryption_set_id = var.virtual_machine.disk_encryption_set_id } - delete_os_disk_on_termination = true - delete_data_disks_on_termination = true + source_image_id = var.image.custom_id - os_profile { - computer_name = var.name - admin_username = var.username - admin_password = var.password - custom_data = var.bootstrap_options - } - - os_profile_linux_config { - disable_password_authentication = var.password == null ? true : false - dynamic "ssh_keys" { - for_each = var.ssh_keys - content { - key_data = ssh_keys.value - path = "/home/${var.username}/.ssh/authorized_keys" - } + dynamic "source_image_reference" { + for_each = var.image.custom_id == null ? [1] : [] + content { + publisher = var.image.publisher + offer = var.image.offer + sku = var.image.sku + version = var.image.version } } - # After converting to azurerm_linux_virtual_machine, an empty block boot_diagnostics {} will use managed storage. Want. - # 2.36 in required_providers per https://github.com/terraform-providers/terraform-provider-azurerm/pull/8917 - dynamic "boot_diagnostics" { - for_each = var.diagnostics_storage_uri != null ? ["one"] : [] + dynamic "plan" { + for_each = var.image.enable_marketplace_plan ? [1] : [] content { - enabled = true - storage_uri = var.diagnostics_storage_uri + name = var.image.sku + publisher = var.image.publisher + product = var.image.offer } } + custom_data = var.virtual_machine.bootstrap_options == null ? null : base64encode(var.virtual_machine.bootstrap_options) + + boot_diagnostics { + storage_account_uri = var.virtual_machine.diagnostics_storage_uri + } + identity { - type = var.identity_type - identity_ids = var.identity_ids + type = var.virtual_machine.identity_type + identity_ids = var.virtual_machine.identity_ids } } + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_backend_address_pool_association +resource "azurerm_network_interface_backend_address_pool_association" "this" { + for_each = { for v in var.interfaces : v.name => v.lb_backend_pool_id if v.attach_to_lb_backend_pool } + + backend_address_pool_id = each.value + ip_configuration_name = azurerm_network_interface.this[each.key].ip_configuration[0].name + network_interface_id = azurerm_network_interface.this[each.key].id + + depends_on = [ + azurerm_network_interface.this, + azurerm_linux_virtual_machine.this + ] +} \ No newline at end of file diff --git a/modules/vmseries/outputs.tf b/modules/vmseries/outputs.tf index b2b5b6bf..ee9edf3b 100644 --- a/modules/vmseries/outputs.tf +++ b/modules/vmseries/outputs.tf @@ -1,6 +1,9 @@ output "mgmt_ip_address" { description = "VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address." - value = try(var.interfaces[0].create_public_ip, false) ? azurerm_public_ip.this[var.interfaces[0].name].ip_address : azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address + value = try( + azurerm_public_ip.this[var.interfaces[0].name].ip_address, + azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address + ) } output "interfaces" { @@ -9,6 +12,6 @@ output "interfaces" { } output "principal_id" { - description = "The oid of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned." - value = var.identity_type != null && var.identity_type != "" ? azurerm_virtual_machine.this.identity[0].principal_id : null + description = "The ID of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned." + value = var.virtual_machine.identity_type != null ? azurerm_linux_virtual_machine.this.identity[0].principal_id : null } diff --git a/modules/vmseries/variables.tf b/modules/vmseries/variables.tf index e6a35520..3cd0cb5f 100644 --- a/modules/vmseries/variables.tf +++ b/modules/vmseries/variables.tf @@ -1,219 +1,244 @@ -variable "location" { - description = "Region where to deploy VM-Series and dependencies." +variable "name" { + description = "The name of the Azure Virtual Machine." type = string } variable "resource_group_name" { - description = "Name of the existing resource group where to place the resources created." + description = "The name of the Resource Group to use." type = string } -variable "name" { - description = "VM-Series instance name." +variable "location" { + description = "The name of the Azure region to deploy the resources in." type = string } -variable "enable_zones" { - description = "If false, the input `avzone` is ignored 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 = "The map of tags to assign to all created resources." + default = {} + type = map(string) } -variable "avzone" { - description = "The availability zone to use, for example \"1\", \"2\", \"3\". Ignored if `enable_zones` is false. Conflicts with `avset_id`, in which case use `avzone = null`." - default = "1" - type = string +variable "authentication" { + description = <<-EOF + A map defining authentication settings (including username and password). + + Following properties are available: + + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series + username. + - `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password. + - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. + - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. + + **Important!** \ + The `password` property is required when `ssh_keys` is not specified. + + **Important!** \ + `ssh_keys` property is a list of strings, so each item should be the actual public key value. + If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. + + EOF + type = object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) } -variable "avzones" { +variable "image" { description = <<-EOF - After provider version 3.x you need to specify in which availability zone(s) you want to place IP. - ie: for zone-redundant with 3 availability zone in current region value will be: - ```["1","2","3"]``` + Basic Azure VM image configuration. + + Following properties are available: + + - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` + - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for a image + which should be deployed + - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a + published image + - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU; list available with + `az vm image list -o table --all --publisher paloaltonetworks` + - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Market Place + - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines + + **Important!** \ + `custom_id` and `version` properties are mutually exclusive. + EOF - default = [] - type = list(string) -} + type = object({ + version = optional(string) + publisher = optional(string, "paloaltonetworks") + offer = optional(string, "vmseries-flex") + sku = optional(string, "byol") + enable_marketplace_plan = optional(bool, true) + custom_id = optional(string) + }) + validation { + condition = (var.image.custom_id != null && var.image.version == null + ) || ( + var.image.custom_id == null && var.image.version != null + ) + error_message = "Either `custom_id` or `version` has to be defined." + } +} + +variable "virtual_machine" { + description = <<-EOF + Firewall parameters configuration. + + This map contains basic, as well as some optional Firewall parameters. Both types contain sane defaults. + Nevertheless they should be at least reviewed to meet deployment requirements. + + List of either required or important properties: + + - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported + - `zone` - (`string`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. + - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) + - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk + - `bootstrap_options` - bootstrap options to pass to VM-Series instance. + + Proper syntax is a string of semicolon separated properties, for example: + + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` + + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + + List of other, optional properties: + + - `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use + - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled) + - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk + - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted + by enabling Encryption at Host + - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold + diagnostic files + - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". + - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned" + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM -variable "avset_id" { - description = "The identifier of the Availability Set to use. When using this variable, set `avzone = null`." - default = null - type = string + EOF + type = object({ + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) + }) + validation { + condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) + error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + } + validation { + condition = contains(["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type) + error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + } + validation { + condition = var.virtual_machine.identity_type == "SystemAssigned" ? length(var.virtual_machine.identity_ids) == 0 : length(var.virtual_machine.identity_ids) > 0 + error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + } } variable "interfaces" { description = <<-EOF List of the network interface specifications. - NOTICE. The ORDER in which you specify the interfaces DOES MATTER. + **Note!** \ + The ORDER in which you specify the interfaces DOES MATTER. + Interfaces will be attached to VM in the order you define here, therefore: - * The first should be the management interface, which does not participate in data filtering. - * The remaining ones are the dataplane interfaces. + + - The first should be the management interface, which does not participate in data filtering. + - The remaining ones are the dataplane interfaces. - Options for an interface object: - - `name` - (required|string) Interface name. - - `subnet_id` - (required|string) Identifier of an existing subnet to create interface in. - - `create_public_ip` - (optional|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. Default is false. - - `private_ip_address` - (optional|string) Static private IP to asssign to the interface. If null, dynamic one is allocated. - - `public_ip_name` - (optional|string) Name of an existing public IP to associate to the interface, used only when `create_public_ip` is `false`. - - `public_ip_resource_group` - (optional|string) Name of a Resource Group that contains public IP resource to associate to the interface. When not specified defaults to `var.resource_group_name`. Used only when `create_public_ip` is `false`. - - `availability_zone` - (optional|string) Availability zone to create public IP in. If not specified, set based on `avzone` and `enable_zones`. - - `enable_ip_forwarding` - (optional|bool) If true, the network interface will not discard packets sent to an IP address other than the one assigned. If false, the network interface only accepts traffic destined to its IP address. - - `enable_backend_pool` - (optional|bool) If true, associate interface with backend pool specified with `lb_backend_pool_id`. Default is false. - - `lb_backend_pool_id` - (optional|string) Identifier of an existing backend pool to associate interface with. Required if `enable_backend_pool` is true. - - `tags` - (optional|map) Tags to assign to the interface and public IP (if created). Overrides contents of `tags` variable. + Following configuration options are available: + + - `name` - (`string`, required) the interface name + - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in + - `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When + skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not + to change as long as the VM is running. Any stop/deallocate/restart operation might cause + the IP to change. + - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface + - `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the + interface. When `create_public_ip` is set to `true` this will become a name of a newly + created Public IP interface. Otherwise this is a name of an existing interfaces that will + be sourced and attached to the interface. + - `public_ip_resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that + contains public IP that that will be associated with the interface. Used only when + `create_public_ip` is `false`. + - `attach_to_lb_backend_pool` - (`bool`, optional, defaults to `false`) set to `true` if you would like to associate this + interface with a Load Balancer backend pool. + - `lb_backend_pool_id` - (`string`, optional, defaults to `null`) ID of an existing backend pool to associate the + interface with. Example: - ``` + ```hcl [ + # management interface with a new public IP { name = "fw-mgmt" subnet_id = azurerm_subnet.my_mgmt_subnet.id - public_ip_address_id = azurerm_public_ip.my_mgmt_ip.id + public_ip_name = "fw-mgmt-pip" create_public_ip = true }, + # public interface reusing an existing public IP resource { - name = "fw-public" - subnet_id = azurerm_subnet.my_pub_subnet.id - lb_backend_pool_id = module.inbound_lb.backend_pool_id - enable_backend_pool = true - create_public_ip = false - public_ip_name = "fw-public-ip" + name = "fw-public" + subnet_id = azurerm_subnet.my_pub_subnet.id + attach_to_lb_backend_pool = true + lb_backend_pool_id = module.inbound_lb.backend_pool_id + create_public_ip = false + public_ip_name = "fw-public-pip" }, ] ``` EOF - type = list(any) -} - -variable "username" { - description = "Initial administrative username to use for VM-Series. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm)." - type = string -} - -variable "password" { - description = "Initial administrative password to use for VM-Series. If not defined the `ssh_key` variable must be specified. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm)." - default = null - type = string - sensitive = true -} - -variable "ssh_keys" { - description = <<-EOF - A list of initial administrative SSH public keys that allow key-pair authentication. - - This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available: - - ``` - [ - file("/path/to/public/keys/key_1.pub"), - file("/path/to/public/keys/key_2.pub") - ] - ``` - - If the `password` variable is also set, VM-Series will accept both authentication methods. - EOF - default = [] - type = list(string) -} - -variable "managed_disk_type" { - description = "Type of OS Managed Disk to create for the virtual machine. Possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs." - default = "StandardSSD_LRS" - type = string -} - -variable "os_disk_name" { - description = "Optional name of the OS disk to create for the virtual machine. If empty, the name is auto-generated." - default = null - type = string -} - -variable "vm_size" { - description = "Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported." - default = "Standard_D3_v2" - type = string -} - -variable "custom_image_id" { - description = "Absolute ID of your own Custom Image to be used for creating new VM-Series. If set, the `username`, `password`, `img_version`, `img_publisher`, `img_offer`, `img_sku` inputs are all ignored (these are used only for published images, not custom ones). The Custom Image is expected to contain PAN-OS software." - default = null - type = string -} - -variable "enable_plan" { - description = "Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku \"byol\", which means \"bring your own license\", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image." - default = true - type = bool -} - -variable "img_publisher" { - description = "The Azure Publisher identifier for a image which should be deployed." - default = "paloaltonetworks" -} - -variable "img_offer" { - description = "The Azure Offer identifier corresponding to a published image. For `img_version` 9.1.1 or above, use \"vmseries-flex\"; for 9.1.0 or below use \"vmseries1\"." - default = "vmseries-flex" -} - -variable "img_sku" { - description = "VM-series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - type = string -} - -variable "img_version" { - description = "VM-series PAN-OS version - list available for a default `img_offer` with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`" - default = "10.1.0" - type = string -} - -variable "tags" { - description = "A map of tags to be associated with the resources created." - default = {} - type = map(any) -} - -variable "identity_type" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_type)." - default = "SystemAssigned" - type = string -} - -variable "identity_ids" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_ids)." - default = null - type = list(string) -} - -variable "accelerated_networking" { - description = "Enable Azure accelerated networking (SR-IOV) for all network interfaces except the primary one (it is the PAN-OS management interface, which [does not support](https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-new-features/virtualization-features/support-for-azure-accelerated-networking-sriov) acceleration)." - default = true - type = bool -} - -variable "bootstrap_options" { - description = <<-EOF - Bootstrap options to pass to VM-Series instance. - - Proper syntax is a string of semicolon separated properties. - Example: - bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" - - A list of available properties: storage-account, access-key, file-share, share-directory, type, ip-address, default-gateway, netmask, ipv6-address, ipv6-default-gateway, hostname, panorama-server, panorama-server-2, tplname, dgname, dns-primary, dns-secondary, vm-auth-key, op-command-modes, op-cmd-dpdk-pkt-io, plugin-op-commands, dhcp-send-hostname, dhcp-send-client-id, dhcp-accept-server-hostname, dhcp-accept-server-domain, auth-key, vm-series-auto-registration-pin-value, vm-series-auto-registration-pin-id. - - For more details on bootstrapping see documentation: https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components - EOF - default = "" - type = string - sensitive = true -} - -variable "diagnostics_storage_uri" { - description = "The storage account's blob endpoint to hold diagnostic files." - default = null - type = string + type = list(object({ + name = string + subnet_id = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + lb_backend_pool_id = optional(string) + attach_to_lb_backend_pool = optional(bool, false) + })) + validation { + condition = alltrue([ + for v in var.interfaces : v.public_ip_name != null if v.create_public_ip + ]) + error_message = "The `public_ip_name` property is required when `create_public_ip` is set to `true`." + } + validation { + condition = alltrue([ + for v in var.interfaces : v.lb_backend_pool_id != null if v.attach_to_lb_backend_pool + ]) + error_message = "The `lb_backend_pool_id` cannot be `null` when `attach_to_lb_backend_pool` is set to `true`." + } } diff --git a/modules/vmss/.header.md b/modules/vmss/.header.md index 0304aaac..c5da546c 100644 --- a/modules/vmss/.header.md +++ b/modules/vmss/.header.md @@ -3,8 +3,8 @@ A terraform module for deploying a Scale Set based on Next Generation Firewalls in Azure. **NOTE!** \ -Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running in a -Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. +Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running +in a Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. For this mode to actually work the `roll_instances_when_required` provider feature has to be also configured and set to `false`. Unfortunately this cannot be set in the `vmss` module, it has to be specified in the **root** module. @@ -30,7 +30,7 @@ a Load Balancer. This provides some obstacles when deploying such setup with Next Generation Firewall based Scale Set: most importantly the health probe would target the management interface which could lead to false-positives. A management service can respond to TCP/Http probes, while the data plane remains unconfigured. An easy solution would to bo configure an interface swap, unfortunately this -is not available in the Azure VMSeries image yet. +is not available in the Azure VM-Series image yet. ## Custom Metrics and Autoscaling @@ -44,7 +44,7 @@ This however requires some additional steps: - `metrics_instrumentation_keys` - a map of instrumentation keys for the deployed Application Insights instances - configure this module with the ID of the desired Application Insights instance, use the [`var.autoscaling_configuration.application_insights_id`](#autoscaling_configuration) property -- depending on the bootstrap method you use, configure the PanOS VMSeries plugins with the metrics instrumentation key +- depending on the bootstrap method you use, configure the PAN-OS VM-Series plugins with the metrics instrumentation key belonging to the Application Insights instance of your choice. The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain diff --git a/modules/vmss/README.md b/modules/vmss/README.md index c2233a4f..24103870 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -4,8 +4,8 @@ A terraform module for deploying a Scale Set based on Next Generation Firewalls in Azure. **NOTE!** \ -Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running in a -Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. +Due to [lack of proper method of running health probes](#about-rolling-upgrades-and-auto-healing) against Pan-OS based VMs running +in a Scale Set, the `upgrade_mode` property is hardcoded to `Manual`. For this mode to actually work the `roll_instances_when_required` provider feature has to be also configured and set to `false`. Unfortunately this cannot be set in the `vmss` module, it has to be specified in the **root** module. @@ -31,7 +31,7 @@ a Load Balancer. This provides some obstacles when deploying such setup with Next Generation Firewall based Scale Set: most importantly the health probe would target the management interface which could lead to false-positives. A management service can respond to TCP/Http probes, while the data plane remains unconfigured. An easy solution would to bo configure an interface swap, unfortunately this -is not available in the Azure VMSeries image yet. +is not available in the Azure VM-Series image yet. ## Custom Metrics and Autoscaling @@ -45,7 +45,7 @@ This however requires some additional steps: - `metrics_instrumentation_keys` - a map of instrumentation keys for the deployed Application Insights instances - configure this module with the ID of the desired Application Insights instance, use the [`var.autoscaling_configuration.application_insights_id`](#autoscaling\_configuration) property -- depending on the bootstrap method you use, configure the PanOS VMSeries plugins with the metrics instrumentation key +- depending on the bootstrap method you use, configure the PAN-OS VM-Series plugins with the metrics instrumentation key belonging to the Application Insights instance of your choice. The metrics gathered within a single Azure Application Insights instance provided by the module, cannot be split to obtain @@ -191,8 +191,8 @@ A map defining authentication settings (including username and password). Following properties are available: -- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VMseries username -- `password` - (`string`, optional, defaults to `null`) the initial administrative VMSeries password +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series username +- `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys @@ -225,18 +225,18 @@ Basic Azure VM configuration. Following properties are available: -- `version` - (`string`, optional, defaults to `null`) VMSeries PAN-OS version; list available with +- `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image which should be deployed - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a published image -- `sku` - (`string`, optional, defaults to `byol`) VMSeries SKU, list available with +- `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU, list available with `az vm image list -o table --all --publisher paloaltonetworks` - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan on Azure Market Place -- `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PanOS image to be used for - creating new Virtual Machines +- `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines **Important!** \ `custom_id` and `version` properties are mutually exclusive. diff --git a/modules/vmss/variables.tf b/modules/vmss/variables.tf index 5d2b8bcd..8d830822 100644 --- a/modules/vmss/variables.tf +++ b/modules/vmss/variables.tf @@ -25,8 +25,8 @@ variable "authentication" { Following properties are available: - - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VMseries username - - `password` - (`string`, optional, defaults to `null`) the initial administrative VMSeries password + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series username + - `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys @@ -64,18 +64,18 @@ variable "image" { Following properties are available: - - `version` - (`string`, optional, defaults to `null`) VMSeries PAN-OS version; list available with + - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image which should be deployed - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a published image - - `sku` - (`string`, optional, defaults to `byol`) VMSeries SKU, list available with + - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU, list available with `az vm image list -o table --all --publisher paloaltonetworks` - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan on Azure Market Place - - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PanOS image to be used for - creating new Virtual Machines + - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used + for creating new Virtual Machines **Important!** \ `custom_id` and `version` properties are mutually exclusive. From 5e8e2660b79d9adef45261abe9ba456dfc64bf6d Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Tue, 23 Jan 2024 14:02:04 +0100 Subject: [PATCH 18/49] refactor(module/gwlb): Refactor Gateway Load Balancer module (#372) --- examples/gwlb_with_vmseries/README.md | 137 ----- examples/gwlb_with_vmseries/example.tfvars | 264 --------- .../files/init-cfg.sample.txt | 2 - examples/gwlb_with_vmseries/main.tf | 332 ----------- examples/gwlb_with_vmseries/main_test.go | 79 --- examples/gwlb_with_vmseries/outputs.tf | 37 -- .../templates/bootstrap-gwlb.tftpl | 520 ------------------ examples/gwlb_with_vmseries/variables.tf | 357 ------------ examples/gwlb_with_vmseries/versions.tf | 25 - modules/gwlb/.header.md | 81 +++ modules/gwlb/README.md | 347 ++++++++++-- modules/gwlb/main.tf | 42 +- modules/gwlb/main_test.go | 2 +- modules/gwlb/variables.tf | 200 +++++-- modules/gwlb/versions.tf | 4 +- 15 files changed, 570 insertions(+), 1859 deletions(-) delete mode 100644 examples/gwlb_with_vmseries/README.md delete mode 100644 examples/gwlb_with_vmseries/example.tfvars delete mode 100644 examples/gwlb_with_vmseries/files/init-cfg.sample.txt delete mode 100644 examples/gwlb_with_vmseries/main.tf delete mode 100644 examples/gwlb_with_vmseries/main_test.go delete mode 100644 examples/gwlb_with_vmseries/outputs.tf delete mode 100644 examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl delete mode 100644 examples/gwlb_with_vmseries/variables.tf delete mode 100644 examples/gwlb_with_vmseries/versions.tf create mode 100644 modules/gwlb/.header.md diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md deleted file mode 100644 index 2e30f781..00000000 --- a/examples/gwlb_with_vmseries/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# VM-Series Azure Gateway Load Balancer example - -The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing Azure Gateway Load Balancer in service chain model as described in the following [document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). - -## Usage - -### Deployment Steps - -* Checkout the code locally. -* Copy `example.tfvars` to `terraform.tfvars` and adjust it to your needs. -* Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) for details). -* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. -* Initialize the Terraform module: - - terraform init - -* (optional) Plan you infrastructure to see what will be actually deployed: - - terraform plan - -* Deploy the infrastructure: - - terraform apply - -* At this stage you have to wait a few minutes for the firewalls to bootstrap. - -### Post deploy - -Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: - -* for username: - - terraform output username - -* for password: - - terraform output password - -The management public IP addresses are available in the `vmseries_mgmt_ips` output: - -```sh -terraform output vmseries_mgmt_ips -``` - -You can now login to the devices using either: - -* CLI - ssh client is required -* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. - -With default example configuration, the devices already contain `DAY0` configuration, so all network interfaces should be configured and Azure Gateway Load Balancer should already report that the devices are healthy. - -You can now proceed with licensing the devices and configuring your first rules. - -Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for `DAY1` configuration (security hardening). - -### Cleanup - -To remove the deployed infrastructure run: - -```sh -terraform destroy -``` - -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0.0, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_http"></a> [http](#provider\_http) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | -| <a name="provider_local"></a> [local](#provider\_local) | n/a | -| <a name="provider_random"></a> [random](#provider\_random) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_gwlb"></a> [gwlb](#module\_gwlb) | ../../modules/gwlb | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_bootstrap"></a> [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| <a name="module_bootstrap_share"></a> [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| <a name="module_vmseries"></a> [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_appvm"></a> [appvm](#module\_appvm) | ../../modules/virtual_machine | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_availability_set.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [local_file.bootstrap_xml](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | -| [random_password.appvms](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [random_password.vmseries](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | -| [http_http.this](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix for resource names. | `string` | `""` | no | -| <a name="input_location"></a> [location](#input\_location) | Location where the resources will be deployed. | `string` | n/a | yes | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to create or use. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to all of the created resources. | `map(string)` | `{}` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | Map with VNet definitions. Each item supports following inputs for `vnet` module:<br>- `name` - (required\|string) VNet name.<br>- `create_virtual_network` - (optional\|bool) Whether to create a new or source an existing VNet, defaults to `true`.<br>- `address_space` - (optional\|list) List of CIDRs for the new VNet.<br>- `resource_group_name` - (optional\|string) VNet's Resource Group, by default the one specified by `var.resource_group_name`.<br>- `create_subnets` - (optional\|bool) Whether to create or source items from `subnets`, defaults to `true`.<br>- `subnets` - (required\|map) Subnet definitions.<br>- `network_security_groups` - (optional\|map) NSGs to create.<br>- `route_tables` - (optional\|map) Route Tables to create.<br><br>Please consult [module documentation](../../modules/vnet/README.md) for details. | `any` | n/a | yes | -| <a name="input_gateway_load_balancers"></a> [gateway\_load\_balancers](#input\_gateway\_load\_balancers) | Map with Gateway Load Balancer definitions. Following settings are supported:<br>- `name` - (required\|string) Gateway Load Balancer name.<br>- `vnet_key` - (required\|string) Key of a VNet from `var.vnets` that contains target Subnet for LB's frontned. Used to get Subnet ID in combination with `subnet_key` below.<br>- `subnet_key` - (required\|string) Key of a Subnet from `var.vnets[vnet_key]`.<br>- `frontend_ip_config` - (optional\|map) Remaining Frontned IP configuration.<br>- `resource_group_name` - (optional\|string) LB's Resource Group, by default the one specified by `var.resource_group_name`.<br>- `backends` - (optional\|map) LB's backend configurations.<br>- `heatlh_probe` - (optional\|map) Health probe configuration.<br><br>Please consult [module documentation](../../modules/gwlb/README.md) for details. | `any` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [module documentation](../modules/application\_insights/README.md)):<br><br>- `name` - (optional\|string) Name of a single AI instance<br>- `workspace_mode` - (optional\|bool) Use AI Workspace mode instead of the Classical (deprecated), defaults to `true`.<br>- `workspace_name` - (optional\|string) Name of the Log Analytics Workspace created when AI is deployed in Workspace mode, defaults to AI name suffixed with `-wrkspc`.<br>- `workspace_sku` - (optional\|string) SKU used by WAL, see module documentation for details, defaults to PerGB2018.<br>- `metrics_retention_in_days` - (optional\|number) Defaults to current Azure default value, see module documentation for details.<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_bootstrap_storages"></a> [bootstrap\_storages](#input\_bootstrap\_storages) | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property.<br>Following properties are supported:<br>- `name` - (required\|string) Name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`.<br>- `create_storage_account` - (optional\|bool) Whether to create or source an existing Storage Account, defaults to `true`.<br>- `resource_group_name` - (optional\|string) Name of the Resource Group hosting the Storage Account, defaults to `var.resource_group_name`.<br>- `storage_acl` - (optional\|bool) Allows to enable network ACLs on the Storage Account. If set to `true`, `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. Defaults to `false`.<br>- `storage_allow_vnet_subnets` - (optional\|map) Map with objects that contains `vnet_key`/`subnet_key` used to identify subnets allowed to access the Storage Account. Note that `enable_storage_service_endpoint` has to be set to `true` in the corresponding subnet configuration.<br>- `storage_allow_inbound_public_ips` - (optional\|list) Whitelist that contains public IPs/ranges allowed to access the Storage Account. Note that the code automatically to queries https://ifcondif.me to obtain the public IP address of the machine executing the code to enable bootstrap files upload. | `any` | `{}` | no | -| <a name="input_vmseries_common"></a> [vmseries\_common](#input\_vmseries\_common) | Configuration common for all firewall instances. Following settings can be specified:<br>- `username` - (required\|string)<br>- `password` - (optional\|string)<br>- `ssh_keys` - (optional\|string)<br>- `img_version` - (optional\|string)<br>- `img_sku` - (optional\|string)<br>- `vm_size` - (optional\|string)<br>- `bootstrap_options` - (optional\|string)<br>- `vnet_key` - (optional\|string)<br>- `interfaces` - (optional\|list(object))<br>- `ai_update_interval` - (optional\|number)<br><br>All are used directly as inputs for `vmseries` module (please see [documentation](../../modules/vmseries/README.md) for details), except for the last three:<br>- `vnet_key` - (required\|string) Used to identify VNet in which subnets for interfaces exist.<br>- `ai_update_interval` - (optional\|number) If Application Insights are used this property can override the default metrics update interval (in minutes). | `any` | n/a | yes | -| <a name="input_vmseries"></a> [vmseries](#input\_vmseries) | Map with VM-Series instance specific configuration. Following properties are supported:<br>- `name` - (required\|string) Instance name.<br>- `avzone` - (optional\|string) AZ to deploy instance in, defaults to "1".<br>- `availability_set_key` - (optional\|string) Key from `var.availability_sets`, used to determine Availabbility Set ID.<br>- `bootstrap_storage` - (optional\|map) Map that contains bootstrap package contents definition, when present triggers creation of a File Share in an existing Storage Account. Following properties supported:<br> - `key` - (required\|string) Identifies Storage Account to use from `var.bootstrap_storages`.<br> - `static_files` - (optional\|map) Map where keys are local file paths, values determine destination in the bootstrap package (file share) where the file will be copied.<br> - `template_bootstrap_xml` - (optional\|string) Path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and it's upload to the boostrap package. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in `var.vmseries_common`). The properties are listed below.<br>- `interfaces` - List of objects with interface definitions. Utilizes all properties of `interfaces` input (see [documantation](../../modules/vmseries/README.md#inputs)), expect for `subnet_id` and `lb_backend_pool_id`, which are determined based on the following new items:<br> - `subnet_key` - (optional\|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with.<br> - `gwlb_key` - (optional\|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`.<br> - `gwlb_backend_key` - (optional\|string) Key that identifies a backend from the GWLB selected by `gwlb_key` to associate th interface with, required when `enable_backend_pool` is `true`.<br><br>Additionally, it's possible to override following settings from `var.vmseries_common`:<br>- `bootstrap_options` - When defined, it not only takes precedence over `var.vmseries_common.bootstrap_options`, but also over `bootstrap_storage` described below.<br>- `img_version`<br>- `img_sku`<br>- `vm_size`<br>- `ai_update_interval` | `map(any)` | n/a | yes | -| <a name="input_availability_sets"></a> [availability\_sets](#input\_availability\_sets) | A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used.<br><br>Following properties are supported:<br>- `name` - (required\|string) Name of the Application Insights.<br>- `update_domain_count` - (optional\|int) Specifies the number of update domains that are used, defaults to 5 (Azure defaults).<br>- `fault_domain_count` - (optional\|int) Specifies the number of fault domains that are used, defaults to 3 (Azure defaults).<br><br>Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br>Following properties are available (for details refer to module's documentation):<br>- `name` - (required\|string) Name of the Load Balancer resource.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips` - (optional\|string) Public LB only - list of IP addresses that will be allowed in the ingress rules.<br>- `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).<br>- `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:<br> - `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<br> - `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<br> - `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<br> - `private_ip_address` - (optional\|string) Private LB only - defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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.<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "internal_app_lb"<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "internal_app_vnet"<br> subnet_key = "internal_app_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_appvms_common"></a> [appvms\_common](#input\_appvms\_common) | Common settings for sample applications:<br>- `username` - (required\|string)<br>- `password` - (optional\|string)<br>- `ssh_keys` - (optional\|list(string)<br>- `vm_size` - (optional\|string)<br>- `disk_type` - (optional\|string)<br>- `accelerated_networking` - (optional\|bool)<br><br>At least one of `password` or `ssh_keys` has to be provided. | `any` | n/a | yes | -| <a name="input_appvms"></a> [appvms](#input\_appvms) | Configuration for sample application VMs. Available settings:<br>- `name` - (required\|string) Instance name.<br>- `avzone` - (optional\|string) AZ to deploy instance in, defaults to "1".<br>- `vnet_key` - (required\|string) Used to identify VNet in which subnets for interfaces exist.<br>- `subnet_key` - (required\|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with.<br>- `load_balancer_key` - (optional\|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`. | `any` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_vmseries_mgmt_ips"></a> [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for VM-Series management. | -| <a name="output_gwlb_frontend_ip_configuration_ids"></a> [gwlb\_frontend\_ip\_configuration\_ids](#output\_gwlb\_frontend\_ip\_configuration\_ids) | Configuration IDs of Gateway Load Balancers' frontends. | -| <a name="output_appvms_username"></a> [appvms\_username](#output\_appvms\_username) | Initial administrative username to use for application VMs. | -| <a name="output_appvms_password"></a> [appvms\_password](#output\_appvms\_password) | Initial administrative password to use for application VMs. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP addresses of the Load Balancers serving applications. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars deleted file mode 100644 index 8a6d426f..00000000 --- a/examples/gwlb_with_vmseries/example.tfvars +++ /dev/null @@ -1,264 +0,0 @@ -# Common -name_prefix = "example-" -location = "North Europe" -resource_group_name = "vmseries-gwlb" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - -# VNets -vnets = { - security = { - name = "security" - address_space = ["10.0.1.0/24"] - subnets = { - mgmt = { - name = "vmseries-mgmt" - address_prefixes = ["10.0.1.0/28"] - network_security_group = "mgmt" - enable_storage_service_endpoint = true - } - data = { - name = "vmseries-data" - address_prefixes = ["10.0.1.16/28"] - network_security_group = "data" - } - } - network_security_groups = { - mgmt = { - name = "vmseries-mgmt" - rules = { - mgmt_inbound = { - name = "vmseries-management-allow-inbound" - priority = 100 - direction = "Inbound" - access = "Allow" - protocol = "Tcp" - source_address_prefixes = ["191.191.191.191"] # Put your own public IP address here - source_port_range = "*" - destination_address_prefix = "*" - destination_port_ranges = ["22", "443"] - } - } - } - data = { - name = "vmseries-data" - } - } - route_tables = { - mgmt = { - name = "vmseries-mgmt" - routes = { - data-blackhole = { - name = "data-blackhole-udr" - address_prefix = "10.0.1.16/28" - next_hop_type = "None" - } - } - } - data = { - name = "vmseries-data" - routes = { - mgmt-blackhole = { - name = "mgmt-blackhole-udr" - address_prefix = "10.0.1.0/28" - next_hop_type = "None" - } - } - } - } - } - - app1 = { - name = "app1-vnet" - address_space = ["10.0.2.0/24"] - subnets = { - web = { - name = "app1-web" - address_prefixes = ["10.0.2.0/28"] - network_security_group = "web" - } - } - network_security_groups = { - web = { - name = "app1-web" - rules = { - application-inbound = { - name = "application-allow-inbound" - priority = 100 - direction = "Inbound" - access = "Allow" - protocol = "Tcp" - source_address_prefixes = ["191.191.191.191"] # Put your own public IP address here - source_port_range = "*" - destination_address_prefix = "*" - destination_port_ranges = ["80", "443"] - } - } - } - } - } -} - -gateway_load_balancers = { - gwlb = { - name = "vmseries-gwlb" - vnet_key = "security" - subnet_key = "data" - - health_probe = { - port = 80 - } - - backends = { - ext-int = { - tunnel_interfaces = { - internal = { - identifier = 800 - port = 2000 - protocol = "VXLAN" - type = "Internal" - } - external = { - identifier = 801 - port = 2001 - protocol = "VXLAN" - type = "External" - } - } - } - } - } -} - -# VM-Series -bootstrap_storages = { - bootstrap = { - name = "vmseriesgwlbboostrap" - storage_acl = true - storage_allow_vnet_subnets = { - management = { - vnet_key = "security" - subnet_key = "mgmt" - } - } - } -} - -vmseries = { - vms01 = { - avzone = 1 - name = "vmseries01" - bootstrap_storage = { - key = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap-gwlb.tftpl" - } - interfaces = [ - { - name = "mgmt" - subnet_key = "mgmt" - create_public_ip = true - }, - { - name = "data" - subnet_key = "data" - enable_backend_pool = true - gwlb_key = "gwlb" - gwlb_backend_key = "ext-int" - } - ] - } - vms02 = { - avzone = 2 - name = "vmseries02" - bootstrap_storage = { - key = "bootstrap" - static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } - template_bootstrap_xml = "templates/bootstrap-gwlb.tftpl" - } - interfaces = [ - { - name = "mgmt" - subnet_key = "mgmt" - create_public_ip = true - }, - { - name = "data" - subnet_key = "data" - enable_backend_pool = true - gwlb_key = "gwlb" - gwlb_backend_key = "ext-int" - } - ] - } -} - -vmseries_common = { - username = "panadmin" - ssh_keys = [] # Update here if required - - img_version = "10.2.3" - img_sku = "byol" - vm_size = "Standard_D3_v2" - - vnet_key = "security" -} - -# Sample application -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" - } - } - out_rules = { - outbound = { - name = "tcp-outbound" - protocol = "Tcp" - } - } - } - } - } -} - -appvms_common = { - username = "appadmin" - custom_data = <<SCRIPT -#!/bin/sh -sudo apt-get update -sudo apt-get install -y nginx -sudo systemctl start nginx -sudo systemctl enable nginx -echo "Backend VM is $(hostname)" | sudo tee /var/www/html/index.html -SCRIPT -} - -appvms = { - app1vm01 = { - name = "app1-vm01" - avzone = "3" - vnet_key = "app1" - subnet_key = "web" - load_balancer_key = "app1" - } -} \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/files/init-cfg.sample.txt b/examples/gwlb_with_vmseries/files/init-cfg.sample.txt deleted file mode 100644 index c4373843..00000000 --- a/examples/gwlb_with_vmseries/files/init-cfg.sample.txt +++ /dev/null @@ -1,2 +0,0 @@ -type=dhcp-client -plugin-op-commands=azure-gwlb-inspect:enable \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf deleted file mode 100644 index ca3ab0da..00000000 --- a/examples/gwlb_with_vmseries/main.tf +++ /dev/null @@ -1,332 +0,0 @@ -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] - vmseries_password = try(var.vmseries_common.password, random_password.vmseries[0].result) - appvms_password = try(var.appvms_common.password, random_password.appvms[0].result) -} - -# Obtain Public IP address of deployment machine -data "http" "this" { - count = length(var.bootstrap_storages) > 0 && anytrue([for v in values(var.bootstrap_storages) : try(v.storage_acl, false)]) ? 1 : 0 - url = "https://ifconfig.me" -} - -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - - name = "${var.name_prefix}${var.resource_group_name}" - location = var.location - - tags = var.tags -} - -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - - name = "${var.name_prefix}${var.resource_group_name}" -} - -# VNets -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - tags = var.tags -} - -# Gateway Load Balancers -module "gwlb" { - for_each = var.gateway_load_balancers - source = "../../modules/gwlb" - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location - - backends = each.value.backends - health_probe = each.value.health_probe - - frontend_ip_config = { - name = try(each.value.frontend_ip_config.name, "${var.name_prefix}${each.value.name}") - private_ip_address_allocation = try(each.value.frontend_ip_config.private_ip_address_allocation, null) - private_ip_address_version = try(each.value.frontend_ip_config.private_ip_address_version, null) - private_ip_address = try(each.value.frontend_ip_config.private_ip_address, null) - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - zones = var.enable_zones ? try(each.value.frontend_ip_config.zones, null) : null - } - - tags = var.tags -} - -# VM-Series -module "ngfw_metrics" { - source = "../../modules/ngfw_metrics" - - count = var.ngfw_metrics != null ? 1 : 0 - - create_workspace = var.ngfw_metrics.create_workspace - - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location - - log_analytics_workspace = { - sku = var.ngfw_metrics.sku - metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days - } - - application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } - - tags = var.tags -} - -resource "local_file" "bootstrap_xml" { - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage.template_bootstrap_xml) } - - filename = "files/${each.key}-bootstrap.xml" - content = templatefile( - each.value.bootstrap_storage.template_bootstrap_xml, - { - data_gateway_ip = cidrhost( - module.vnet[var.vmseries_common.vnet_key].subnet_cidrs[each.value.interfaces[1].subnet_key], - 1 - ) - ai_instr_key = try(module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], null) - - ai_update_interval = try( - each.value.ai_update_interval, - var.vmseries_common.ai_update_interval, - 5 - ) - } - ) - - depends_on = [ - module.ngfw_metrics, - module.vnet - ] -} - -module "bootstrap" { - for_each = var.bootstrap_storages - source = "../../modules/bootstrap" - - name = each.value.name - create_storage_account = try(each.value.create_storage, true) - resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location - - storage_acl = try(each.value.storage_acl, false) - storage_allow_vnet_subnet_ids = try([for v in each.value.storage_allow_vnet_subnets : module.vnet[v.vnet_key].subnet_ids[v.subnet_key]], []) - storage_allow_inbound_public_ips = concat(try(each.value.storage_allow_inbound_public_ips, []), try([data.http.this[0].response_body], [])) - - tags = var.tags -} - -module "bootstrap_share" { - source = "../../modules/bootstrap" - - for_each = { for k, v in var.vmseries : k => v if can(v.bootstrap_storage) } - - create_storage_account = false - name = module.bootstrap[each.value.bootstrap_storage.key].storage_account.name - resource_group_name = try(var.bootstrap_storages[each.value.bootstrap_storage.key].resource_group_name, local.resource_group.name) - location = var.location - storage_share_name = "${var.name_prefix}${each.value.name}" - - files = merge( - each.value.bootstrap_storage.static_files, - can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.key}-bootstrap.xml" = "config/bootstrap.xml" - } : {} - ) - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.key}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} - - tags = var.tags - - depends_on = [ - local_file.bootstrap_xml, - module.bootstrap - ] -} - -resource "random_password" "vmseries" { - count = try(var.vmseries_common.password, null) == null ? 1 : 0 - - length = 16 - min_lower = 16 - 4 - min_numeric = 1 - min_special = 1 - min_upper = 1 - override_special = "_%@" -} - -resource "azurerm_availability_set" "this" { - for_each = var.availability_sets - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name - location = var.location - platform_update_domain_count = try(each.value.update_domain_count, null) - platform_fault_domain_count = try(each.value.fault_domain_count, null) - - tags = var.tags -} - -module "vmseries" { - for_each = var.vmseries - source = "../../modules/vmseries" - - name = "${var.name_prefix}${each.value.name}" - location = var.location - resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzone = try(each.value.avzone, 1) - avset_id = try(azurerm_availability_set.this[each.value.availability_set_key].id, null) - - username = var.vmseries_common.username - password = local.vmseries_password - ssh_keys = var.vmseries_common.ssh_keys - img_version = try(each.value.img_version, var.vmseries_common.img_version) - img_sku = try(each.value.img_sku, var.vmseries_common.img_sku) - vm_size = try(each.value.vm_size, var.vmseries_common.vm_size) - - bootstrap_options = try( - each.value.bootstrap_options, - var.vmseries_common.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[each.value.bootstrap_storage.key].storage_account.name}", - "access-key=${module.bootstrap[each.value.bootstrap_storage.key].storage_account.primary_access_key}", - "file-share=${var.name_prefix}${each.value.name}", - "share-directory=None" - ]), - "" - ) - - interfaces = [for interface in each.value.interfaces : - { - name = "${var.name_prefix}${each.value.name}-${interface.name}" - subnet_id = module.vnet[var.vmseries_common.vnet_key].subnet_ids[interface.subnet_key] - create_public_ip = try(interface.create_public_ip, false) - public_ip_name = try(interface.public_ip_name, null) - public_ip_resource_group_name = try(interface.public_ip_resource_group_name, null) - private_ip_address = try(interface.private_ip_address, null) - enable_backend_pool = try(interface.enable_backend_pool, false) - lb_backend_pool_id = try(interface.enable_backend_pool, false) ? module.gwlb[interface.gwlb_key].backend_pool_ids[interface.gwlb_backend_key] : null - tags = try(interface.tags, null) - } - ] - - tags = var.tags - - depends_on = [ - module.vnet, - module.bootstrap, - module.bootstrap_share, - azurerm_availability_set.this - ] -} - -# Sample application VMs and Load Balancers -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 - - 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 => 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 - depends_on = [module.vnet] -} - - - -resource "random_password" "appvms" { - count = try(var.appvms_common.password, null) == null ? 1 : 0 - - length = 16 - min_lower = 16 - 4 - min_numeric = 1 - min_special = 1 - min_upper = 1 -} - -module "appvm" { - for_each = var.appvms - source = "../../modules/virtual_machine" - - name = "${var.name_prefix}${each.value.name}" - location = var.location - resource_group_name = local.resource_group.name - avzone = each.value.avzone - - interfaces = [ - { - name = "${var.name_prefix}${each.value.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - enable_backend_pool = true - lb_backend_pool_id = module.load_balancer[each.value.load_balancer_key].backend_pool_id - }, - ] - - username = try(var.appvms_common.username, null) - password = try(local.appvms_password) - ssh_keys = try(var.appvms_common.ssh_keys, []) - custom_data = try(var.appvms_common.custom_data, null) - - vm_size = try(var.appvms_common.vm_size, "Standard_B1ls") - managed_disk_type = try(var.appvms_common.disk_type, "Standard_LRS") - accelerated_networking = try(var.appvms_common.accelerated_networking, false) - - tags = var.tags -} diff --git a/examples/gwlb_with_vmseries/main_test.go b/examples/gwlb_with_vmseries/main_test.go deleted file mode 100644 index 2540ab9a..00000000 --- a/examples/gwlb_with_vmseries/main_test.go +++ /dev/null @@ -1,79 +0,0 @@ -package gwlb_with_vmseries - -import ( - "fmt" - "log" - "os" - "testing" - - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" - "github.com/gruntwork-io/terratest/modules/logger" - "github.com/gruntwork-io/terratest/modules/terraform" -) - -func CreateTerraformOptions(t *testing.T) *terraform.Options { - // prepare random prefix - randomNames, _ := testskeleton.GenerateTerraformVarsInfo("azure") - storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\", public_snet_key = \"public\", private_snet_key = \"private\", intranet_cidr = \"10.0.1.0/24\"} }", randomNames.AzureStorageAccountName) - - // copy the init-cfg.sample.txt file to init-cfg.txt for test purposes - _, err := os.Stat("files/init-cfg.txt") - if err != nil { - buff, err := os.ReadFile("files/init-cfg.sample.txt") - if err != nil { - log.Fatal(err) - } - err = os.WriteFile("files/init-cfg.txt", buff, 0644) - if err != nil { - log.Fatal(err) - } - } - - // 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, - "bootstrap_storages": storageDefinition, - }, - 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/gwlb_with_vmseries/outputs.tf b/examples/gwlb_with_vmseries/outputs.tf deleted file mode 100644 index f8616e39..00000000 --- a/examples/gwlb_with_vmseries/outputs.tf +++ /dev/null @@ -1,37 +0,0 @@ -output "username" { - description = "Initial administrative username to use for VM-Series." - value = var.vmseries_common.username -} - -output "password" { - description = "Initial administrative password to use for VM-Series." - value = local.vmseries_password - sensitive = true -} - -output "vmseries_mgmt_ips" { - description = "IP addresses for VM-Series management." - value = { for k, v in module.vmseries : k => v.mgmt_ip_address } -} - -output "gwlb_frontend_ip_configuration_ids" { - description = "Configuration IDs of Gateway Load Balancers' frontends." - value = { for k, v in module.gwlb : k => v.frontend_ip_config_id } -} - -output "appvms_username" { - description = "Initial administrative username to use for application VMs." - value = var.appvms_common.username - sensitive = true -} - -output "appvms_password" { - description = "Initial administrative password to use for application VMs." - value = local.appvms_password - sensitive = true -} - -output "lb_frontend_ips" { - description = "IP addresses of the Load Balancers serving applications." - value = { for k, v in module.load_balancer : k => v.frontend_ip_configs } -} diff --git a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl deleted file mode 100644 index ffaf7ed3..00000000 --- a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl +++ /dev/null @@ -1,520 +0,0 @@ -<?xml version="1.0"?> -<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> - <mgt-config> - <users> - <entry name="panadmin"> - <phash>*</phash> - <permissions> - <role-based> - <superuser>yes</superuser> - </role-based> - </permissions> - </entry> - </users> - <password-complexity> - <enabled>yes</enabled> - <minimum-length>8</minimum-length> - </password-complexity> - </mgt-config> - <shared> - <application/> - <application-group/> - <service/> - <service-group/> - <botnet> - <configuration> - <http> - <dynamic-dns> - <enabled>yes</enabled> - <threshold>5</threshold> - </dynamic-dns> - <malware-sites> - <enabled>yes</enabled> - <threshold>5</threshold> - </malware-sites> - <recent-domains> - <enabled>yes</enabled> - <threshold>5</threshold> - </recent-domains> - <ip-domains> - <enabled>yes</enabled> - <threshold>10</threshold> - </ip-domains> - <executables-from-unknown-sites> - <enabled>yes</enabled> - <threshold>5</threshold> - </executables-from-unknown-sites> - </http> - <other-applications> - <irc>yes</irc> - </other-applications> - <unknown-applications> - <unknown-tcp> - <destinations-per-hour>10</destinations-per-hour> - <sessions-per-hour>10</sessions-per-hour> - <session-length> - <maximum-bytes>100</maximum-bytes> - <minimum-bytes>50</minimum-bytes> - </session-length> - </unknown-tcp> - <unknown-udp> - <destinations-per-hour>10</destinations-per-hour> - <sessions-per-hour>10</sessions-per-hour> - <session-length> - <maximum-bytes>100</maximum-bytes> - <minimum-bytes>50</minimum-bytes> - </session-length> - </unknown-udp> - </unknown-applications> - </configuration> - <report> - <topn>100</topn> - <scheduled>yes</scheduled> - </report> - </botnet> - </shared> - <devices> - <entry name="localhost.localdomain"> - <network> - <interface> - <ethernet> - <entry name="ethernet1/1"> - <layer3> - <ndp-proxy> - <enabled>no</enabled> - </ndp-proxy> - <sdwan-link-settings> - <upstream-nat> - <enable>no</enable> - <static-ip/> - </upstream-nat> - <enable>no</enable> - </sdwan-link-settings> - <dhcp-client> - <create-default-route>no</create-default-route> - </dhcp-client> - <interface-management-profile>gwlb-healthcheck</interface-management-profile> - <lldp> - <enable>no</enable> - </lldp> - <units> - <entry name="ethernet1/1.1"> - <ipv6> - <neighbor-discovery> - <router-advertisement> - <enable>no</enable> - </router-advertisement> - </neighbor-discovery> - </ipv6> - <ndp-proxy> - <enabled>no</enabled> - </ndp-proxy> - <sdwan-link-settings> - <enable>no</enable> - </sdwan-link-settings> - <adjust-tcp-mss> - <enable>no</enable> - </adjust-tcp-mss> - <tag>1</tag> - </entry> - <entry name="ethernet1/1.2"> - <ipv6> - <neighbor-discovery> - <router-advertisement> - <enable>no</enable> - </router-advertisement> - </neighbor-discovery> - </ipv6> - <ndp-proxy> - <enabled>no</enabled> - </ndp-proxy> - <sdwan-link-settings> - <enable>no</enable> - </sdwan-link-settings> - <adjust-tcp-mss> - <enable>no</enable> - </adjust-tcp-mss> - <tag>2</tag> - </entry> - </units> - </layer3> - </entry> - </ethernet> - </interface> - <profiles> - <monitor-profile> - <entry name="default"> - <interval>3</interval> - <threshold>5</threshold> - <action>wait-recover</action> - </entry> - </monitor-profile> - <interface-management-profile> - <entry name="gwlb-healthcheck"> - <http>yes</http> - <permitted-ip> - <entry name="168.63.129.16"/> - </permitted-ip> - </entry> - </interface-management-profile> - </profiles> - <ike> - <crypto-profiles> - <ike-crypto-profiles> - <entry name="default"> - <encryption> - <member>aes-128-cbc</member> - <member>3des</member> - </encryption> - <hash> - <member>sha1</member> - </hash> - <dh-group> - <member>group2</member> - </dh-group> - <lifetime> - <hours>8</hours> - </lifetime> - </entry> - <entry name="Suite-B-GCM-128"> - <encryption> - <member>aes-128-cbc</member> - </encryption> - <hash> - <member>sha256</member> - </hash> - <dh-group> - <member>group19</member> - </dh-group> - <lifetime> - <hours>8</hours> - </lifetime> - </entry> - <entry name="Suite-B-GCM-256"> - <encryption> - <member>aes-256-cbc</member> - </encryption> - <hash> - <member>sha384</member> - </hash> - <dh-group> - <member>group20</member> - </dh-group> - <lifetime> - <hours>8</hours> - </lifetime> - </entry> - </ike-crypto-profiles> - <ipsec-crypto-profiles> - <entry name="default"> - <esp> - <encryption> - <member>aes-128-cbc</member> - <member>3des</member> - </encryption> - <authentication> - <member>sha1</member> - </authentication> - </esp> - <dh-group>group2</dh-group> - <lifetime> - <hours>1</hours> - </lifetime> - </entry> - <entry name="Suite-B-GCM-128"> - <esp> - <encryption> - <member>aes-128-gcm</member> - </encryption> - <authentication> - <member>none</member> - </authentication> - </esp> - <dh-group>group19</dh-group> - <lifetime> - <hours>1</hours> - </lifetime> - </entry> - <entry name="Suite-B-GCM-256"> - <esp> - <encryption> - <member>aes-256-gcm</member> - </encryption> - <authentication> - <member>none</member> - </authentication> - </esp> - <dh-group>group20</dh-group> - <lifetime> - <hours>1</hours> - </lifetime> - </entry> - </ipsec-crypto-profiles> - <global-protect-app-crypto-profiles> - <entry name="default"> - <encryption> - <member>aes-128-cbc</member> - </encryption> - <authentication> - <member>sha1</member> - </authentication> - </entry> - </global-protect-app-crypto-profiles> - </crypto-profiles> - </ike> - <qos> - <profile> - <entry name="default"> - <class-bandwidth-type> - <mbps> - <class> - <entry name="class1"> - <priority>real-time</priority> - </entry> - <entry name="class2"> - <priority>high</priority> - </entry> - <entry name="class3"> - <priority>high</priority> - </entry> - <entry name="class4"> - <priority>medium</priority> - </entry> - <entry name="class5"> - <priority>medium</priority> - </entry> - <entry name="class6"> - <priority>low</priority> - </entry> - <entry name="class7"> - <priority>low</priority> - </entry> - <entry name="class8"> - <priority>low</priority> - </entry> - </class> - </mbps> - </class-bandwidth-type> - </entry> - </profile> - </qos> - <virtual-router> - <entry name="default"> - <protocol> - <bgp> - <enable>no</enable> - <dampening-profile> - <entry name="default"> - <cutoff>1.25</cutoff> - <reuse>0.5</reuse> - <max-hold-time>900</max-hold-time> - <decay-half-life-reachable>300</decay-half-life-reachable> - <decay-half-life-unreachable>900</decay-half-life-unreachable> - <enable>yes</enable> - </entry> - </dampening-profile> - <routing-options> - <graceful-restart> - <enable>yes</enable> - </graceful-restart> - </routing-options> - </bgp> - <rip> - <enable>no</enable> - </rip> - <ospf> - <enable>no</enable> - </ospf> - <ospfv3> - <enable>no</enable> - </ospfv3> - </protocol> - <interface> - <member>ethernet1/1</member> - <member>ethernet1/1.1</member> - <member>ethernet1/1.2</member> - </interface> - <ecmp> - <algorithm> - <ip-modulo/> - </algorithm> - </ecmp> - <routing-table> - <ip> - <static-route> - <entry name="default-route"> - <path-monitor> - <enable>no</enable> - <failure-condition>any</failure-condition> - <hold-time>2</hold-time> - </path-monitor> - <nexthop> - <ip-address>${data_gateway_ip}</ip-address> - </nexthop> - <bfd> - <profile>None</profile> - </bfd> - <interface>ethernet1/1</interface> - <metric>10</metric> - <destination>168.63.129.16/32</destination> - <route-table> - <unicast/> - </route-table> - </entry> - </static-route> - </ip> - </routing-table> - </entry> - </virtual-router> - </network> - <deviceconfig> - <system> - <type> - <dhcp-client> - <send-hostname>yes</send-hostname> - <send-client-id>no</send-client-id> - <accept-dhcp-hostname>no</accept-dhcp-hostname> - <accept-dhcp-domain>no</accept-dhcp-domain> - </dhcp-client> - </type> - <update-server>updates.paloaltonetworks.com</update-server> - <update-schedule> - <threats> - <recurring> - <weekly> - <day-of-week>wednesday</day-of-week> - <at>01:02</at> - <action>download-only</action> - </weekly> - </recurring> - </threats> - </update-schedule> - <timezone>US/Pacific</timezone> - <service> - <disable-telnet>yes</disable-telnet> - <disable-http>yes</disable-http> - </service> - <ntp-servers> - <primary-ntp-server> - <ntp-server-address>0.us.pool.ntp.org</ntp-server-address> - <authentication-type> - <none/> - </authentication-type> - </primary-ntp-server> - <secondary-ntp-server> - <ntp-server-address>1.us.pool.ntp.org</ntp-server-address> - <authentication-type> - <none/> - </authentication-type> - </secondary-ntp-server> - </ntp-servers> - </system> - <setting> - <config> - <rematch>yes</rematch> - </config> - <management> - <hostname-type-in-syslog>FQDN</hostname-type-in-syslog> - <initcfg> - <username>panadmin</username> - <type> - <dhcp-client> - <send-hostname>yes</send-hostname> - <send-client-id>no</send-client-id> - <accept-dhcp-hostname>no</accept-dhcp-hostname> - <accept-dhcp-domain>no</accept-dhcp-domain> - </dhcp-client> - </type> - </initcfg> - </management> - </setting> -%{ if ai_instr_key != null ~} - <plugins> - <vm_series version="2.0.3"> - <azure-advanced-metrics> - <enable>yes</enable> - <instrumentation-key>${ai_instr_key}</instrumentation-key> - <update-interval>${ai_update_interval}</update-interval> - </azure-advanced-metrics> - </vm_series> - </plugins> -%{ endif ~} - </deviceconfig> - <vsys> - <entry name="vsys1"> - <application/> - <application-group/> - <zone> - <entry name="untrust"> - <network> - <layer3> - <member>ethernet1/1.2</member> - </layer3> - </network> - </entry> - <entry name="trust"> - <network> - <layer3> - <member>ethernet1/1</member> - <member>ethernet1/1.1</member> - </layer3> - </network> - </entry> - </zone> - <service/> - <service-group/> - <schedule/> - <rulebase> - <security> - <rules> - <entry name="allow-all" uuid="54cb4573-d2a6-4338-8ee8-8064f14a55ca"> - <to> - <member>any</member> - </to> - <from> - <member>any</member> - </from> - <source> - <member>any</member> - </source> - <destination> - <member>any</member> - </destination> - <source-user> - <member>any</member> - </source-user> - <category> - <member>any</member> - </category> - <application> - <member>any</member> - </application> - <service> - <member>service-http</member> - <member>service-https</member> - </service> - <source-hip> - <member>any</member> - </source-hip> - <destination-hip> - <member>any</member> - </destination-hip> - <action>allow</action> - </entry> - </rules> - </security> - </rulebase> - <import> - <network> - <interface> - <member>ethernet1/1</member> - <member>ethernet1/1.1</member> - <member>ethernet1/1.2</member> - </interface> - </network> - </import> - </entry> - </vsys> - </entry> - </devices> -</config> diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf deleted file mode 100644 index 475bf0ae..00000000 --- a/examples/gwlb_with_vmseries/variables.tf +++ /dev/null @@ -1,357 +0,0 @@ -# Common -variable "name_prefix" { - description = "Prefix for resource names." - default = "" - type = string -} - -variable "location" { - description = "Location where the resources will be deployed." - 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 to create or use." - type = string -} - -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - -variable "tags" { - description = "Map of tags to assign to all of the created resources." - default = {} - type = map(string) -} - -# VNets -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 `true`) 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`, optional, 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 - resource_group_name = optional(string) - create_virtual_network = optional(bool, true) - address_space = optional(list(string)) - network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) - 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 - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_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) - })), {}) - })) -} - -# GWLB -variable "gateway_load_balancers" { - description = <<-EOF - Map with Gateway Load Balancer definitions. Following settings are supported: - - `name` - (required|string) Gateway Load Balancer name. - - `vnet_key` - (required|string) Key of a VNet from `var.vnets` that contains target Subnet for LB's frontned. Used to get Subnet ID in combination with `subnet_key` below. - - `subnet_key` - (required|string) Key of a Subnet from `var.vnets[vnet_key]`. - - `frontend_ip_config` - (optional|map) Remaining Frontned IP configuration. - - `resource_group_name` - (optional|string) LB's Resource Group, by default the one specified by `var.resource_group_name`. - - `backends` - (optional|map) LB's backend configurations. - - `heatlh_probe` - (optional|map) Health probe configuration. - - Please consult [module documentation](../../modules/gwlb/README.md) for details. - EOF - default = {} - type = any -} - -# VM-Series -variable "ngfw_metrics" { - description = <<-EOF - A map defining metrics related resources for Next Generation Firewall. - - All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - - > [!Note] - > We do not explicitly define Application Insights instances. Each Virtual Machine will receive one automatically - > as long as this object is not `null`. - > The name of the Application Insights instance will be derived from the VM's name and suffixed with `-ai`. - - Following properties are available: - - - `name` - (`string`, required) name of the (common) Log Analytics Workspace - - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace - - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. - EOF - default = null - type = object({ - name = string - create_workspace = optional(bool, true) - resource_group_name = optional(string) - sku = optional(string) - metrics_retention_in_days = optional(number) - }) -} - -variable "bootstrap_storages" { - description = <<-EOF - A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. This variable defines only Storage Accounts, file shares are defined per each VM. See `vmseries` variable, `bootstrap_storage` property. - Following properties are supported: - - `name` - (required|string) Name of the Storage Account. Please keep in mind that storage account name has to be globally unique. This name will not be prefixed with the value of `var.name_prefix`. - - `create_storage_account` - (optional|bool) Whether to create or source an existing Storage Account, defaults to `true`. - - `resource_group_name` - (optional|string) Name of the Resource Group hosting the Storage Account, defaults to `var.resource_group_name`. - - `storage_acl` - (optional|bool) Allows to enable network ACLs on the Storage Account. If set to `true`, `storage_allow_vnet_subnets` and `storage_allow_inbound_public_ips` options become available. Defaults to `false`. - - `storage_allow_vnet_subnets` - (optional|map) Map with objects that contains `vnet_key`/`subnet_key` used to identify subnets allowed to access the Storage Account. Note that `enable_storage_service_endpoint` has to be set to `true` in the corresponding subnet configuration. - - `storage_allow_inbound_public_ips` - (optional|list) Whitelist that contains public IPs/ranges allowed to access the Storage Account. Note that the code automatically to queries https://ifcondif.me to obtain the public IP address of the machine executing the code to enable bootstrap files upload. - EOF - default = {} - type = any -} - -variable "vmseries_common" { - description = <<-EOF - Configuration common for all firewall instances. Following settings can be specified: - - `username` - (required|string) - - `password` - (optional|string) - - `ssh_keys` - (optional|string) - - `img_version` - (optional|string) - - `img_sku` - (optional|string) - - `vm_size` - (optional|string) - - `bootstrap_options` - (optional|string) - - `vnet_key` - (optional|string) - - `interfaces` - (optional|list(object)) - - `ai_update_interval` - (optional|number) - - All are used directly as inputs for `vmseries` module (please see [documentation](../../modules/vmseries/README.md) for details), except for the last three: - - `vnet_key` - (required|string) Used to identify VNet in which subnets for interfaces exist. - - `ai_update_interval` - (optional|number) If Application Insights are used this property can override the default metrics update interval (in minutes). - EOF - type = any -} - -variable "vmseries" { - description = <<-EOF - Map with VM-Series instance specific configuration. Following properties are supported: - - `name` - (required|string) Instance name. - - `avzone` - (optional|string) AZ to deploy instance in, defaults to "1". - - `availability_set_key` - (optional|string) Key from `var.availability_sets`, used to determine Availabbility Set ID. - - `bootstrap_storage` - (optional|map) Map that contains bootstrap package contents definition, when present triggers creation of a File Share in an existing Storage Account. Following properties supported: - - `key` - (required|string) Identifies Storage Account to use from `var.bootstrap_storages`. - - `static_files` - (optional|map) Map where keys are local file paths, values determine destination in the bootstrap package (file share) where the file will be copied. - - `template_bootstrap_xml` - (optional|string) Path to the `bootstrap.xml` template. When defined it will trigger creation of the `bootstrap.xml` file and it's upload to the boostrap package. This is a simple `day 0` configuration file that should set up only basic networking. Specifying this property forces additional properties that are required to properly template the file. They can be defined per each VM or globally for all VMs (in `var.vmseries_common`). The properties are listed below. - - `interfaces` - List of objects with interface definitions. Utilizes all properties of `interfaces` input (see [documantation](../../modules/vmseries/README.md#inputs)), expect for `subnet_id` and `lb_backend_pool_id`, which are determined based on the following new items: - - `subnet_key` - (optional|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with. - - `gwlb_key` - (optional|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`. - - `gwlb_backend_key` - (optional|string) Key that identifies a backend from the GWLB selected by `gwlb_key` to associate th interface with, required when `enable_backend_pool` is `true`. - - Additionally, it's possible to override following settings from `var.vmseries_common`: - - `bootstrap_options` - When defined, it not only takes precedence over `var.vmseries_common.bootstrap_options`, but also over `bootstrap_storage` described below. - - `img_version` - - `img_sku` - - `vm_size` - - `ai_update_interval` - EOF - type = map(any) -} - -variable "availability_sets" { - description = <<-EOF - A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. - - Following properties are supported: - - `name` - (required|string) Name of the Application Insights. - - `update_domain_count` - (optional|int) Specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - (optional|int) Specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. - EOF - default = {} - type = any -} - -# Application -variable "load_balancers" { - description = <<-EOF - 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_name = 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" { - description = <<-EOF - Common settings for sample applications: - - `username` - (required|string) - - `password` - (optional|string) - - `ssh_keys` - (optional|list(string) - - `vm_size` - (optional|string) - - `disk_type` - (optional|string) - - `accelerated_networking` - (optional|bool) - - At least one of `password` or `ssh_keys` has to be provided. - EOF - type = any -} - -variable "appvms" { - description = <<-EOF - Configuration for sample application VMs. Available settings: - - `name` - (required|string) Instance name. - - `avzone` - (optional|string) AZ to deploy instance in, defaults to "1". - - `vnet_key` - (required|string) Used to identify VNet in which subnets for interfaces exist. - - `subnet_key` - (required|string) Key of a subnet from `var.vnets[vnet_key]` to associate interface with. - - `load_balancer_key` - (optional|string) Key from `var.gwlbs` that identifies GWLB that will be associated with the interface, required when `enable_backend_pool` is `true`. - EOF - default = {} - type = any -} diff --git a/examples/gwlb_with_vmseries/versions.tf b/examples/gwlb_with_vmseries/versions.tf deleted file mode 100644 index 7f8fef9c..00000000 --- a/examples/gwlb_with_vmseries/versions.tf +++ /dev/null @@ -1,25 +0,0 @@ -terraform { - required_version = ">= 1.0.0, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - http = { - source = "hashicorp/http" - } - local = { - source = "hashicorp/local" - } - random = { - source = "hashicorp/random" - } - } -} - -provider "azurerm" { - features { - resource_group { - prevent_deletion_if_contains_resources = false - } - } -} diff --git a/modules/gwlb/.header.md b/modules/gwlb/.header.md new file mode 100644 index 00000000..2e763934 --- /dev/null +++ b/modules/gwlb/.header.md @@ -0,0 +1,81 @@ +# Gateway Load Balancer Module for Azure + +A Terraform module for deploying a Gateway Load Balancer for VM-Series firewalls. + +## Usage + +In order to use GWLB, below minimal definition of Gateway Load Balancer can be used, for which: + +- only name, VNet and subnet are defined +- default frontend IP configuration is used (Dynamic IPv4) +- zones 1, 2, 3 are configured (GWLB is zone redundant) +- default load balancing rule is used (with default load distribution) +- default health probe is used (protocol TCP on port 80) +- default 1 backend is configured (with 2 tunnel interfaces on ports 2000, 2001) + +```hcl + gwlb = { + name = "vmseries-gwlb" + + frontend_ip = { + vnet_key = "security" + subnet_key = "data" + } + } +``` + +For more customized requirements, below extended definition of GWLB can be applied, for which: + +- frontend IP has custom name and static private IP address +- there are no zones defined +- custom name for load balancing rule is defined +- custom name and port for health probe is configured +- 2 backends are defined (external and internal) + +```hcl + gwlb2 = { + name = "vmseries-gwlb2" + zones = [] + + frontend_ip = { + name = "custom-name-frontend-ip" + vnet_key = "security" + subnet_key = "data" + private_ip_address = "10.0.1.24" + } + + lb_rule = { + name = "custom-name-lb-rule" + } + + health_probe = { + name = "custom-name-health-probe" + port = 80 + } + + backends = { + ext = { + name = "external" + tunnel_interfaces = { + external = { + identifier = 801 + port = 2001 + protocol = "VXLAN" + type = "External" + } + } + } + int = { + name = "internal" + tunnel_interfaces = { + internal = { + identifier = 800 + port = 2000 + protocol = "VXLAN" + type = "Internal" + } + } + } + } + } +``` diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index eccaa41b..a51fb937 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -1,55 +1,328 @@ +<!-- BEGIN_TF_DOCS --> # Gateway Load Balancer Module for Azure A Terraform module for deploying a Gateway Load Balancer for VM-Series firewalls. ## Usage -For usage see any of the reference architecture examples. +In order to use GWLB, below minimal definition of Gateway Load Balancer can be used, for which: -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements +- only name, VNet and subnet are defined +- default frontend IP configuration is used (Dynamic IPv4) +- zones 1, 2, 3 are configured (GWLB is zone redundant) +- default load balancing rule is used (with default load distribution) +- default health probe is used (protocol TCP on port 80) +- default 1 backend is configured (with 2 tunnel interfaces on ports 2000, 2001) -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0, < 2.0 | -| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~> 3.25 | +```hcl + gwlb = { + name = "vmseries-gwlb" -### Providers + frontend_ip = { + vnet_key = "security" + subnet_key = "data" + } + } +``` -| Name | Version | -|------|---------| -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~> 3.25 | +For more customized requirements, below extended definition of GWLB can be applied, for which: -### Modules +- frontend IP has custom name and static private IP address +- there are no zones defined +- custom name for load balancing rule is defined +- custom name and port for health probe is configured +- 2 backends are defined (external and internal) -No modules. +```hcl + gwlb2 = { + name = "vmseries-gwlb2" + zones = [] -### Resources + frontend_ip = { + name = "custom-name-frontend-ip" + vnet_key = "security" + subnet_key = "data" + private_ip_address = "10.0.1.24" + } -| Name | Type | -|------|------| -| [azurerm_lb.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb) | resource | -| [azurerm_lb_backend_address_pool.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool) | resource | -| [azurerm_lb_probe.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe) | resource | -| [azurerm_lb_rule.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule) | resource | + lb_rule = { + name = "custom-name-lb-rule" + } -### Inputs + health_probe = { + name = "custom-name-health-probe" + port = 80 + } -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_name"></a> [name](#input\_name) | The name of the gateway load balancer. | `string` | n/a | yes | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of a pre-existing resource group to place resources in. | `string` | n/a | yes | -| <a name="input_location"></a> [location](#input\_location) | Region to deploy load balancer and related resources in. | `string` | n/a | yes | -| <a name="input_frontend_ip_config"></a> [frontend\_ip\_config](#input\_frontend\_ip\_config) | Frontend IP configuration of the gateway load balancer. Following settings are available:<br>- `name` - (Optional\|string) Name of the frontend IP configuration. `var.name` by default.<br>- `private_ip_address_allocation` - (Optional\|string) The allocation method for the private IP address.<br>- `private_ip_address_version` - (Optional\|string) The IP version for the private IP address.<br>- `private_ip_address` - (Optional\|string) Private IP address to assign.<br>- `subnet_id` - (Required\|string) Id of a subnet to associate with the configuration.<br>- `zones` - (Optional\|list) List of AZs in which the IP address will be located in. | `any` | n/a | yes | -| <a name="input_health_probe"></a> [health\_probe](#input\_health\_probe) | Health probe configuration for the gateway load balancer backends. Following settings are available:<br>- `name` - (Optional\|string) Name of the health probe. Defaults to `name` variable value.<br>- `port` - (Required\|int)<br>- `protocol` - (Optional\|string)<br>- `probe_threshold` - (Optional\|int)<br>- `request_path` - (Optional\|string)<br>- `interval_in_seconds` - (Optional\|int)<br>- `number_of_probes` - (Optional\|int)<br><br>For details, please refer to [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe#argument-reference). | `map(any)` | n/a | yes | -| <a name="input_backends"></a> [backends](#input\_backends) | Map with backend configurations for the gateway load balancer. Azure GWLB rule can have up to two backends.<br>Following settings are available:<br>- `name` - (Optional\|string) Name of the backend. If not specified name is generated from `name` variable and backend key.<br>- `tunnel_interfaces` - (Required\|map) Map with tunnel interfaces specs.)<br><br>Each tunnel interface specification consists of following settings (refer to [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool#tunnel_interface) for details):<br>- `identifier` - (Required\|int) Interface identifier.<br>- `port` - (Required\|int) Interface port.<br>- `type` - (Required\|string) Either "External" or "Internal".<br><br>If one backend is specified, it has to have both external and internal tunnel interfaces specified.<br>For two backends, each has to have exactly one.<br><br>On GWLB inspection enabled VM-Series instance, `identifier` and `port` default to:<br>- `800`/`2000` for `Internal` tunnel type<br>- `801`/`2001` for `External` tunnel type<br>Variable default reflects this configuration on GWLB side. Additionally, for VM-Series tunnel interface protocol is always VXLAN. | `map(any)` | <pre>{<br> "ext-int": {<br> "tunnel_interfaces": {<br> "external": {<br> "identifier": 801,<br> "port": 2001,<br> "protocol": "VXLAN",<br> "type": "External"<br> },<br> "internal": {<br> "identifier": 800,<br> "port": 2000,<br> "protocol": "VXLAN",<br> "type": "Internal"<br> }<br> }<br> }<br>}</pre> | no | -| <a name="input_lb_rule"></a> [lb\_rule](#input\_lb\_rule) | Load balancing rule config. Available options:<br>- `name` - (Optional\|string) Name for the rule. Defaults to `var.frontend_ip_config.name`.<br>- `load_distribution` - (Optional\|string) Refer to [provider docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule#load_distribution). | `map(string)` | `null` | no | -| <a name="input_tags"></a> [tags](#input\_tags) | Azure tags to apply to the created resources. | `map(string)` | `{}` | no | + backends = { + ext = { + name = "external" + tunnel_interfaces = { + external = { + identifier = 801 + port = 2001 + protocol = "VXLAN" + type = "External" + } + } + } + int = { + name = "internal" + tunnel_interfaces = { + internal = { + identifier = 800 + port = 2000 + protocol = "VXLAN" + type = "Internal" + } + } + } + } + } +``` -### Outputs +## Module's Required Inputs -| Name | Description | -|------|-------------| -| <a name="output_backend_pool_ids"></a> [backend\_pool\_ids](#output\_backend\_pool\_ids) | Backend pools' identifiers. | -| <a name="output_frontend_ip_config_id"></a> [frontend\_ip\_config\_id](#output\_frontend\_ip\_config\_id) | Frontend IP configuration identifier. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +Name | Type | Description +--- | --- | --- +[`name`](#name) | `string` | The name of the Azure Load Balancer. +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`frontend_ip`](#frontend_ip) | `object` | Frontend IP configuration of the Gateway Load Balancer. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`zones`](#zones) | `list` | Controls zones for Gateway Load Balancer's Fronted IP configurations. +[`health_probe`](#health_probe) | `object` | Health probe configuration for the Gateway Load Balancer backends. +[`backends`](#backends) | `map` | Map with backend configurations for the Gateway Load Balancer. +[`lb_rule`](#lb_rule) | `object` | Load balancing rule configuration. + + + +## Module's Outputs + +Name | Description +--- | --- +`backend_pool_ids` | Backend pools' identifiers. +`frontend_ip_config_id` | Frontend IP configuration identifier. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.80 + + + + +Resources used in this module: + +- `lb` (managed) +- `lb_backend_address_pool` (managed) +- `lb_probe` (managed) +- `lb_rule` (managed) + +## Inputs/Outpus details + +### Required Inputs + + +#### name + +The name of the Azure Load Balancer. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### location + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### frontend_ip + +Frontend IP configuration of the Gateway Load Balancer. + +Following settings are available: +- `name` - (`string`, required) name of the frontend IP configuration. `var.name` by default. +- `subnet_id` - (`string`, required) id of a subnet to associate with the configuration. +- `private_ip_address` - (`string`, optional) private IP address to assign. +- `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. + Can be one of "IPv4", "IPv6". + + +Type: + +```hcl +object({ + name = string + subnet_id = string + private_ip_address = optional(string) + private_ip_address_version = optional(string, "IPv4") + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + +### Optional Inputs + + + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### zones + +Controls zones for Gateway Load Balancer's Fronted IP configurations. + +Setting this variable to explicit `null` disables a zonal deployment. +This can be helpful in regions where Availability Zones are not available. + + +Type: list(string) + +Default value: `[1 2 3]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### health_probe + +Health probe configuration for the Gateway Load Balancer backends. + +Following settings are available: +- `name` - (`string`, required) name of the health probe. +- `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https". +- `port` - (`number`, optional) port to run the probe against. +- `probe_threshold` - (`number`, optional) number of consecutive probes that decide on forwarding traffic to an endpoint. +- `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5 +- `request_path` - (`string`, optional) used only for non `Tcp` probes, + the URI used to check the endpoint status when `protocol` is set to `Http(s)`. + + +Type: + +```hcl +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:health_probe port:80 protocol:Tcp]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### backends + +Map with backend configurations for the Gateway Load Balancer. Azure GWLB rule can have up to two backends. + +Following settings are available: +- `name` - (`string`, required) name of the backend. +- `tunnel_interfaces` - (`map`, required) map with tunnel interfaces. + - `identifier` - (`number`, required) interface identifier. + - `port` - (`number`, required) interface port. + - `type` - (`string`, required) either "External" or "Internal". + +**Note!** \ +If one backend is specified, it has to have both external and internal tunnel interfaces specified. +For two backends, each has to have exactly one. + +On GWLB inspection enabled VM-Series instance, `identifier` and `port` default to: +- `800`/`2000` for `Internal` tunnel type +- `801`/`2001` for `External` tunnel type + +Variable default reflects this configuration on GWLB side. +Additionally, for VM-Series tunnel interface protocol is always VXLAN. + + +Type: + +```hcl +map(object({ + name = string + tunnel_interfaces = map(object({ + identifier = number + port = number + protocol = optional(string, "VXLAN") + type = string + })) + })) +``` + + +Default value: `map[backend:map[name:backend tunnel_interfaces:map[external:map[identifier:801 port:2001 protocol:VXLAN type:External] internal:map[identifier:800 port:2000 protocol:VXLAN type:Internal]]]]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### lb_rule + +Load balancing rule configuration. + +Available options: +- `name` - (`string`, optional) name for the rule. +- `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type + to be used by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". + + +Type: + +```hcl +object({ + name = string + load_distribution = optional(string, "Default") + }) +``` + + +Default value: `map[name:lb_rule]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/gwlb/main.tf b/modules/gwlb/main.tf index 40249594..d84df3ba 100644 --- a/modules/gwlb/main.tf +++ b/modules/gwlb/main.tf @@ -1,25 +1,26 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb resource "azurerm_lb" "this" { name = var.name resource_group_name = var.resource_group_name location = var.location sku = "Gateway" + tags = var.tags frontend_ip_configuration { - name = try(var.frontend_ip_config.name, var.name) - private_ip_address_allocation = try(var.frontend_ip_config.private_ip_address_allocation, null) - private_ip_address_version = try(var.frontend_ip_config.private_ip_address_version, null) - private_ip_address = try(var.frontend_ip_config.private_ip_address, null) - subnet_id = var.frontend_ip_config.subnet_id - zones = try(var.frontend_ip_config.zones, null) + name = var.frontend_ip.name + private_ip_address_allocation = var.frontend_ip.private_ip_address != null ? "Static" : "Dynamic" + private_ip_address_version = var.frontend_ip.private_ip_address_version + private_ip_address = var.frontend_ip.private_ip_address + subnet_id = var.frontend_ip.subnet_id + zones = var.frontend_ip.subnet_id != null ? var.zones : null } - - tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool resource "azurerm_lb_backend_address_pool" "this" { for_each = var.backends - name = try(each.value.name, "${var.name}-${each.key}") + name = each.value.name loadbalancer_id = azurerm_lb.this.id dynamic "tunnel_interface" { @@ -27,35 +28,36 @@ resource "azurerm_lb_backend_address_pool" "this" { content { identifier = tunnel_interface.value.identifier port = tunnel_interface.value.port - protocol = "VXLAN" + protocol = tunnel_interface.value.protocol type = tunnel_interface.value.type } } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe resource "azurerm_lb_probe" "this" { - name = try(var.health_probe.name, var.name) + name = var.health_probe.name loadbalancer_id = azurerm_lb.this.id - port = try(var.health_probe.port, null) - protocol = try(var.health_probe.protocol, null) - probe_threshold = try(var.health_probe.probe_threshold, null) - request_path = try(var.health_probe.request_path, null) - interval_in_seconds = try(var.health_probe.interval_in_seconds, null) - number_of_probes = try(var.health_probe.number_of_probes, null) + port = var.health_probe.port + protocol = var.health_probe.protocol + probe_threshold = var.health_probe.probe_threshold + request_path = var.health_probe.protocol != "Tcp" ? var.health_probe.request_path : null + interval_in_seconds = var.health_probe.interval_in_seconds } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule resource "azurerm_lb_rule" "this" { - name = try(var.lb_rule.name, azurerm_lb.this.frontend_ip_configuration[0].name) + name = var.lb_rule.name loadbalancer_id = azurerm_lb.this.id probe_id = azurerm_lb_probe.this.id frontend_ip_configuration_name = azurerm_lb.this.frontend_ip_configuration[0].name backend_address_pool_ids = [for _, v in azurerm_lb_backend_address_pool.this : v.id] - load_distribution = try(var.lb_rule.load_distribution, null) + load_distribution = var.lb_rule.load_distribution # HA port rule - required by Azure GWLB protocol = "All" backend_port = 0 frontend_port = 0 -} \ No newline at end of file +} diff --git a/modules/gwlb/main_test.go b/modules/gwlb/main_test.go index e598c2f2..036fe1d6 100644 --- a/modules/gwlb/main_test.go +++ b/modules/gwlb/main_test.go @@ -1,4 +1,4 @@ -package bootstrap +package gwlb import ( "testing" diff --git a/modules/gwlb/variables.tf b/modules/gwlb/variables.tf index 1a69d9bc..e6e31807 100644 --- a/modules/gwlb/variables.tf +++ b/modules/gwlb/variables.tf @@ -1,69 +1,145 @@ variable "name" { - description = "The name of the gateway load balancer." + description = "The name of the Azure Load Balancer." type = string } variable "resource_group_name" { - description = "Name of a pre-existing resource group to place resources in." + description = "The name of the Resource Group to use." type = string } variable "location" { - description = "Region to deploy load balancer and related resources in." + description = "The name of the Azure region to deploy the resources in." type = string } -variable "frontend_ip_config" { +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} + +variable "zones" { + description = <<-EOF + Controls zones for Gateway Load Balancer's Fronted IP configurations. + + Setting this variable to explicit `null` disables a zonal deployment. + This can be helpful in regions where Availability Zones are not available. + EOF + default = ["1", "2", "3"] + nullable = false + type = list(string) +} + +variable "frontend_ip" { description = <<-EOF - Frontend IP configuration of the gateway load balancer. Following settings are available: - - `name` - (Optional|string) Name of the frontend IP configuration. `var.name` by default. - - `private_ip_address_allocation` - (Optional|string) The allocation method for the private IP address. - - `private_ip_address_version` - (Optional|string) The IP version for the private IP address. - - `private_ip_address` - (Optional|string) Private IP address to assign. - - `subnet_id` - (Required|string) Id of a subnet to associate with the configuration. - - `zones` - (Optional|list) List of AZs in which the IP address will be located in. + Frontend IP configuration of the Gateway Load Balancer. + + Following settings are available: + - `name` - (`string`, required) name of the frontend IP configuration. `var.name` by default. + - `subnet_id` - (`string`, required) id of a subnet to associate with the configuration. + - `private_ip_address` - (`string`, optional) private IP address to assign. + - `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. + Can be one of "IPv4", "IPv6". EOF - type = any + nullable = false + type = object({ + name = string + subnet_id = string + private_ip_address = optional(string) + private_ip_address_version = optional(string, "IPv4") + }) + validation { # private_ip_address + condition = (var.frontend_ip.private_ip_address != null ? + can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", var.frontend_ip.private_ip_address)) : true) + error_message = "The `private_ip_address` property should be in IPv4 format." + } + validation { # private_ip_address_version + condition = contains(["IPv4", "IPv6"], var.frontend_ip.private_ip_address_version) + error_message = "The `private_ip_address_version` property can be one of \"IPv4\", \"IPv6\"." + } } variable "health_probe" { description = <<-EOF - Health probe configuration for the gateway load balancer backends. Following settings are available: - - `name` - (Optional|string) Name of the health probe. Defaults to `name` variable value. - - `port` - (Required|int) - - `protocol` - (Optional|string) - - `probe_threshold` - (Optional|int) - - `request_path` - (Optional|string) - - `interval_in_seconds` - (Optional|int) - - `number_of_probes` - (Optional|int) - - For details, please refer to [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe#argument-reference). + Health probe configuration for the Gateway Load Balancer backends. + + Following settings are available: + - `name` - (`string`, required) name of the health probe. + - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https". + - `port` - (`number`, optional) port to run the probe against. + - `probe_threshold` - (`number`, optional) number of consecutive probes that decide on forwarding traffic to an endpoint. + - `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5 + - `request_path` - (`string`, optional) used only for non `Tcp` probes, + the URI used to check the endpoint status when `protocol` is set to `Http(s)`. EOF - type = map(any) + default = { + name = "health_probe" + port = 80 + protocol = "Tcp" + } + nullable = false + type = object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + }) + validation { # port + condition = var.health_probe.protocol == "Tcp" ? var.health_probe.port != null : true + error_message = "The `port` property is required when protocol is set to \"Tcp\"." + } + validation { # port + condition = var.health_probe.port != null ? var.health_probe.port >= 1 && var.health_probe.port <= 65535 : true + error_message = "The `port` property has to be a valid TCP port." + } + validation { # protocol + condition = contains(["Tcp", "Http", "Https"], var.health_probe.protocol) + error_message = "The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\"." + } + validation { # interval_in_seconds + condition = (var.health_probe.interval_in_seconds != null ? + var.health_probe.interval_in_seconds >= 5 && var.health_probe.interval_in_seconds <= 3600 : true) + error_message = "The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour)." + } + validation { # probe_threshold + condition = (var.health_probe.probe_threshold != null ? + var.health_probe.probe_threshold >= 1 && var.health_probe.probe_threshold <= 100 : true) + error_message = "The `probe_threshold` property has to be between 1 and 100." + } + validation { # request_path + condition = var.health_probe.protocol != "Tcp" ? var.health_probe.request_path != null : true + error_message = "The `request_path` property is required when protocol is set to \"Http\" or \"Https\"." + } } variable "backends" { description = <<-EOF - Map with backend configurations for the gateway load balancer. Azure GWLB rule can have up to two backends. + Map with backend configurations for the Gateway Load Balancer. Azure GWLB rule can have up to two backends. + Following settings are available: - - `name` - (Optional|string) Name of the backend. If not specified name is generated from `name` variable and backend key. - - `tunnel_interfaces` - (Required|map) Map with tunnel interfaces specs.) - - Each tunnel interface specification consists of following settings (refer to [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool#tunnel_interface) for details): - - `identifier` - (Required|int) Interface identifier. - - `port` - (Required|int) Interface port. - - `type` - (Required|string) Either "External" or "Internal". - + - `name` - (`string`, required) name of the backend. + - `tunnel_interfaces` - (`map`, required) map with tunnel interfaces. + - `identifier` - (`number`, required) interface identifier. + - `port` - (`number`, required) interface port. + - `type` - (`string`, required) either "External" or "Internal". + + **Note!** \ If one backend is specified, it has to have both external and internal tunnel interfaces specified. For two backends, each has to have exactly one. On GWLB inspection enabled VM-Series instance, `identifier` and `port` default to: - `800`/`2000` for `Internal` tunnel type - `801`/`2001` for `External` tunnel type - Variable default reflects this configuration on GWLB side. Additionally, for VM-Series tunnel interface protocol is always VXLAN. + + Variable default reflects this configuration on GWLB side. + Additionally, for VM-Series tunnel interface protocol is always VXLAN. EOF default = { - ext-int = { + backend = { + name = "backend" tunnel_interfaces = { internal = { identifier = 800 @@ -80,21 +156,53 @@ variable "backends" { } } } - type = map(any) + nullable = false + type = map(object({ + name = string + tunnel_interfaces = map(object({ + identifier = number + port = number + protocol = optional(string, "VXLAN") + type = string + })) + })) + validation { # protocol + condition = (var.backends == null ? + true : alltrue(flatten([for k, v in var.backends : + [for p, r in v.tunnel_interfaces : contains(["VXLAN"], r.protocol)]]))) + error_message = "The `protocol` property can be only \"VXLAN\"." + } + validation { # type + condition = (var.backends == null ? + true : alltrue(flatten([for k, v in var.backends : + [for p, r in v.tunnel_interfaces : contains(["Internal", "External"], r.type)]]))) + error_message = "The `type` property can be one of \"Internal\", \"External\"." + } + validation { # backends + condition = (var.backends == null ? true : length(var.backends) <= 2) + error_message = "Maximum allowed number of `backends` is 2." + } } variable "lb_rule" { description = <<-EOF - Load balancing rule config. Available options: - - `name` - (Optional|string) Name for the rule. Defaults to `var.frontend_ip_config.name`. - - `load_distribution` - (Optional|string) Refer to [provider docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule#load_distribution). - EOF - default = null - type = map(string) -} + Load balancing rule configuration. -variable "tags" { - description = "Azure tags to apply to the created resources." - default = {} - type = map(string) + Available options: + - `name` - (`string`, optional) name for the rule. + - `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type + to be used by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". + EOF + default = { + name = "lb_rule" + } + nullable = false + type = object({ + name = string + load_distribution = optional(string, "Default") + }) + validation { # load_distribution + condition = contains(["Default", "SourceIP", "SourceIPProtocol"], var.lb_rule.load_distribution) + error_message = "The `load_distribution` property can be one of \"Default\", \"SourceIP\", \"SourceIPProtocol\"." + } } diff --git a/modules/gwlb/versions.tf b/modules/gwlb/versions.tf index e367f5b8..9abec711 100644 --- a/modules/gwlb/versions.tf +++ b/modules/gwlb/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.0, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } From 19d7e939f7de0bbc8038ecabaead9382807dec8a Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 29 Jan 2024 10:31:13 +0100 Subject: [PATCH 19/49] refactor(example/gwlb_with_vmseries): Refactor Gateway Load Balancer example (#4) --- examples/gwlb_with_vmseries/.header.md | 78 ++ examples/gwlb_with_vmseries/README.md | 812 ++++++++++++++++++ examples/gwlb_with_vmseries/example.tfvars | 280 ++++++ .../files/init-cfg.sample.txt | 2 + examples/gwlb_with_vmseries/main.tf | 338 ++++++++ examples/gwlb_with_vmseries/main_test.go | 79 ++ examples/gwlb_with_vmseries/outputs.tf | 31 + .../templates/bootstrap-gwlb.tftpl | 520 +++++++++++ examples/gwlb_with_vmseries/variables.tf | 595 +++++++++++++ examples/gwlb_with_vmseries/versions.tf | 19 + 10 files changed, 2754 insertions(+) create mode 100644 examples/gwlb_with_vmseries/.header.md create mode 100644 examples/gwlb_with_vmseries/README.md create mode 100644 examples/gwlb_with_vmseries/example.tfvars create mode 100644 examples/gwlb_with_vmseries/files/init-cfg.sample.txt create mode 100644 examples/gwlb_with_vmseries/main.tf create mode 100644 examples/gwlb_with_vmseries/main_test.go create mode 100644 examples/gwlb_with_vmseries/outputs.tf create mode 100644 examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl create mode 100644 examples/gwlb_with_vmseries/variables.tf create mode 100644 examples/gwlb_with_vmseries/versions.tf diff --git a/examples/gwlb_with_vmseries/.header.md b/examples/gwlb_with_vmseries/.header.md new file mode 100644 index 00000000..c69ec1d9 --- /dev/null +++ b/examples/gwlb_with_vmseries/.header.md @@ -0,0 +1,78 @@ +# VM-Series Azure Gateway Load Balancer example + +The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing +Azure Gateway Load Balancer in service chain model as described in the following +[document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). + +## Usage + +### Deployment Steps + +* Checkout the code locally. +* Copy `example.tfvars` to `terraform.tfvars` and adjust it to your needs. +* Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this +[documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) +for details). +* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* Initialize the Terraform module: + +```bash +terraform init +``` + +* (optional) Plan you infrastructure to see what will be actually deployed: + +```bash +terraform plan +``` + +* Deploy the infrastructure: + +```bash +terraform apply +``` + +* At this stage you have to wait a few minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +* for username: + +```bash +terraform output username +``` + +* for password: + +```bash +terraform output password +``` + +The management public IP addresses are available in the `vmseries_mgmt_ips` output: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +* CLI - ssh client is required +* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +With default example configuration, the devices already contain `DAY0` configuration, so all network interfaces should be +configured and Azure Gateway Load Balancer should already report that the devices are healthy. + +You can now proceed with licensing the devices and configuring your first rules. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for +`DAY1` configuration (security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md new file mode 100644 index 00000000..c8fd8993 --- /dev/null +++ b/examples/gwlb_with_vmseries/README.md @@ -0,0 +1,812 @@ +<!-- BEGIN_TF_DOCS --> +# VM-Series Azure Gateway Load Balancer example + +The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing +Azure Gateway Load Balancer in service chain model as described in the following +[document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). + +## Usage + +### Deployment Steps + +* Checkout the code locally. +* Copy `example.tfvars` to `terraform.tfvars` and adjust it to your needs. +* Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this +[documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) +for details). +* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* Initialize the Terraform module: + +```bash +terraform init +``` + +* (optional) Plan you infrastructure to see what will be actually deployed: + +```bash +terraform plan +``` + +* Deploy the infrastructure: + +```bash +terraform apply +``` + +* At this stage you have to wait a few minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +* for username: + +```bash +terraform output username +``` + +* for password: + +```bash +terraform output password +``` + +The management public IP addresses are available in the `vmseries_mgmt_ips` output: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +* CLI - ssh client is required +* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +With default example configuration, the devices already contain `DAY0` configuration, so all network interfaces should be +configured and Azure Gateway Load Balancer should already report that the devices are healthy. + +You can now proceed with licensing the devices and configuring your first rules. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for +`DAY1` configuration (security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` + +## 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 Balancers. +[`gateway_load_balancers`](#gateway_load_balancers) | `map` | Map with Gateway Load Balancer definitions. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. +[`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`appvms`](#appvms) | `any` | Configuration for sample application VMs. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial administrative username to use for VM-Series. +`passwords` | Initial administrative password to use for VM-Series. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. +`vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. +`bootstrap_storage_urls` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` +- `local` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`gwlb` | - | ../../modules/gwlb | create Gateway Load Balancers +`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`appvm` | - | ../../modules/virtual_machine | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### load_balancers + +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, 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 + 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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### gateway_load_balancers + +Map with Gateway Load Balancer definitions. + +Following settings are available: +- `name` - (`string`, required) name of the Gatewa Load Balancer Gateway. +- `frontend_ip` - (`object`, required) frontend IP configuration + (refer to [module documentation](../../modules/gwlb/README.md) for details) +- `health_probe - (`object`, optional) health probe settings + (refer to [module documentation](../../modules/gwlb/README.md) for details) +- `backends` - (`map`, optional) map of backends + (refer to [module documentation](../../modules/gwlb/README.md) for details) +- `lb_rule` - (`object`, optional) load balancer rule + (refer to [module documentation](../../modules/gwlb/README.md) for details) + + +Type: + +```hcl +map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + frontend_ip = object({ + name = optional(string) + vnet_key = string + subnet_key = string + private_ip_address = optional(string) + private_ip_address_version = optional(string, "IPv4") + }) + health_probe = optional(object({ + name = optional(string) + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + })) + backends = optional(map(object({ + name = optional(string) + tunnel_interfaces = map(object({ + identifier = number + port = number + protocol = optional(string, "VXLAN") + type = string + })) + }))) + lb_rule = optional(object({ + name = optional(string) + load_distribution = optional(string, "Default") + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### availability_sets + +A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + +Following properties are supported: +- `name` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). +Please verify how many update and fault domain are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### bootstrap_storages + +A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + +You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to +[module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + +- `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. +- `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. +- `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vmseries + +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + +For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + +The most basic properties are as follows: + +- `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a data + Load Balancer health checks. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + + +Type: + +```hcl +map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + data_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### appvms + +Configuration for sample application VMs. + + +Type: any + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars new file mode 100644 index 00000000..a6f462ea --- /dev/null +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -0,0 +1,280 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "gwlb" +name_prefix = "example-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + +# --- VNET PART --- # +vnets = { + "transit" = { + name = "transit" + address_space = ["10.0.0.0/25"] + network_security_groups = { + "management" = { + name = "mgmt-nsg" + rules = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["0.0.0.0/0"] # 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"] + } + } + } + "data" = { + name = "data-nsg" + } + } + route_tables = { + "management" = { + name = "mgmt-rt" + routes = { + "data_blackhole" = { + name = "data-blackhole-udr" + address_prefix = "10.0.0.16/28" + next_hop_type = "None" + } + } + } + "data" = { + name = "data-rt" + routes = { + "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" + address_prefix = "10.0.0.0/28" + next_hop_type = "None" + } + } + } + } + subnets = { + "management" = { + name = "mgmt-snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group_key = "management" + route_table_key = "management" + enable_storage_service_endpoint = true + } + "data" = { + name = "data-snet" + address_prefixes = ["10.0.0.16/28"] + network_security_group_key = "data" + route_table_key = "data" + } + } + } + "app1" = { + name = "app1" + address_space = ["10.0.2.0/25"] + network_security_groups = { + "application_inbound" = { + name = "application-inbound-nsg" + rules = { + app_inbound = { + name = "application-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances + source_port_range = "*" + destination_address_prefix = "*" + destination_port_ranges = ["22", "80", "443"] + } + } + } + } + subnets = { + "app1" = { + name = "app1-snet" + address_prefixes = ["10.0.2.0/28"] + network_security_group_key = "application_inbound" + } + } + } +} + +# --- LOAD BALANCING PART --- # +load_balancers = { + "app1" = { + name = "app1-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "app1" + nsg_key = "application_inbound" + source_ips = ["0.0.0.0/0"] + } + frontend_ips = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" + create_public_ip = true + gwlb_key = "gwlb" + in_rules = { + "balanceHttp" = { + name = "HTTP" + protocol = "Tcp" + port = 80 + floating_ip = false + } + "balanceHttps" = { + name = "HTTPS" + protocol = "Tcp" + port = 443 + floating_ip = false + } + } + out_rules = { + outbound = { + name = "tcp-outbound" + protocol = "Tcp" + } + } + } + } + } +} + +# --- GWLB PART --- # +gateway_load_balancers = { + gwlb = { + name = "vmseries-gwlb" + + frontend_ip = { + vnet_key = "transit" + subnet_key = "data" + } + + health_probe = { + name = "custom-health-probe" + port = 80 + protocol = "Tcp" + } + + backends = { + backend = { + name = "custom-backend" + tunnel_interfaces = { + internal = { + identifier = 800 + port = 2000 + protocol = "VXLAN" + type = "Internal" + } + external = { + identifier = 801 + port = 2001 + protocol = "VXLAN" + type = "External" + } + } + } + } + + lb_rule = { + name = "custom-lb-rule" + } + } +} + +# --- VMSERIES PART --- # +bootstrap_storages = { + "bootstrap" = { + name = "examplegwlbbootstrap" + storage_network_security = { + vnet_key = "transit" + allowed_subnet_keys = ["management"] + allowed_public_ips = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances + } + } +} + +vmseries = { + "fw-1" = { + name = "firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap-gwlb.tftpl" + data_snet_key = "data" + } + } + interfaces = [ + { + name = "vm01-mgmt" + subnet_key = "management" + create_public_ip = true + }, + { + name = "vm01-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" + } + ] + } + "fw-2" = { + name = "firewall02" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap-gwlb.tftpl" + data_snet_key = "data" + } + } + interfaces = [ + { + name = "vm02-mgmt" + subnet_key = "management" + create_public_ip = true + }, + { + name = "vm02-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" + } + ] + } +} + +# --- APPLICATION VM PART --- # +appvms = { + app1vm01 = { + name = "app1-vm01" + avzone = "3" + vnet_key = "app1" + subnet_key = "app1" + load_balancer_key = "app1" + username = "appadmin" + custom_data = <<SCRIPT +#!/bin/sh +sudo apt-get update +sudo apt-get install -y nginx +sudo systemctl start nginx +sudo systemctl enable nginx +echo "Backend VM is $(hostname)" | sudo tee /var/www/html/index.html +SCRIPT + } +} \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/files/init-cfg.sample.txt b/examples/gwlb_with_vmseries/files/init-cfg.sample.txt new file mode 100644 index 00000000..c4373843 --- /dev/null +++ b/examples/gwlb_with_vmseries/files/init-cfg.sample.txt @@ -0,0 +1,2 @@ +type=dhcp-client +plugin-op-commands=azure-gwlb-inspect:enable \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf new file mode 100644 index 00000000..b6026240 --- /dev/null +++ b/examples/gwlb_with_vmseries/main.tf @@ -0,0 +1,338 @@ +# Generate a random password. +resource "random_password" "this" { + count = anytrue([ + for _, v in var.vmseries : v.authentication.password == null + ]) ? 1 : 0 + + length = 16 + min_lower = 16 - 4 + min_numeric = 1 + min_special = 1 + min_upper = 1 + override_special = "_%@" +} + +locals { + authentication = { + for k, v in var.vmseries : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } +} + +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} + +# create load balancers, both internal and external +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 + + 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 => 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 + depends_on = [module.vnet] +} + +# create Gateway Load Balancers +module "gwlb" { + for_each = var.gateway_load_balancers + source = "../../modules/gwlb" + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + location = var.location + + backends = try(each.value.backends, null) + health_probe = try(each.value.health_probe, null) + lb_rule = try(each.value.lb_rule, null) + + zones = try(each.value.zones, null) + frontend_ip = { + name = coalesce(each.value.frontend_ip.name, "${var.name_prefix}${each.value.name}") + private_ip_address_version = try(each.value.frontend_ip.private_ip_address_version, null) + private_ip_address = try(each.value.frontend_ip.private_ip_address, null) + subnet_id = module.vnet[each.value.frontend_ip.vnet_key].subnet_ids[each.value.frontend_ip.subnet_key] + } + + tags = var.tags +} + + +# create the actual VM-Series VMs and resources +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" + + count = var.ngfw_metrics != null ? 1 : 0 + + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + location = var.location + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } + + tags = var.tags +} + +resource "local_file" "bootstrap_xml" { + for_each = { + for k, v in var.vmseries : + k => v.virtual_machine + if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + } + + filename = "files/${each.key}-bootstrap.xml" + content = templatefile( + each.value.bootstrap_package.bootstrap_xml_template, + { + data_gateway_ip = cidrhost( + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.data_snet_key], + 1 + ) + + ai_instr_key = try( + module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], + null + ) + + ai_update_interval = each.value.bootstrap_package.ai_update_interval + } + ) + + depends_on = [ + module.ngfw_metrics, + module.vnet + ] +} + +locals { + bootstrap_file_shares_flat = flatten([ + for k, v in var.vmseries : + merge(v.virtual_machine.bootstrap_package, { vm_key = k }) + if v.virtual_machine.bootstrap_package != null + ]) + + bootstrap_file_shares = { for k, v in var.bootstrap_storages : k => { + for file_share in local.bootstrap_file_shares_flat : file_share.vm_key => { + name = file_share.vm_key + bootstrap_package_path = file_share.bootstrap_package_path + bootstrap_files = merge( + file_share.static_files, + file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = "config/bootstrap.xml" + } + ) + bootstrap_files_md5 = file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = local_file.bootstrap_xml[file_share.vm_key].content_md5 + } + } if file_share.bootstrap_storage_key == k } + } +} + +module "bootstrap" { + source = "../../modules/bootstrap" + + for_each = var.bootstrap_storages + + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + storage_network_security = merge( + each.value.storage_network_security, + each.value.storage_network_security.vnet_key == null ? {} : { + allowed_subnet_ids = [ + for v in each.value.storage_network_security.allowed_subnet_keys : + module.vnet[each.value.storage_network_security.vnet_key].subnet_ids[v] + ] } + ) + file_shares_configuration = each.value.file_shares_configuration + file_shares = local.bootstrap_file_shares[each.key] + + tags = var.tags +} + +resource "azurerm_availability_set" "this" { + for_each = var.availability_sets + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count + + tags = var.tags +} + +module "vmseries" { + source = "../../modules/vmseries" + + for_each = var.vmseries + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + bootstrap_options = try( + coalesce( + each.value.virtual_machine.bootstrap_options, + join(",", [ + "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + ), + null + ) + } + ) + + interfaces = [for v in each.value.interfaces : { + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address + attach_to_lb_backend_pool = v.load_balancer_key != null || v.gwlb_key != null + lb_backend_pool_id = try( + module.load_balancer[v.load_balancer_key].backend_pool_id, + try( + module.gwlb[v.gwlb_key].backend_pool_ids[v.gwlb_backend_key], + null + ) + ) + }] + + tags = var.tags + depends_on = [ + module.vnet, + azurerm_availability_set.this, + module.load_balancer, + module.bootstrap, + ] +} + +module "appvm" { + for_each = var.appvms + source = "../../modules/virtual_machine" + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + avzone = each.value.avzone + + interfaces = [ + { + name = "${var.name_prefix}${each.value.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + enable_backend_pool = true + lb_backend_pool_id = module.load_balancer[each.value.load_balancer_key].backend_pool_id + }, + ] + + username = try(each.value.username, null) + password = try(random_password.this[0].result, null) + ssh_keys = try(each.value.ssh_keys, []) + custom_data = try(each.value.custom_data, null) + + vm_size = try(each.value.vm_size, "Standard_B1ls") + managed_disk_type = try(each.value.disk_type, "Standard_LRS") + accelerated_networking = try(each.value.accelerated_networking, false) + + tags = var.tags +} \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main_test.go b/examples/gwlb_with_vmseries/main_test.go new file mode 100644 index 00000000..12be6e63 --- /dev/null +++ b/examples/gwlb_with_vmseries/main_test.go @@ -0,0 +1,79 @@ +package gwlb_with_vmseries + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +func CreateTerraformOptions(t *testing.T) *terraform.Options { + // prepare random prefix + randomNames, _ := testskeleton.GenerateTerraformVarsInfo("azure") + storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\" } }", randomNames.AzureStorageAccountName) + + // copy the init-cfg.sample.txt file to init-cfg.txt for test purposes + _, err := os.Stat("files/init-cfg.txt") + if err != nil { + buff, err := os.ReadFile("files/init-cfg.sample.txt") + if err != nil { + log.Fatal(err) + } + err = os.WriteFile("files/init-cfg.txt", buff, 0644) + if err != nil { + log.Fatal(err) + } + } + + // 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, + "bootstrap_storages": storageDefinition, + }, + 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/gwlb_with_vmseries/outputs.tf b/examples/gwlb_with_vmseries/outputs.tf new file mode 100644 index 00000000..368e85a2 --- /dev/null +++ b/examples/gwlb_with_vmseries/outputs.tf @@ -0,0 +1,31 @@ +output "usernames" { + description = "Initial administrative username to use for VM-Series." + value = { for k, v in local.authentication : k => v.username } +} + +output "passwords" { + description = "Initial administrative password to use for VM-Series." + value = { for k, v in local.authentication : k => v.password } + sensitive = true +} + +output "metrics_instrumentation_keys" { + description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) + sensitive = true +} + +output "lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null +} + +output "vmseries_mgmt_ips" { + description = "IP addresses for the VM-Series management interface." + value = { for k, v in module.vmseries : k => v.mgmt_ip_address } +} + +output "bootstrap_storage_urls" { + value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null + sensitive = true +} diff --git a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl new file mode 100644 index 00000000..ffaf7ed3 --- /dev/null +++ b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl @@ -0,0 +1,520 @@ +<?xml version="1.0"?> +<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> + <mgt-config> + <users> + <entry name="panadmin"> + <phash>*</phash> + <permissions> + <role-based> + <superuser>yes</superuser> + </role-based> + </permissions> + </entry> + </users> + <password-complexity> + <enabled>yes</enabled> + <minimum-length>8</minimum-length> + </password-complexity> + </mgt-config> + <shared> + <application/> + <application-group/> + <service/> + <service-group/> + <botnet> + <configuration> + <http> + <dynamic-dns> + <enabled>yes</enabled> + <threshold>5</threshold> + </dynamic-dns> + <malware-sites> + <enabled>yes</enabled> + <threshold>5</threshold> + </malware-sites> + <recent-domains> + <enabled>yes</enabled> + <threshold>5</threshold> + </recent-domains> + <ip-domains> + <enabled>yes</enabled> + <threshold>10</threshold> + </ip-domains> + <executables-from-unknown-sites> + <enabled>yes</enabled> + <threshold>5</threshold> + </executables-from-unknown-sites> + </http> + <other-applications> + <irc>yes</irc> + </other-applications> + <unknown-applications> + <unknown-tcp> + <destinations-per-hour>10</destinations-per-hour> + <sessions-per-hour>10</sessions-per-hour> + <session-length> + <maximum-bytes>100</maximum-bytes> + <minimum-bytes>50</minimum-bytes> + </session-length> + </unknown-tcp> + <unknown-udp> + <destinations-per-hour>10</destinations-per-hour> + <sessions-per-hour>10</sessions-per-hour> + <session-length> + <maximum-bytes>100</maximum-bytes> + <minimum-bytes>50</minimum-bytes> + </session-length> + </unknown-udp> + </unknown-applications> + </configuration> + <report> + <topn>100</topn> + <scheduled>yes</scheduled> + </report> + </botnet> + </shared> + <devices> + <entry name="localhost.localdomain"> + <network> + <interface> + <ethernet> + <entry name="ethernet1/1"> + <layer3> + <ndp-proxy> + <enabled>no</enabled> + </ndp-proxy> + <sdwan-link-settings> + <upstream-nat> + <enable>no</enable> + <static-ip/> + </upstream-nat> + <enable>no</enable> + </sdwan-link-settings> + <dhcp-client> + <create-default-route>no</create-default-route> + </dhcp-client> + <interface-management-profile>gwlb-healthcheck</interface-management-profile> + <lldp> + <enable>no</enable> + </lldp> + <units> + <entry name="ethernet1/1.1"> + <ipv6> + <neighbor-discovery> + <router-advertisement> + <enable>no</enable> + </router-advertisement> + </neighbor-discovery> + </ipv6> + <ndp-proxy> + <enabled>no</enabled> + </ndp-proxy> + <sdwan-link-settings> + <enable>no</enable> + </sdwan-link-settings> + <adjust-tcp-mss> + <enable>no</enable> + </adjust-tcp-mss> + <tag>1</tag> + </entry> + <entry name="ethernet1/1.2"> + <ipv6> + <neighbor-discovery> + <router-advertisement> + <enable>no</enable> + </router-advertisement> + </neighbor-discovery> + </ipv6> + <ndp-proxy> + <enabled>no</enabled> + </ndp-proxy> + <sdwan-link-settings> + <enable>no</enable> + </sdwan-link-settings> + <adjust-tcp-mss> + <enable>no</enable> + </adjust-tcp-mss> + <tag>2</tag> + </entry> + </units> + </layer3> + </entry> + </ethernet> + </interface> + <profiles> + <monitor-profile> + <entry name="default"> + <interval>3</interval> + <threshold>5</threshold> + <action>wait-recover</action> + </entry> + </monitor-profile> + <interface-management-profile> + <entry name="gwlb-healthcheck"> + <http>yes</http> + <permitted-ip> + <entry name="168.63.129.16"/> + </permitted-ip> + </entry> + </interface-management-profile> + </profiles> + <ike> + <crypto-profiles> + <ike-crypto-profiles> + <entry name="default"> + <encryption> + <member>aes-128-cbc</member> + <member>3des</member> + </encryption> + <hash> + <member>sha1</member> + </hash> + <dh-group> + <member>group2</member> + </dh-group> + <lifetime> + <hours>8</hours> + </lifetime> + </entry> + <entry name="Suite-B-GCM-128"> + <encryption> + <member>aes-128-cbc</member> + </encryption> + <hash> + <member>sha256</member> + </hash> + <dh-group> + <member>group19</member> + </dh-group> + <lifetime> + <hours>8</hours> + </lifetime> + </entry> + <entry name="Suite-B-GCM-256"> + <encryption> + <member>aes-256-cbc</member> + </encryption> + <hash> + <member>sha384</member> + </hash> + <dh-group> + <member>group20</member> + </dh-group> + <lifetime> + <hours>8</hours> + </lifetime> + </entry> + </ike-crypto-profiles> + <ipsec-crypto-profiles> + <entry name="default"> + <esp> + <encryption> + <member>aes-128-cbc</member> + <member>3des</member> + </encryption> + <authentication> + <member>sha1</member> + </authentication> + </esp> + <dh-group>group2</dh-group> + <lifetime> + <hours>1</hours> + </lifetime> + </entry> + <entry name="Suite-B-GCM-128"> + <esp> + <encryption> + <member>aes-128-gcm</member> + </encryption> + <authentication> + <member>none</member> + </authentication> + </esp> + <dh-group>group19</dh-group> + <lifetime> + <hours>1</hours> + </lifetime> + </entry> + <entry name="Suite-B-GCM-256"> + <esp> + <encryption> + <member>aes-256-gcm</member> + </encryption> + <authentication> + <member>none</member> + </authentication> + </esp> + <dh-group>group20</dh-group> + <lifetime> + <hours>1</hours> + </lifetime> + </entry> + </ipsec-crypto-profiles> + <global-protect-app-crypto-profiles> + <entry name="default"> + <encryption> + <member>aes-128-cbc</member> + </encryption> + <authentication> + <member>sha1</member> + </authentication> + </entry> + </global-protect-app-crypto-profiles> + </crypto-profiles> + </ike> + <qos> + <profile> + <entry name="default"> + <class-bandwidth-type> + <mbps> + <class> + <entry name="class1"> + <priority>real-time</priority> + </entry> + <entry name="class2"> + <priority>high</priority> + </entry> + <entry name="class3"> + <priority>high</priority> + </entry> + <entry name="class4"> + <priority>medium</priority> + </entry> + <entry name="class5"> + <priority>medium</priority> + </entry> + <entry name="class6"> + <priority>low</priority> + </entry> + <entry name="class7"> + <priority>low</priority> + </entry> + <entry name="class8"> + <priority>low</priority> + </entry> + </class> + </mbps> + </class-bandwidth-type> + </entry> + </profile> + </qos> + <virtual-router> + <entry name="default"> + <protocol> + <bgp> + <enable>no</enable> + <dampening-profile> + <entry name="default"> + <cutoff>1.25</cutoff> + <reuse>0.5</reuse> + <max-hold-time>900</max-hold-time> + <decay-half-life-reachable>300</decay-half-life-reachable> + <decay-half-life-unreachable>900</decay-half-life-unreachable> + <enable>yes</enable> + </entry> + </dampening-profile> + <routing-options> + <graceful-restart> + <enable>yes</enable> + </graceful-restart> + </routing-options> + </bgp> + <rip> + <enable>no</enable> + </rip> + <ospf> + <enable>no</enable> + </ospf> + <ospfv3> + <enable>no</enable> + </ospfv3> + </protocol> + <interface> + <member>ethernet1/1</member> + <member>ethernet1/1.1</member> + <member>ethernet1/1.2</member> + </interface> + <ecmp> + <algorithm> + <ip-modulo/> + </algorithm> + </ecmp> + <routing-table> + <ip> + <static-route> + <entry name="default-route"> + <path-monitor> + <enable>no</enable> + <failure-condition>any</failure-condition> + <hold-time>2</hold-time> + </path-monitor> + <nexthop> + <ip-address>${data_gateway_ip}</ip-address> + </nexthop> + <bfd> + <profile>None</profile> + </bfd> + <interface>ethernet1/1</interface> + <metric>10</metric> + <destination>168.63.129.16/32</destination> + <route-table> + <unicast/> + </route-table> + </entry> + </static-route> + </ip> + </routing-table> + </entry> + </virtual-router> + </network> + <deviceconfig> + <system> + <type> + <dhcp-client> + <send-hostname>yes</send-hostname> + <send-client-id>no</send-client-id> + <accept-dhcp-hostname>no</accept-dhcp-hostname> + <accept-dhcp-domain>no</accept-dhcp-domain> + </dhcp-client> + </type> + <update-server>updates.paloaltonetworks.com</update-server> + <update-schedule> + <threats> + <recurring> + <weekly> + <day-of-week>wednesday</day-of-week> + <at>01:02</at> + <action>download-only</action> + </weekly> + </recurring> + </threats> + </update-schedule> + <timezone>US/Pacific</timezone> + <service> + <disable-telnet>yes</disable-telnet> + <disable-http>yes</disable-http> + </service> + <ntp-servers> + <primary-ntp-server> + <ntp-server-address>0.us.pool.ntp.org</ntp-server-address> + <authentication-type> + <none/> + </authentication-type> + </primary-ntp-server> + <secondary-ntp-server> + <ntp-server-address>1.us.pool.ntp.org</ntp-server-address> + <authentication-type> + <none/> + </authentication-type> + </secondary-ntp-server> + </ntp-servers> + </system> + <setting> + <config> + <rematch>yes</rematch> + </config> + <management> + <hostname-type-in-syslog>FQDN</hostname-type-in-syslog> + <initcfg> + <username>panadmin</username> + <type> + <dhcp-client> + <send-hostname>yes</send-hostname> + <send-client-id>no</send-client-id> + <accept-dhcp-hostname>no</accept-dhcp-hostname> + <accept-dhcp-domain>no</accept-dhcp-domain> + </dhcp-client> + </type> + </initcfg> + </management> + </setting> +%{ if ai_instr_key != null ~} + <plugins> + <vm_series version="2.0.3"> + <azure-advanced-metrics> + <enable>yes</enable> + <instrumentation-key>${ai_instr_key}</instrumentation-key> + <update-interval>${ai_update_interval}</update-interval> + </azure-advanced-metrics> + </vm_series> + </plugins> +%{ endif ~} + </deviceconfig> + <vsys> + <entry name="vsys1"> + <application/> + <application-group/> + <zone> + <entry name="untrust"> + <network> + <layer3> + <member>ethernet1/1.2</member> + </layer3> + </network> + </entry> + <entry name="trust"> + <network> + <layer3> + <member>ethernet1/1</member> + <member>ethernet1/1.1</member> + </layer3> + </network> + </entry> + </zone> + <service/> + <service-group/> + <schedule/> + <rulebase> + <security> + <rules> + <entry name="allow-all" uuid="54cb4573-d2a6-4338-8ee8-8064f14a55ca"> + <to> + <member>any</member> + </to> + <from> + <member>any</member> + </from> + <source> + <member>any</member> + </source> + <destination> + <member>any</member> + </destination> + <source-user> + <member>any</member> + </source-user> + <category> + <member>any</member> + </category> + <application> + <member>any</member> + </application> + <service> + <member>service-http</member> + <member>service-https</member> + </service> + <source-hip> + <member>any</member> + </source-hip> + <destination-hip> + <member>any</member> + </destination-hip> + <action>allow</action> + </entry> + </rules> + </security> + </rulebase> + <import> + <network> + <interface> + <member>ethernet1/1</member> + <member>ethernet1/1.1</member> + <member>ethernet1/1.2</member> + </interface> + </network> + </import> + </entry> + </vsys> + </entry> + </devices> +</config> diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf new file mode 100644 index 00000000..23d3690d --- /dev/null +++ b/examples/gwlb_with_vmseries/variables.tf @@ -0,0 +1,595 @@ +### 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: + ``` + name_prefix = "test-" + ``` + + **Note!** \ + 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +} + + +### Load Balancing +variable "load_balancers" { + description = <<-EOF + 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, 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_name = 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) + })), {}) + })), {}) + })) +} + + +### GWLB +variable "gateway_load_balancers" { + description = <<-EOF + Map with Gateway Load Balancer definitions. + + Following settings are available: + - `name` - (`string`, required) name of the Gatewa Load Balancer Gateway. + - `frontend_ip` - (`object`, required) frontend IP configuration + (refer to [module documentation](../../modules/gwlb/README.md) for details) + - `health_probe - (`object`, optional) health probe settings + (refer to [module documentation](../../modules/gwlb/README.md) for details) + - `backends` - (`map`, optional) map of backends + (refer to [module documentation](../../modules/gwlb/README.md) for details) + - `lb_rule` - (`object`, optional) load balancer rule + (refer to [module documentation](../../modules/gwlb/README.md) for details) + EOF + default = {} + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + frontend_ip = object({ + name = optional(string) + vnet_key = string + subnet_key = string + private_ip_address = optional(string) + private_ip_address_version = optional(string, "IPv4") + }) + health_probe = optional(object({ + name = optional(string) + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string, "/") + })) + backends = optional(map(object({ + name = optional(string) + tunnel_interfaces = map(object({ + identifier = number + port = number + protocol = optional(string, "VXLAN") + type = string + })) + }))) + lb_rule = optional(object({ + name = optional(string) + load_distribution = optional(string, "Default") + })) + })) +} + + +variable "availability_sets" { + description = <<-EOF + A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + + Following properties are supported: + - `name` - name of the Application Insights. + - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). + - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + + Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). + Please verify how many update and fault domain are supported in a region before deploying this resource. + EOF + default = {} + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +} + + +variable "ngfw_metrics" { + description = <<-EOF + A map controlling metrics-relates resources. + + When set to explicit `null` (default) it will disable any metrics resources in this deployment. + + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + + Following properties are available: + + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + EOF + default = null + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +} + + +variable "bootstrap_storages" { + description = <<-EOF + A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + + You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to + [module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + EOF + default = {} + nullable = false + type = map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +} + + +### VM-Series +variable "vmseries" { + description = <<-EOF + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + + For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + + The most basic properties are as follows: + + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a data + Load Balancer health checks. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + EOF + default = {} + nullable = false + type = map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + data_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || + v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null + ]) + error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + } + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.data_snet_key != null : true + if v.virtual_machine.bootstrap_package != null + ]) + error_message = "The `data_snet_key` is required when `bootstrap_xml_template` is set." + } +} + + +### Application VMs +variable "appvms" { + description = <<-EOF + Configuration for sample application VMs. + EOF + default = {} + type = any +} diff --git a/examples/gwlb_with_vmseries/versions.tf b/examples/gwlb_with_vmseries/versions.tf new file mode 100644 index 00000000..e69126ed --- /dev/null +++ b/examples/gwlb_with_vmseries/versions.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">= 1.5, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} From 36ddea5a122b0088fba9796b387c69102204db6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Pawl=C4=99ga?= <42772730+FoSix@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:03:44 +0100 Subject: [PATCH 20/49] refactor: common config variable (#6) Co-authored-by: Adrian Celebanski <acelebanski@paloaltonetworks.com> Co-authored-by: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Co-authored-by: Alp Eren Kose <alperenkose@gmail.com> --- examples/appgw/README.md | 219 ++-- examples/appgw/example.tfvars | 433 +++---- examples/appgw/main.tf | 45 +- examples/appgw/variables.tf | 216 ++-- examples/common_vmseries/README.md | 437 ++++--- examples/common_vmseries/example.tfvars | 89 +- examples/common_vmseries/main.tf | 81 +- examples/common_vmseries/variables.tf | 298 ++--- .../common_vmseries_and_autoscale/.header.md | 198 ++++ .../common_vmseries_and_autoscale/README.md | 1040 +++++++++++++--- .../example.tfvars | 47 +- .../common_vmseries_and_autoscale/main.tf | 41 +- .../variables.tf | 297 ++--- examples/dedicated_vmseries/README.md | 437 ++++--- examples/dedicated_vmseries/example.tfvars | 91 +- examples/dedicated_vmseries/main.tf | 81 +- examples/dedicated_vmseries/variables.tf | 300 ++--- .../.header.md | 194 +++ .../README.md | 1036 +++++++++++++--- .../example.tfvars | 32 +- .../dedicated_vmseries_and_autoscale/main.tf | 41 +- .../variables.tf | 295 ++--- examples/gwlb_with_vmseries/README.md | 40 +- examples/gwlb_with_vmseries/main.tf | 3 +- examples/gwlb_with_vmseries/variables.tf | 39 +- examples/standalone_panorama/.header.md | 120 ++ examples/standalone_panorama/README.md | 524 ++++++-- examples/standalone_panorama/example.tfvars | 2 +- examples/standalone_panorama/main.tf | 2 +- examples/standalone_panorama/variables.tf | 6 +- examples/standalone_vmseries/README.md | 437 ++++--- examples/standalone_vmseries/example.tfvars | 2 +- examples/standalone_vmseries/main.tf | 79 +- examples/standalone_vmseries/variables.tf | 300 ++--- examples/virtual_network_gateway/.header.md | 5 + examples/virtual_network_gateway/README.md | 310 +++++ .../virtual_network_gateway/example.tfvars | 232 ++++ examples/virtual_network_gateway/main.tf | 67 ++ examples/virtual_network_gateway/main_test.go | 62 + examples/virtual_network_gateway/outputs.tf | 9 + examples/virtual_network_gateway/variables.tf | 209 ++++ examples/virtual_network_gateway/versions.tf | 22 + modules/appgw/.header.md | 11 + modules/appgw/README.md | 702 +++++------ modules/appgw/main.tf | 86 +- modules/appgw/outputs.tf | 4 +- modules/appgw/variables.tf | 909 +++++++------- modules/appgw/versions.tf | 2 +- modules/bootstrap/README.md | 30 +- modules/bootstrap/variables.tf | 107 +- modules/gwlb/README.md | 17 +- modules/gwlb/variables.tf | 82 +- modules/loadbalancer/README.md | 31 +- modules/loadbalancer/outputs.tf | 3 +- modules/loadbalancer/variables.tf | 173 ++- modules/name_templater/.header.md | 54 + modules/name_templater/README.md | 167 ++- modules/name_templater/variables.tf | 22 +- modules/natgw/.header.md | 8 + modules/natgw/README.md | 23 +- modules/natgw/main.tf | 8 - modules/natgw/variables.tf | 41 +- modules/ngfw_metrics/README.md | 9 +- modules/ngfw_metrics/variables.tf | 31 +- modules/panorama/README.md | 9 +- modules/panorama/variables.tf | 70 +- modules/virtual_network_gateway/.header.md | 358 ++++-- modules/virtual_network_gateway/README.md | 1053 +++++++++-------- modules/virtual_network_gateway/main.tf | 201 ++-- modules/virtual_network_gateway/variables.tf | 839 ++++++------- modules/virtual_network_gateway/versions.tf | 4 +- modules/vmseries/README.md | 75 +- modules/vmseries/main.tf | 7 +- modules/vmseries/variables.tf | 118 +- modules/vmss/README.md | 213 ++-- modules/vmss/main.tf | 7 +- modules/vmss/variables.tf | 357 +++--- modules/vnet/README.md | 97 +- modules/vnet/variables.tf | 267 +++-- 79 files changed, 9429 insertions(+), 5184 deletions(-) create mode 100644 examples/common_vmseries_and_autoscale/.header.md create mode 100644 examples/dedicated_vmseries_and_autoscale/.header.md create mode 100644 examples/standalone_panorama/.header.md create mode 100644 examples/virtual_network_gateway/.header.md create mode 100644 examples/virtual_network_gateway/README.md create mode 100644 examples/virtual_network_gateway/example.tfvars create mode 100644 examples/virtual_network_gateway/main.tf create mode 100644 examples/virtual_network_gateway/main_test.go create mode 100644 examples/virtual_network_gateway/outputs.tf create mode 100644 examples/virtual_network_gateway/variables.tf create mode 100644 examples/virtual_network_gateway/versions.tf create mode 100644 modules/name_templater/.header.md diff --git a/examples/appgw/README.md b/examples/appgw/README.md index abb9fc3b..c1a29658 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -43,7 +43,7 @@ Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | Manage the network required for the topology. -`appgw` | - | ../../modules/appgw | Create Application Gateay +`appgw` | - | ../../modules/appgw | Create Application Gateway Resources used in this module: @@ -158,122 +158,124 @@ A map defining all Application Gateways in the current deployment. For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). -Following properties are supported: -- `name` - (`string`, required) name of the Application Gateway. -- `public_ip` - (`string`, required) public IP address. -- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. -- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. - This has to be a subnet dedicated to Application Gateways v2. -- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, - which Application Gateway uses to retrieve certificates from Key Vault. -- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported -- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway -- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - - this property is used by both: the Application Gateway and the Public IP created - in front of the AppGW. -- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name -- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address - will be used in backend pool -- `listeners` - (`map`, required) map of listeners (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `backend_pool` - (`object`, optional) backend pool (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `backends` - (`map`, optional) map of backends (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `probes` - (`map`, optional) map of probes (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `rewrites` - (`map`, optional) map of rewrites (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `rules` - (`map`, required) map of rules (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `redirects` - (`map`, optional) map of redirects (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `url_path_maps` - (`map`, optional) map of URL path maps (refer to - [module documentation](../../modules/appgw/README.md) for details) -- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` -- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` -- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, - for `ssl_policy_type` set to `Custom` -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, - for `ssl_policy_type` set to `Custom` -- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS - listeners by providing a name of the profile in the `ssl_profile_name` property +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. Type: ```hcl map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -281,51 +283,39 @@ map(object({ sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) ``` @@ -385,5 +375,4 @@ Default value: `true` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars index 96b37a39..ffd4a7fb 100644 --- a/examples/appgw/example.tfvars +++ b/examples/appgw/example.tfvars @@ -1,7 +1,7 @@ # --- GENERAL --- # location = "North Europe" resource_group_name = "appgw-example" -name_prefix = "sczech-" +name_prefix = "fosix-" tags = { "CreatedBy" = "Palo Alto Networks" "CreatedWith" = "Terraform" @@ -39,16 +39,42 @@ vnets = { # --- APPGW PART --- # appgws = { - "public-http-minimum" = { - name = "appgw-http-minimum" + "public-empty" = { + name = "empty" + vnet_key = "transit" + subnet_key = "appgw" public_ip = { - name = "pip-http-minimum" + name = "public-empty-ip" + } + listeners = { + "http" = { + name = "http" + port = 80 + } + } + backend_settings = { + http = { + name = "http" + port = 80 + protocol = "Http" + } + } + rules = { + "http" = { + name = "http" + listener_key = "http" + backend_key = "http" + priority = 1 + } } + } + "public-http-minimum" = { + name = "appgw-http-minimum" vnet_key = "transit" subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 + zones = [] + public_ip = { + name = "pip-http-minimum" } listeners = { minimum = { @@ -56,6 +82,13 @@ appgws = { port = 80 } } + backend_settings = { + minimum = { + name = "minimum-backend" + port = 80 + protocol = "Http" + } + } rewrites = { minimum = { name = "minimum-set" @@ -72,41 +105,38 @@ appgws = { } rules = { minimum = { - name = "minimum-rule" - priority = 1 - backend = "minimum" - listener = "minimum" - rewrite = "minimum" + name = "minimum-rule" + priority = 1 + backend_key = "minimum" + listener_key = "minimum" + rewrite_key = "minimum" } } } "public-http-existing" = { - name = "appgw-http-existing" + name = "appgw-http-existing" + vnet_key = "transit" + subnet_key = "appgw" + zones = ["1"] public_ip = { name = "pip-existing" create = false } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 - } - backends = { - existing = { - name = "http-backend" - port = 80 - protocol = "Http" - timeout = 60 - cookie_based_affinity = "Enabled" - } - } listeners = { existing = { name = "existing-listener" port = 80 } } + backend_settings = { + existing = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + use_cookie_based_affinity = true + } + } rewrites = { existing = { name = "existing-set" @@ -123,83 +153,84 @@ appgws = { } rules = { existing = { - name = "existing-rule" - priority = 1 - backend = "existing" - listener = "existing" - rewrite = "existing" + name = "existing-rule" + priority = 1 + backend_key = "existing" + listener_key = "existing" + rewrite_key = "existing" } } } "public-http-autoscale" = { - name = "appgw-http-autoscale" + name = "appgw-http-autoscale" + vnet_key = "transit" + subnet_key = "appgw" + zones = null public_ip = { name = "pip-http-autoscale" } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] capacity = { autoscale = { min = 2 max = 20 } } - backends = { - http = { - name = "http-backend" - port = 80 - protocol = "Http" - timeout = 60 - cookie_based_affinity = "Enabled" - } - } listeners = { http = { name = "http-listener" port = 80 } } + backend_settings = { + http = { + name = "http-backend" + port = 80 + protocol = "Http" + timeout = 60 + use_cookie_based_affinity = true + } + } rules = { http = { - name = "http-rule" - priority = 1 - backend = "http" - listener = "http" + name = "http-rule" + priority = 1 + backend_key = "http" + listener_key = "http" } } } "public-waf" = { - name = "appgw-waf" + name = "appgw-waf" + vnet_key = "transit" + subnet_key = "appgw" + zones = [] public_ip = { name = "pip-waf" } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] capacity = { - static = 2 + static = 4 } + enable_http2 = true waf = { prevention_mode = true rule_set_type = "OWASP" rule_set_version = "3.2" } - backends = { - waf = { - name = "waf-backend" - port = 80 - protocol = "Http" - timeout = 60 - cookie_based_affinity = "Enabled" - } - } listeners = { waf = { name = "waf-listener" port = 80 } } + backend_settings = { + waf = { + name = "waf-backend" + port = 80 + protocol = "Http" + timeout = 60 + use_cookie_based_affinity = true + } + } rewrites = { waf = { name = "waf-set" @@ -216,11 +247,11 @@ appgws = { } rules = { minimum = { - name = "waf-rule" - priority = 1 - backend = "waf" - listener = "waf" - rewrite = "waf" + name = "waf-rule" + priority = 1 + backend_key = "waf" + listener_key = "waf" + rewrite_key = "waf" } } } @@ -238,20 +269,17 @@ appgws = { # openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx # openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx "public-ssl-custom" = { - name = "appgw-ssl-custom" - public_ip = { - name = "pip-ssl-custom" - } + name = "appgw-ssl-custom" vnet_key = "transit" subnet_key = "appgw" zones = ["1", "2", "3"] - capacity = { - static = 2 + public_ip = { + name = "pip-ssl-custom" } - ssl_global = { - ssl_policy_type = "Custom" - ssl_policy_min_protocol_version = "TLSv1_0" - ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + global_ssl_policy = { + type = "Custom" + min_protocol_version = "TLSv1_0" + cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", @@ -333,23 +361,23 @@ appgws = { backend_pool = { name = "vmseries-pool" } - backends = { + backend_settings = { http = { - name = "http-settings" - port = 80 - protocol = "Http" - timeout = 60 - cookie_based_affinity = "Enabled" - probe = "http" + name = "http-settings" + port = 80 + protocol = "Http" + timeout = 60 + use_cookie_based_affinity = true + probe = "http" } https1 = { - name = "https1-settings" - port = 481 - protocol = "Https" - timeout = 60 - cookie_based_affinity = "Enabled" - hostname_from_backend = false - hostname = "test1.appgw.local" + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + use_cookie_based_affinity = true + hostname_from_backend = false + hostname = "test1.appgw.local" root_certs = { test = { name = "https-application-test1" @@ -359,13 +387,13 @@ appgws = { probe = "https1" } https2 = { - name = "https2-settings" - port = 482 - protocol = "Https" - timeout = 60 - cookie_based_affinity = "Enabled" - hostname_from_backend = false - hostname = "test2.appgw.local" + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + use_cookie_based_affinity = true + hostname_from_backend = false + hostname = "test2.appgw.local" root_certs = { test = { name = "https-application-test2" @@ -450,64 +478,11 @@ appgws = { } } } - rules = { - http = { - name = "http-rule" - priority = 1 - backend = "http" - listener = "http" - rewrite = "http" - } - https1 = { - name = "https1-rule" - priority = 2 - backend = "https1" - listener = "https1" - rewrite = "https1" - } - https2 = { - name = "https2-rule" - priority = 3 - backend = "https2" - listener = "https2" - rewrite = "https2" - } - redirect_listener = { - name = "redirect-listener-rule" - priority = 4 - listener = "redirect_listener" - redirect = "redirect_listener" - } - redirect_url = { - name = "redirect-url-rule" - priority = 5 - listener = "redirect_url" - redirect = "redirect_url" - } - path_based_backend = { - name = "path-based-backend-rule" - priority = 6 - listener = "path_based_backend" - url_path_map = "path_based_backend" - } - path_based_redirect_listener = { - name = "path-redirect-listener-rule" - priority = 7 - listener = "path_based_redirect_listener" - url_path_map = "path_based_redirect_listener" - } - path_based_redirect_url = { - name = "path-redirect-rul-rule" - priority = 8 - listener = "path_based_redirect_url" - url_path_map = "path_based_redirect_url" - } - } redirects = { redirect_listener = { name = "listener-redirect" type = "Permanent" - target_listener = "http" + target_listener_key = "http" include_path = true include_query_string = true } @@ -521,55 +496,104 @@ appgws = { } url_path_maps = { path_based_backend = { - name = "backend-map" - backend = "http" + name = "backend-map" + backend_key = "http" path_rules = { http = { - paths = ["/plaintext"] - backend = "http" + paths = ["/plaintext"] + backend_key = "http" } https = { - paths = ["/secure"] - backend = "https1" + paths = ["/secure"] + backend_key = "https1" } } } path_based_redirect_listener = { - name = "redirect-listener-map" - backend = "http" + name = "redirect-listener-map" + backend_key = "http" path_rules = { http = { - paths = ["/redirect"] - redirect = "redirect_listener" + paths = ["/redirect"] + redirect_key = "redirect_listener" } } } path_based_redirect_url = { - name = "redirect-url-map" - backend = "http" + name = "redirect-url-map" + backend_key = "http" path_rules = { http = { - paths = ["/redirect"] - redirect = "redirect_url" + paths = ["/redirect"] + redirect_key = "redirect_url" } } } } + rules = { + http = { + name = "http-rule" + priority = 1 + backend_key = "http" + listener_key = "http" + rewrite_key = "http" + } + https1 = { + name = "https1-rule" + priority = 2 + backend_key = "https1" + listener_key = "https1" + rewrite_key = "https1" + } + https2 = { + name = "https2-rule" + priority = 3 + backend_key = "https2" + listener_key = "https2" + rewrite_key = "https2" + } + redirect_listener = { + name = "redirect-listener-rule" + priority = 4 + listener_key = "redirect_listener" + redirect_key = "redirect_listener" + } + redirect_url = { + name = "redirect-url-rule" + priority = 5 + listener_key = "redirect_url" + redirect_key = "redirect_url" + } + path_based_backend = { + name = "path-based-backend-rule" + priority = 6 + listener_key = "path_based_backend" + url_path_map_key = "path_based_backend" + } + path_based_redirect_listener = { + name = "path-redirect-listener-rule" + priority = 7 + listener_key = "path_based_redirect_listener" + url_path_map_key = "path_based_redirect_listener" + } + path_based_redirect_url = { + name = "path-redirect-rul-rule" + priority = 8 + listener_key = "path_based_redirect_url" + url_path_map_key = "path_based_redirect_url" + } + } } "public-ssl-predefined" = { - name = "appgw-ssl-predefined" - public_ip = { - name = "pip-ssl-predefined" - } + name = "appgw-ssl-predefined" vnet_key = "transit" subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 + public_ip = { + name = "pip-ssl-predefined" } - ssl_global = { - ssl_policy_type = "Predefined" - ssl_policy_name = "AppGwSslPolicy20170401" + global_ssl_policy = { + type = "Predefined" + name = "AppGwSslPolicy20170401" } ssl_profiles = { profile1 = { @@ -577,7 +601,6 @@ appgws = { ssl_policy_name = "AppGwSslPolicy20170401S" } } - frontend_ip_configuration_name = "public_ipconfig" listeners = { https1 = { name = "https1-listener" @@ -598,17 +621,17 @@ appgws = { } } backend_pool = { - name = "vmseries-pool" + name = "vmseries-pool-custom" } - backends = { + backend_settings = { https1 = { - name = "https1-settings" - port = 481 - protocol = "Https" - timeout = 60 - cookie_based_affinity = "Enabled" - hostname_from_backend = false - hostname = "test1.appgw.local" + name = "https1-settings" + port = 481 + protocol = "Https" + timeout = 60 + use_cookie_based_affinity = true + hostname_from_backend = false + hostname = "test1.appgw.local" root_certs = { test = { name = "https-application-test1" @@ -617,13 +640,13 @@ appgws = { } } https2 = { - name = "https2-settings" - port = 482 - protocol = "Https" - timeout = 60 - cookie_based_affinity = "Enabled" - hostname_from_backend = false - hostname = "test2.appgw.local" + name = "https2-settings" + port = 482 + protocol = "Https" + timeout = 60 + use_cookie_based_affinity = true + hostname_from_backend = false + hostname = "test2.appgw.local" root_certs = { test = { name = "https-application-test2" @@ -676,18 +699,18 @@ appgws = { } rules = { https1 = { - name = "https1-rule" - priority = 2 - backend = "https1" - listener = "https1" - rewrite = "https1" + name = "https1-rule" + priority = 2 + backend_key = "https1" + listener_key = "https1" + rewrite_key = "https1" } https2 = { - name = "https2-rule" - priority = 3 - backend = "https2" - listener = "https2" - rewrite = "https2" + name = "https2-rule" + priority = 3 + backend_key = "https2" + listener_key = "https2" + rewrite_key = "https2" } } } diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf index c6fcf8fa..55610608 100644 --- a/examples/appgw/main.tf +++ b/examples/appgw/main.tf @@ -19,7 +19,7 @@ locals { # Create public IP in order to reuse it in 1 of the application gateways resource "azurerm_public_ip" "this" { name = "pip-existing" - resource_group_name = "${var.name_prefix}${var.resource_group_name}" + resource_group_name = local.resource_group.name location = var.location sku = "Standard" @@ -34,51 +34,56 @@ module "vnet" { for_each = var.vnets - name = each.value.name + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location address_space = each.value.address_space - create_subnets = each.value.create_subnets - subnets = each.value.subnets - network_security_groups = each.value.network_security_groups - route_tables = each.value.route_tables + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } tags = var.tags } -# Create Application Gateay +# Create Application Gateway module "appgw" { source = "../../modules/appgw" for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners backend_pool = each.value.backend_pool - backends = each.value.backends + backend_settings = each.value.backend_settings probes = each.value.probes rewrites = each.value.rewrites - rules = each.value.rules redirects = each.value.redirects url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + rules = each.value.rules tags = var.tags depends_on = [module.vnet, azurerm_public_ip.this] diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf index bcdece72..796b38b0 100644 --- a/examples/appgw/variables.tf +++ b/examples/appgw/variables.tf @@ -122,118 +122,120 @@ variable "appgws" { For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. - This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, - which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - - this property is used by both: the Application Gateway and the Public IP created - in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address - will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to - [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, - for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, - for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS - listeners by providing a name of the profile in the `ssl_profile_name` property + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -241,50 +243,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) } diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 8e919c2d..64350283 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -174,6 +174,7 @@ 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -183,14 +184,12 @@ 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. -[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -265,7 +264,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -346,6 +344,176 @@ map(object({ +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> @@ -400,16 +568,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> -#### enable_zones - -If `true`, enable zone support for resources. - -Type: bool - -Default value: `true` - -<sup>[back to list](#modules-optional-inputs)</sup> - #### natgws @@ -491,42 +649,44 @@ This is a brief description of available properties. For a detailed one please r 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 +- `name` - (`string`, required) a name of the Load Balancer. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. Type: ```hcl map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -548,7 +708,6 @@ map(object({ public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -768,8 +927,6 @@ The most basic properties are as follows: The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -831,6 +988,9 @@ The most basic properties are as follows: For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -840,15 +1000,16 @@ The most basic properties are as follows: The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. @@ -872,7 +1033,6 @@ map(object({ custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -885,18 +1045,20 @@ map(object({ ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -905,173 +1067,8 @@ map(object({ public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) - })) - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - -Following properties are supported: -- `name` - (`string`, required) name of the Application Gateway. -- `public_ip` - (`string`, required) public IP address. -- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. -- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. -- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. -- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) -- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported -- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway -- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. -- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name -- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool -- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) -- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) -- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) -- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) -- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` -- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` -- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` -- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - - -Type: - -```hcl -map(object({ - name = string - public_ip = object({ - name = string - resource_group_name = optional(string) - create = optional(bool, true) - }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ - static = optional(number) - autoscale = optional(object({ - min = optional(number) - max = optional(number) - })) - }) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string, "OWASP") - rule_set_version = optional(string) - })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") - listeners = map(object({ - name = string - port = number - protocol = optional(string, "Http") - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) - })) - backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) - })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - })), {}) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) - match_code = optional(list(number)) - match_body = optional(string) - })), {}) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) - }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) + application_gateway_key = optional(string) })) - redirects = optional(map(object({ - name = string - type = string - target_listener = optional(string) - target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) - url_path_maps = optional(map(object({ - name = string - backend = string - path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) - }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) ``` diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 86769e85..3dcf6c81 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -47,6 +47,11 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } + "appgw_blackhole" = { + name = "appgw-blackhole-udr" + address_prefix = "10.0.0.48/28" + next_hop_type = "None" + } } } "private" = { @@ -68,6 +73,11 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } + "appgw_blackhole" = { + name = "appgw-blackhole-udr" + address_prefix = "10.0.0.48/28" + next_hop_type = "None" + } } } "public" = { @@ -105,6 +115,10 @@ vnets = { network_security_group_key = "public" route_table_key = "public" } + "appgw" = { + name = "appgw-snet" + address_prefixes = ["10.0.0.48/28"] + } } } } @@ -135,11 +149,11 @@ load_balancers = { } } "private" = { - name = "private-lb" + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { name = "private-vmseries" - vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { @@ -162,11 +176,11 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" size = "Standard_DS3_v2" zone = 1 bootstrap_options = "type=dhcp-client" } + vnet_key = "transit" interfaces = [ { name = "vm01-mgmt" @@ -179,10 +193,11 @@ vmseries = { load_balancer_key = "private" }, { - name = "vm01-public" - subnet_key = "public" - create_public_ip = true - load_balancer_key = "public" + name = "vm01-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + application_gateway_key = "public" } ] } @@ -192,11 +207,11 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" size = "Standard_DS3_v2" zone = 2 bootstrap_options = "type=dhcp-client" } + vnet_key = "transit" interfaces = [ { name = "vm02-mgmt" @@ -209,11 +224,61 @@ vmseries = { load_balancer_key = "private" }, { - name = "vm02-public" - subnet_key = "public" - create_public_ip = true - load_balancer_key = "public" + name = "vm02-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + application_gateway_key = "public" } ] } } + + +# --- APPLICATION GATEWAYs --- # +appgws = { + public = { + name = "appgw" + vnet_key = "transit" + subnet_key = "appgw" + public_ip = { + name = "appgw-pip" + } + listeners = { + "http" = { + name = "http" + port = 80 + } + } + backend_settings = { + http = { + name = "http" + port = 80 + protocol = "Http" + } + } + rewrites = { + xff = { + name = "XFF-set" + rules = { + "xff-strip-port" = { + name = "xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + "http" = { + name = "http" + listener_key = "http" + backend_key = "http" + rewrite_key = "xff" + priority = 1 + } + } + } +} diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 031912b2..b2a5393d 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -74,7 +74,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) @@ -99,6 +99,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -123,8 +124,8 @@ module "load_balancer" { 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) + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : v.public_ip_name, + subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) } ) } @@ -160,7 +161,7 @@ module "ngfw_metrics" { resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : - k => v.virtual_machine + k => merge(v.virtual_machine, { vnet_key = v.vnet_key }) if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) } @@ -295,7 +296,7 @@ module "vmseries" { interfaces = [for v in each.value.interfaces : { name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name @@ -314,43 +315,59 @@ module "vmseries" { ] } +# Create Application Gateway + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + module "appgw" { source = "../../modules/appgw" for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners - backend_pool = { - name = "vmseries" - vmseries_ips = [ - for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name - ].private_ip_address if try(v.add_to_appgw_backend, false) - ] - } - backends = each.value.backends - probes = each.value.probes - rewrites = each.value.rewrites - rules = each.value.rules - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules tags = var.tags - depends_on = [module.vnet] -} \ No newline at end of file + depends_on = [module.vnet, module.vmseries] +} diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index a0bf9103..9057e006 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -45,12 +45,6 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - ### VNET @@ -197,40 +191,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -252,7 +248,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -447,8 +442,6 @@ variable "vmseries" { The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -510,6 +503,9 @@ variable "vmseries" { For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -519,15 +515,16 @@ variable "vmseries" { The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. EOF default = {} @@ -549,7 +546,6 @@ variable "vmseries" { custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -562,18 +558,20 @@ variable "vmseries" { ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -582,7 +580,7 @@ variable "vmseries" { public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) + application_gateway_key = optional(string) })) })) validation { @@ -608,105 +606,123 @@ variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF - default = {} - nullable = false type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -714,50 +730,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) } diff --git a/examples/common_vmseries_and_autoscale/.header.md b/examples/common_vmseries_and_autoscale/.header.md new file mode 100644 index 00000000..ec76e4d7 --- /dev/null +++ b/examples/common_vmseries_and_autoscale/.header.md @@ -0,0 +1,198 @@ +--- +short_title: Common Firewall Option with Autoscaling +type: refarch +show_in_hub: true +--- +# Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Common NGFW Option with Autoscaling + +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +common VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented +metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane +utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. To ease licensing, +management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, +but a [dedicated one exists](../standalone\_panorama/README.md). + +## Reference Architecture Design + +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) + +This code implements: + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *common option*, which routes all traffic flows onto a single set of VM-Series +- *auto scaling- for the VM-Series, where a Virtual Machine Scale Set (VMSS) is used to provision VM-Series that will scale in and + out dynamically, as workload demands fluctuate + +## Detailed Architecture and Design + +### Centralized Design + +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a +hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, +east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. + +### Common Option + +The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource +and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation +that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and +outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. + +![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) + +This reference architecture consists of: + +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 1 Virtual Machine Scale set: + - deployed across availability zones + - for inbound, outbound and east-west traffic + - with 3 network interfaces: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the public interfaces of the firewalls in VMSS, for incoming traffic + - private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic +- an Application Insights, used to store the custom PanOS metrics sent from firewalls in scale set +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly + +**Disclaimer!** \ +Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private +Panorama connectivity in place and Panorama Software Firewall License plugin you can bootstrap the firewalls without public IPs +assigned to the management interfaces. You should also enable +[Automatically push content when software device registers to Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-new-features/panorama-features/automatic-content-push-for-vm-series-and-cn-series-firewalls) +on the template stack and +[schedule content updates using Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-upgrade/upgrade-panorama/deploy-updates-to-firewalls-log-collectors-and-wildfire-appliances-using-panorama/schedule-a-content-update-using-panorama). +Alternatively content updates can be configured to be fetched via data plane interfaces with service routes. + +### Auto Scaling VM-Series + +Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference +stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources +allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and +VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload +demands fluctuate. The VM-Series firewalls are deployed in a Virtual Machine Scale Set for inbound and outbound/east-west +firewalls in common option, and are automatically registered to Azure Load Balancers. + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) + +A non-platform requirement would be a running Panorama instance. For full automation you might want to consider the following requirements: + +- a template and a template stack with `DAY0` configuration +- a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + and any security and NAT rules of your choice +- a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices +- a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) + plugin to enable additional template options (custom metrics) + +**Note!** + +- after the deployment the firewalls remain not configured and not licensed. +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. + +## Usage + +### Deployment Steps + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you + might want to also adjust the `bootstrap_options` for the scale set [`common`](./example.tfvars#L224). +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + Apply complete! Resources: 43 added, 0 changed, 0 destroyed. + + Outputs: + + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = <sensitive> + password = <sensitive> + username = "panadmin" + ``` + +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. + +### Post deploy + +The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights +instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: + +```bash +terraform output metrics_instrumentation_keys +``` + +The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom +metrics are being sent to Application Insights and retrieved by the Virtual Machine Scale Set to trigger scale-in and scale-out +operations. + +Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. +To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output username + ``` + +- for password: + + ```bash + terraform output password + ``` + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 23ff0882..1cb1b8b5 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -1,211 +1,975 @@ +<!-- BEGIN_TF_DOCS --> --- -short_title: Common Firewall Option with Autoscaling +short\_title: Common Firewall Option with Autoscaling type: refarch -show_in_hub: true +show\_in\_hub: true --- # Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Common NGFW Option with Autoscaling -Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with common VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PAN-OS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +common VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from +[the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented +metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane +utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. To ease licensing, +management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, +but a [dedicated one exists](../standalone\\_panorama/README.md). ## Reference Architecture Design ![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) This code implements: -- a _centralized design_, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, east-west, and enterprise traffic -- the _common option_, which routes all traffic flows onto a single set of VM-Series -- _auto scaling_ for the VM-Series, where a Virtual Machine Scale Set (VMSS) is used to provision VM-Series that will scale in and out dynamically, as workload demands fluctuate + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *common option*, which routes all traffic flows onto a single set of VM-Series +- *auto scaling- for the VM-Series, where a Virtual Machine Scale Set (VMSS) is used to provision VM-Series that will scale in and + out dynamically, as workload demands fluctuate ## Detailed Architecture and Design ### Centralized Design -This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a +hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, +east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. ### Common Option -The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. +The common firewall option leverages a single set of VM-Series firewalls. The sole set of firewalls operates as a shared resource +and may present scale limitations with all traffic flowing through a single set of firewalls due to the performance degradation +that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and +outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. -![Common-VM-Series-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) +![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) This reference architecture consists of: -* a VNET containing: - * 4 subnets: - * 3 of them dedicated to the firewalls: management, private and public - * one dedicated to an Application Gateway - * Route Tables and Network Security Groups -* 1 Virtual Machine Scale set: - * deployed across availability zones - * for inbound, outbound and east-west traffic - * with 3 network interfaces: management, public, private - * with public IP addresses assigned to: - * management interface - * public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic -* 2 Load Balancers: - * public - with a public IP address assigned, in front of the public interfaces of the firewalls in VMSS, for incoming traffic - * private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic -* an Application Insights, used to store the custom PAN-OS metrics sent from firewalls in scale set -* an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly - -_DISCLAIMER_ - Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private Panorama connectivity in place and Panorama Software Firewall License plugin you can bootstrap the firewalls without public IPs assigned to the management interfaces. You should also enable [Automatically push content when software device registers to Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-new-features/panorama-features/automatic-content-push-for-vm-series-and-cn-series-firewalls) on the template stack and [schedule content updates using Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-upgrade/upgrade-panorama/deploy-updates-to-firewalls-log-collectors-and-wildfire-appliances-using-panorama/schedule-a-content-update-using-panorama). Alternatively content updates can be configured to be fetched via data plane interfaces with service routes. +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 1 Virtual Machine Scale set: + - deployed across availability zones + - for inbound, outbound and east-west traffic + - with 3 network interfaces: management, public, private + - with public IP addresses assigned to: + - management interface + - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the public interfaces of the firewalls in VMSS, for incoming traffic + - private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic +- an Application Insights, used to store the custom PanOS metrics sent from firewalls in scale set +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly + +**Disclaimer!** \ +Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private +Panorama connectivity in place and Panorama Software Firewall License plugin you can bootstrap the firewalls without public IPs +assigned to the management interfaces. You should also enable +[Automatically push content when software device registers to Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-new-features/panorama-features/automatic-content-push-for-vm-series-and-cn-series-firewalls) +on the template stack and +[schedule content updates using Panorama](https://docs.paloaltonetworks.com/pan-os/10-2/pan-os-upgrade/upgrade-panorama/deploy-updates-to-firewalls-log-collectors-and-wildfire-appliances-using-panorama/schedule-a-content-update-using-panorama). +Alternatively content updates can be configured to be fetched via data plane interfaces with service routes. ### Auto Scaling VM-Series -Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload demands fluctuate. The VM-Series firewalls are deployed in a Virtual Machine Scale Set for inbound and outbound/east-west firewalls in common option, and are automatically registered to Azure Load Balancers. +Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference +stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources +allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and +VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload +demands fluctuate. The VM-Series firewalls are deployed in a Virtual Machine Scale Set for inbound and outbound/east-west +firewalls in common option, and are automatically registered to Azure Load Balancers. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) A non-platform requirement would be a running Panorama instance. For full automation you might want to consider the following requirements: -* a template and a template stack with `DAY0` configuration -* a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + any security and NAT rules of your choice -* a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices -* a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) plugin to enable additional template options (custom metrics) +- a template and a template stack with `DAY0` configuration +- a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + and any security and NAT rules of your choice +- a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices +- a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) + plugin to enable additional template options (custom metrics) -**NOTE:** +**Note!** -* after the deployment the firewalls remain not configured and not licensed. -* this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. +- after the deployment the firewalls remain not configured and not licensed. +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. ## Usage ### Deployment Steps -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for the scale set ([common](./example.tfvars#L205) . -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you + might want to also adjust the `bootstrap_options` for the scale set [`common`](./example.tfvars#L224). +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - Apply complete! Resources: 43 added, 0 changed, 0 destroyed. + ```console + Apply complete! Resources: 43 added, 0 changed, 0 destroyed. - Outputs: + Outputs: - lb_frontend_ips = { - "private" = { - "ha-ports" = "1.2.3.4" - } - "public" = { - "palo-lb-app1-pip" = "1.2.3.4" - } - } - metrics_instrumentation_keys = <sensitive> - password = <sensitive> - username = "panadmin" + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = <sensitive> + password = <sensitive> + username = "panadmin" + ``` -* at this stage you have to wait couple of minutes for the firewalls to bootstrap. +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. ### Post deploy -The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: +The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights +instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: -```sh +```bash terraform output metrics_instrumentation_keys ``` -The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom metrics are being sent to Application Insights and retrieved by the Virtual Machine Scale Set to trigger scale-in and scale-out operations. +The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom +metrics are being sent to Application Insights and retrieved by the Virtual Machine Scale Set to trigger scale-in and scale-out +operations. -Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. To retrieve the initial credentials run: +Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. +To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output username + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output password + ``` ### Cleanup To remove the deployed infrastructure run: -```sh +```bash terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_natgw"></a> [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_appgw"></a> [appgw](#module\_appgw) | ../../modules/appgw | n/a | -| <a name="module_vmss"></a> [vmss](#module\_vmss) | ../../modules/vmss | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | -| <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | -| <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VM-Series virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PAN-OS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VM-Series interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_metrics_instrumentation_keys"></a> [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + +## 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. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial firewall administrative usernames for all deployed Scale Sets. +`passwords` | Initial firewall administrative passwords for all deployed Scale Sets. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. + +## Module's Nameplate + + + + +Providers used in this module: + +- `random` +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`appgw` | - | ../../modules/appgw | +`vmss` | - | ../../modules/vmss | + + +Resources used in this module: + +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### natgws + +A map defining NAT Gateways. + +Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one +explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. +For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + +Following properties are supported: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### load_balancers + +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. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. +- `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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. +- `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, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### scale_sets + +A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + +For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. + +The basic Scale Set configuration properties are as follows: + +- `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` +- `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. + + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. + + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: + + - do not specify anything else - a random password will be generated for you + - specify at least one of `password` or `ssh_keys` properties. + + For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image + + For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). + +- `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set + configuration options. + + Below we present only the most important ones, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): + + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported + - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from + this Scale Set will be created + - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series + instance + +- `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics thresholds, etc) + + Below we present only the most important properties, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in + the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare + the metrics to the thresholds + +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set +- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. Following properties are available: + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets` + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be + added to the Load Balancer's backend pool + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance + +- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + configuration please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) + + + +Type: + +```hcl +map(object({ + name = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + vnet_key = string + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index fa70dd1c..1a9cc94b 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -149,11 +149,11 @@ load_balancers = { } } "private" = { - name = "private-lb" + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { name = "private-vmseries" - vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { @@ -172,29 +172,32 @@ load_balancers = { # --- APPLICATION GATEWAYs --- # appgws = { - "public" = { - name = "appgw" - public_ip = { - name = "pip" - } + public = { + name = "appgw" vnet_key = "transit" subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 + public_ip = { + name = "appgw-pip" } listeners = { - minimum = { - name = "minimum-listener" + "http" = { + name = "http" port = 80 } } + backend_settings = { + http = { + name = "http" + port = 80 + protocol = "Http" + } + } rewrites = { - minimum = { - name = "minimum-set" + xff = { + name = "XFF-set" rules = { "xff-strip-port" = { - name = "minimum-xff-strip-port" + name = "xff-strip-port" sequence = 100 request_headers = { "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" @@ -204,12 +207,12 @@ appgws = { } } rules = { - minimum = { - name = "minimum-rule" - priority = 1 - backend = "minimum" - listener = "minimum" - rewrite = "minimum" + "http" = { + name = "http" + listener_key = "http" + backend_key = "http" + rewrite_key = "xff" + priority = 1 } } } @@ -230,13 +233,13 @@ scale_sets = { disable_password_authentication = false } virtual_machine_scale_set = { - vnet_key = "transit" bootstrap_options = "type=dhcp-client" zones = ["1", "2", "3"] } autoscaling_configuration = { default_count = 1 } + vnet_key = "transit" interfaces = [ { name = "management" diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index bea154c9..4f7c5f84 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -74,7 +74,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) @@ -98,6 +98,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -122,8 +123,8 @@ module "load_balancer" { 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) + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : v.public_ip_name, + subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) } ) } @@ -162,29 +163,32 @@ module "appgw" { for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners - backends = each.value.backends + backend_pool = each.value.backend_pool + backend_settings = each.value.backend_settings probes = each.value.probes rewrites = each.value.rewrites - rules = each.value.rules redirects = each.value.redirects url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + rules = each.value.rules tags = var.tags depends_on = [module.vnet] @@ -206,7 +210,7 @@ module "vmss" { interfaces = [ for v in each.value.interfaces : { name = v.name - subnet_id = module.vnet[each.value.virtual_machine_scale_set.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip pip_domain_name_label = v.pip_domain_name_label lb_backend_pool_ids = try([module.load_balancer[v.load_balancer_key].backend_pool_id], []) @@ -220,5 +224,6 @@ module "vmss" { ) autoscaling_profiles = each.value.autoscaling_profiles - tags = var.tags + tags = var.tags + depends_on = [module.vnet, module.load_balancer, module.appgw] } diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 1d451e72..c085b3f2 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -13,16 +13,17 @@ 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: ``` 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. + **Note!** \ + 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 @@ -30,9 +31,10 @@ variable "name_prefix" { 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. + 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 @@ -76,8 +78,7 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -95,7 +96,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -189,40 +191,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -244,7 +248,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -343,8 +346,6 @@ variable "scale_sets" { Below we present only the most important ones, for the rest please refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from @@ -365,6 +366,8 @@ variable "scale_sets" { the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the metrics to the thresholds + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> interface should be the management one. Following properties are available: - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) @@ -404,17 +407,20 @@ variable "scale_sets" { custom_id = optional(string) }) virtual_machine_scale_set = optional(object({ - vnet_key = string - bootstrap_options = optional(string) - size = optional(string) - zones = optional(list(string)) - disk_type = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool) - platform_fault_domain_count = optional(number) - disk_encryption_set_id = optional(string) - allow_extension_operations = optional(bool) + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) @@ -423,6 +429,7 @@ variable "scale_sets" { notification_emails = optional(list(string), []) webhooks_uris = optional(map(string), {}) }), {}) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -476,105 +483,123 @@ variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF - default = {} - nullable = false type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -582,50 +607,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) -} +} \ No newline at end of file diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 319978e2..42e914c2 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -178,6 +178,7 @@ 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -187,14 +188,12 @@ 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. -[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -269,7 +268,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -350,6 +348,176 @@ map(object({ +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> @@ -404,16 +572,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> -#### enable_zones - -If `true`, enable zone support for resources. - -Type: bool - -Default value: `true` - -<sup>[back to list](#modules-optional-inputs)</sup> - #### natgws @@ -495,42 +653,44 @@ This is a brief description of available properties. For a detailed one please r 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 +- `name` - (`string`, required) a name of the Load Balancer. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. Type: ```hcl map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -552,7 +712,6 @@ map(object({ public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -772,8 +931,6 @@ The most basic properties are as follows: The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -835,6 +992,9 @@ The most basic properties are as follows: For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -844,15 +1004,16 @@ The most basic properties are as follows: The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. @@ -876,7 +1037,6 @@ map(object({ custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -889,18 +1049,20 @@ map(object({ ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -909,173 +1071,8 @@ map(object({ public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) - })) - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - -Following properties are supported: -- `name` - (`string`, required) name of the Application Gateway. -- `public_ip` - (`string`, required) public IP address. -- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. -- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. -- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. -- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) -- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported -- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway -- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. -- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name -- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool -- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) -- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) -- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) -- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) -- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` -- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` -- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` -- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - - -Type: - -```hcl -map(object({ - name = string - public_ip = object({ - name = string - resource_group_name = optional(string) - create = optional(bool, true) - }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ - static = optional(number) - autoscale = optional(object({ - min = optional(number) - max = optional(number) - })) - }) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string, "OWASP") - rule_set_version = optional(string) - })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") - listeners = map(object({ - name = string - port = number - protocol = optional(string, "Http") - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) - })) - backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) - })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - })), {}) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) - match_code = optional(list(number)) - match_body = optional(string) - })), {}) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) - }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) + application_gateway_key = optional(string) })) - redirects = optional(map(object({ - name = string - type = string - target_listener = optional(string) - target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) - url_path_maps = optional(map(object({ - name = string - backend = string - path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) - }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) ``` diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 1a9ae056..e522960b 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -47,11 +47,6 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } - "appgw_blackhole" = { - name = "appgw-blackhole-udr" - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "private" = { @@ -73,11 +68,6 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } - "appgw_blackhole" = { - name = "appgw-blackhole-udr" - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "public" = { @@ -115,10 +105,6 @@ vnets = { network_security_group_key = "public" route_table_key = "public" } - "appgw" = { - name = "appgw-snet" - address_prefixes = ["10.0.0.48/28"] - } } } } @@ -149,11 +135,11 @@ load_balancers = { } } "private" = { - name = "private-lb" + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { name = "private-vmseries" - vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { @@ -168,55 +154,6 @@ load_balancers = { } } - - -# --- APPLICATION GATEWAYs --- # -appgws = { - "public" = { - name = "appgw" - public_ip = { - name = "pip" - } - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = { - static = 2 - } - listeners = { - minimum = { - name = "minimum-listener" - port = 80 - } - } - rewrites = { - minimum = { - name = "minimum-set" - rules = { - "xff-strip-port" = { - name = "minimum-xff-strip-port" - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } - } - rules = { - minimum = { - name = "minimum-rule" - priority = 1 - backend = "minimum" - listener = "minimum" - rewrite = "minimum" - } - } - } -} - - - # --- VMSERIES PART --- # ngfw_metrics = { name = "metrics" @@ -240,9 +177,8 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 1 + size = "Standard_DS3_v2" + zone = 1 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -251,6 +187,7 @@ vmseries = { public_snet_key = "public" } } + vnet_key = "transit" interfaces = [ { name = "vm-in-01-mgmt" @@ -275,9 +212,8 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 2 + size = "Standard_DS3_v2" + zone = 2 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -286,6 +222,7 @@ vmseries = { public_snet_key = "public" } } + vnet_key = "transit" interfaces = [ { name = "vm-in-02-mgmt" @@ -309,9 +246,8 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 1 + size = "Standard_DS3_v2" + zone = 1 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -320,6 +256,7 @@ vmseries = { public_snet_key = "public" } } + vnet_key = "transit" interfaces = [ { name = "vm-obew-01-mgmt" @@ -344,9 +281,8 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 2 + size = "Standard_DS3_v2" + zone = 2 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -355,6 +291,7 @@ vmseries = { public_snet_key = "public" } } + vnet_key = "transit" interfaces = [ { name = "vm-obew-02-mgmt" diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 031912b2..b2a5393d 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -74,7 +74,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) @@ -99,6 +99,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -123,8 +124,8 @@ module "load_balancer" { 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) + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : v.public_ip_name, + subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) } ) } @@ -160,7 +161,7 @@ module "ngfw_metrics" { resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : - k => v.virtual_machine + k => merge(v.virtual_machine, { vnet_key = v.vnet_key }) if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) } @@ -295,7 +296,7 @@ module "vmseries" { interfaces = [for v in each.value.interfaces : { name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name @@ -314,43 +315,59 @@ module "vmseries" { ] } +# Create Application Gateway + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + module "appgw" { source = "../../modules/appgw" for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners - backend_pool = { - name = "vmseries" - vmseries_ips = [ - for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name - ].private_ip_address if try(v.add_to_appgw_backend, false) - ] - } - backends = each.value.backends - probes = each.value.probes - rewrites = each.value.rewrites - rules = each.value.rules - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules tags = var.tags - depends_on = [module.vnet] -} \ No newline at end of file + depends_on = [module.vnet, module.vmseries] +} diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index a0bf9103..d401eaab 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -45,12 +45,6 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - ### VNET @@ -197,40 +191,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -252,7 +248,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -447,8 +442,6 @@ variable "vmseries" { The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -510,6 +503,9 @@ variable "vmseries" { For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -519,15 +515,16 @@ variable "vmseries" { The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. EOF default = {} @@ -549,7 +546,6 @@ variable "vmseries" { custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -562,18 +558,20 @@ variable "vmseries" { ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -582,7 +580,7 @@ variable "vmseries" { public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) + application_gateway_key = optional(string) })) })) validation { @@ -608,105 +606,123 @@ variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF - default = {} - nullable = false type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -714,50 +730,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) -} +} \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/.header.md b/examples/dedicated_vmseries_and_autoscale/.header.md new file mode 100644 index 00000000..57d88b65 --- /dev/null +++ b/examples/dedicated_vmseries_and_autoscale/.header.md @@ -0,0 +1,194 @@ +--- +short_title: Dedicated Firewall Option with Autoscaling +type: refarch +show_in_hub: true +--- +# Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Dedicated Inbound NGFW Option with Autoscaling + +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. + +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +dedicated-inbound VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design +guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented +metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane +utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. Therefore they are not +assigned with public IP addresses. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a +Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). + +## Reference Architecture Design + +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) + +This code implements: + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *dedicated inbound option*, which separates inbound traffic flows onto a separate set of VM-Series +- *auto scaling* for the VM-Series, where Virtual Machine Scale Sets (VMSS) are used to provision VM-Series that will scale in and + out dynamically, as workload demands fluctuate + +## Detailed Architecture and Design + +### Centralized Design + +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a +hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, +east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. + +### Dedicated Inbound Option + +The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series +firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. The second +set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment choice offers +increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting +other traffic flows within the deployment. + +![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) + +This reference architecture consists of: + +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 2 Virtual Machine Scale sets: + - one for inbound, one for outbound and east-west traffic + - with 3 network interfaces: management, public, private + - no public addresses are assigned to firewalls' interfaces +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the public interfaces of the inbound VMSS, for incoming traffic + - private - in front of the firewalls private interfaces of the OBEW VMSS, for outgoing and east-west traffic +- a NAT Gateway responsible for handling the outgoing traffic for the management (updates) and public (outbound traffic in OBEW +- firewalls mainly) interfaces +- 2 Application Insights, one per each scale set, used to store the custom PanOS metrics +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly + +A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's +default mechanism). + +### Auto Scaling VM-Series + +Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference +stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources +allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and +VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload +demands fluctuate. The VM-Series firewalls are deployed in separate Virtual Machine Scale Sets for inbound and outbound/east-west +firewalls, and are automatically registered to Azure Load Balancers. + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) + +A non-platform requirement would be a running Panorama instance. For full automation you might want to consider the following +requirements: + +- a template and a template stack with `DAY0` configuration +- a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + and any security and NAT rules of your choice +- a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices +- a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) + plugin to enable additional template options (custom metrics) + +**Note!** + +- after the deployment the firewalls remain not configured and not licensed. +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. + +## Usage + +### Deployment Steps + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you + might want to also adjust the `bootstrap_options` for each scale set ([inbound](./example.tfvars#L205) and + [obew](./example.tfvars#L249) separately). +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + Apply complete! Resources: 52 added, 0 changed, 0 destroyed. + + Outputs: + + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = <sensitive> + password = <sensitive> + username = "panadmin" + ``` + +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. + +### Post deploy + +The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights +instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: + +```bash +terraform output metrics_instrumentation_keys +``` + +The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom +metrics are being sent to Application Insights and retrieved by Virtual Machine Scale Sets to trigger scale-in and scale-out +operations. + +Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. +To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output username + ``` + +- for password: + + ```bash + terraform output password + ``` + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 165370dd..dcbfe2fd 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -1,209 +1,971 @@ +<!-- BEGIN_TF_DOCS --> --- -short_title: Dedicated Firewall Option with Autoscaling +short\_title: Dedicated Firewall Option with Autoscaling type: refarch -show_in_hub: true +show\_in\_hub: true --- # Reference Architecture with Terraform: VM-Series in Azure, Centralized Architecture, Dedicated Inbound NGFW Option with Autoscaling -Palo Alto Networks produces several [validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with dedicated-inbound VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). +Palo Alto Networks produces several +[validated reference architecture design and deployment documentation guides](https://www.paloaltonetworks.com/resources/reference-architectures), +which describe well-architected and tested deployments. When deploying VM-Series in a public cloud, the reference architectures +guide users toward the best security outcomes, whilst reducing rollout time and avoiding common integration efforts. -Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented metrics published by PAN-OS it is possible to adjust the number of firewall appliances to the current workload (data plane utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. Therefore they are not assigned with public IP addresses. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a Panorama instance is not covered in this example, but a [dedicated one exists](../standalone_panorama/README.md). +The Terraform code presented here will deploy Palo Alto Networks VM-Series firewalls in Azure based on a centralized design with +dedicated-inbound VM-Series with autoscaling(Virtual Machine Scale Sets); for a discussion of other options, please see the design +guide from [the reference architecture guides](https://www.paloaltonetworks.com/resources/reference-architectures). + +Virtual Machine Scale Sets (VMSS) are used for autoscaling to run the Next Generation Firewalls, with custom data plane oriented +metrics published by PanOS it is possible to adjust the number of firewall appliances to the current workload (data plane +utilization). Since firewalls are added or removed automatically, they cannot be managed in a classic way. Therefore they are not +assigned with public IP addresses. To ease licensing, management and updates a Panorama appliance is suggested. Deployment of a +Panorama instance is not covered in this example, but a [dedicated one exists](../standalone\_panorama/README.md). ## Reference Architecture Design ![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) This code implements: -- a _centralized design_, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, east-west, and enterprise traffic -- the _dedicated inbound option_, which separates inbound traffic flows onto a separate set of VM-Series -- _auto scaling_ for the VM-Series, where Virtual Machine Scale Sets (VMSS) are used to provision VM-Series that will scale in and out dynamically, as workload demands fluctuate + +- a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, + east-west, and enterprise traffic +- the *dedicated inbound option*, which separates inbound traffic flows onto a separate set of VM-Series +- *auto scaling* for the VM-Series, where Virtual Machine Scale Sets (VMSS) are used to provision VM-Series that will scale in and + out dynamically, as workload demands fluctuate ## Detailed Architecture and Design ### Centralized Design -This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. +This design uses a Transit VNet. Application functions and resources are deployed across multiple VNets that are connected in a +hub-and-spoke topology. The hub of the topology, or transit VNet, is the central point of connectivity for all inbound, outbound, +east-west, and enterprise traffic. You deploy all VM-Series firewalls within the transit VNet. ### Dedicated Inbound Option -The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. The second set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. +The dedicated inbound option separates traffic flows across two separate sets of VM-Series firewalls. One set of VM-Series +firewalls is dedicated to inbound traffic flows, allowing for greater flexibility and scaling of inbound traffic loads. The second +set of VM-Series firewalls services all outbound, east-west, and enterprise network traffic flows. This deployment choice offers +increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting +other traffic flows within the deployment. -![Dedicated-VM-Series-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) +![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) This reference architecture consists of: -* a VNET containing: - * 4 subnets: - * 3 of them dedicated to the firewalls: management, private and public - * one dedicated to an Application Gateway - * Route Tables and Network Security Groups -* 2 Virtual Machine Scale sets: - * one for inbound, one for outbound and east-west traffic - * with 3 network interfaces: management, public, private - * no public addresses are assigned to firewalls' interfaces -* 2 Load Balancers: - * public - with a public IP address assigned, in front of the public interfaces of the inbound VMSS, for incoming traffic - * private - in front of the firewalls private interfaces of the OBEW VMSS, for outgoing and east-west traffic -* a NAT Gateway responsible for handling the outgoing traffic for the management (updates) and public (outbound traffic in OBEW firewalls mainly) interfaces -* 2 Application Insights, one per each scale set, used to store the custom PAN-OS metrics -* an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly - -A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's default mechanism). +- a VNET containing: + - 4 subnets: + - 3 of them dedicated to the firewalls: management, private and public + - one dedicated to an Application Gateway + - Route Tables and Network Security Groups +- 2 Virtual Machine Scale sets: + - one for inbound, one for outbound and east-west traffic + - with 3 network interfaces: management, public, private + - no public addresses are assigned to firewalls' interfaces +- 2 Load Balancers: + - public - with a public IP address assigned, in front of the public interfaces of the inbound VMSS, for incoming traffic + - private - in front of the firewalls private interfaces of the OBEW VMSS, for outgoing and east-west traffic +- a NAT Gateway responsible for handling the outgoing traffic for the management (updates) and public (outbound traffic in OBEW +- firewalls mainly) interfaces +- 2 Application Insights, one per each scale set, used to store the custom PanOS metrics +- an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly + +A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's +default mechanism). ### Auto Scaling VM-Series -Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload demands fluctuate. The VM-Series firewalls are deployed in separate Virtual Machine Scale Sets for inbound and outbound/east-west firewalls, and are automatically registered to Azure Load Balancers. +Auto scaling: Public-cloud environments focus on scaling out a deployment instead of scaling up. This architectural difference +stems primarily from the capability of public-cloud environments to dynamically increase or decrease the number of resources +allocated to your environment. Using native Azure services like Virtual Machine Scale Sets (VMSS), Application Insights and +VM-Series automation features, the guide implements VM-Series that will scale in and out dynamically, as your protected workload +demands fluctuate. The VM-Series firewalls are deployed in separate Virtual Machine Scale Sets for inbound and outbound/east-west +firewalls, and are automatically registered to Azure Load Balancers. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)) -A non-platform requirement would be a running Panorama instance. For full automation you might want to consider the following requirements: +A non-platform requirement would be a running Panorama instance. For full automation you might want to consider the following +requirements: -* a template and a template stack with `DAY0` configuration -* a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + any security and NAT rules of your choice -* a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices -* a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) plugin to enable additional template options (custom metrics) +- a template and a template stack with `DAY0` configuration +- a device group with security configuration (`DAY1` [iron skillet](https://github.com/PaloAltoNetworks/iron-skillet) for example) + and any security and NAT rules of your choice +- a [Panorama Software Firewall License](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/license-the-vm-series-firewall/use-panorama-based-software-firewall-license-management) plugin to automatically manage licenses on newly created devices +- a [VM-Series](https://docs.paloaltonetworks.com/panorama/9-1/panorama-admin/panorama-plugins/plugins-types/install-the-vm-series-plugin-on-panorama) + plugin to enable additional template options (custom metrics) -**NOTE:** +**Note!** -* after the deployment the firewalls remain not configured and not licensed. -* this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. +- after the deployment the firewalls remain not configured and not licensed. +- this example contains some **files** that **can contain sensitive data**. Keep in mind that **this code** is + **only an example**. It's main purpose is to introduce the Terraform modules. ## Usage ### Deployment Steps -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for each scale set ([inbound](./example.tfvars#L205) and [obew](./example.tfvars#L249) separately). -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you + might want to also adjust the `bootstrap_options` for each scale set ([inbound](./example.tfvars#L205) and + [obew](./example.tfvars#L249) separately). +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - Apply complete! Resources: 52 added, 0 changed, 0 destroyed. + ```console + Apply complete! Resources: 52 added, 0 changed, 0 destroyed. - Outputs: + Outputs: - lb_frontend_ips = { - "private" = { - "ha-ports" = "1.2.3.4" - } - "public" = { - "palo-lb-app1-pip" = "1.2.3.4" - } - } - metrics_instrumentation_keys = <sensitive> - password = <sensitive> - username = "panadmin" + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = <sensitive> + password = <sensitive> + username = "panadmin" + ``` -* at this stage you have to wait couple of minutes for the firewalls to bootstrap. +- at this stage you have to wait couple of minutes for the firewalls to bootstrap. ### Post deploy -The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: +The most important post-deployment action is (for deployments with auto scaling and Panorama) to retrieve the Application Insights +instrumentation keys. This can be done by looking up the AI resources in the Azure portal, or directly from Terraform outputs: -```sh +```bash terraform output metrics_instrumentation_keys ``` -The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom metrics are being sent to Application Insights and retrieved by Virtual Machine Scale Sets to trigger scale-in and scale-out operations. +The retrieved keys should be put into appropriate templates in Panorama and pushed to the devices. From this moment on, custom +metrics are being sent to Application Insights and retrieved by Virtual Machine Scale Sets to trigger scale-in and scale-out +operations. -Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. To retrieve the initial credentials run: +Although firewalls in a Scale Set are not meant to be managed directly, they are still configured with password authentication. +To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output username + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output password + ``` ### Cleanup To remove the deployed infrastructure run: -```sh +```bash terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_natgw"></a> [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| <a name="module_load_balancer"></a> [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| <a name="module_ai"></a> [ai](#module\_ai) | ../../modules/application_insights | n/a | -| <a name="module_appgw"></a> [appgw](#module\_appgw) | ../../modules/appgw | n/a | -| <a name="module_vmss"></a> [vmss](#module\_vmss) | ../../modules/vmss | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_natgws"></a> [natgws](#input\_natgws) | A map defining Nat Gateways. <br><br>Please note that a NatGW is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NatGW's zonal resiliency. <br><br>Following properties are supported:<br><br>- `name` : a name of the newly created NatGW.<br>- `create_natgw` : (default: `true`) create or source (when `false`) an existing NatGW. Created or sourced: the NatGW will be assigned to a subnet created by the `vnet` module.<br>- `resource_group_name : name of a Resource Group hosting the NatGW (newly create or the existing one).<br>- `zone` : Availability Zone in which the NatGW will be placed, when skipped AzureRM will pick a zone.<br>- `idle\_timeout` : connection IDLE timeout in minutes, for newly created resources<br>- `vnet\_key` : a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this NatGW will be assigned to.<br>- `subnet\_keys` : a list of subnets (key values) the NatGW will be assigned to, defined in `var.vnets` for a VNET described by `vnet\_name`.<br>- `create\_pip` : (default: `true`) create a Public IP that will be attached to a NatGW<br>- `existing\_pip\_name` : when `create\_pip` is set to `false`, source and attach and existing Public IP to the NatGW<br>- `existing\_pip\_resource\_group\_name` : when `create\_pip` is set to `false`, name of the Resource Group hosting the existing Public IP<br>- `create\_pip\_prefix` : (default: `false`) create a Public IP Prefix that will be attached to the NatGW.<br>- `pip\_prefix\_length` : length of the newly created Public IP Prefix, can bet between 0 and 31 but this actually supported value depends on the Subscription.<br>- `existing\_pip\_prefix\_name` : when `create\_pip\_prefix` is set to `false`, source and attach and existing Public IP Prefix to the NatGW<br>- `existing\_pip\_prefix\_resource\_group\_name` : when `create\_pip\_prefix` is set to `false`, name of the Resource Group hosting the existing Public IP Prefix.<br><br>Example:<br>`<pre>natgws = {<br> "natgw" = {<br> name = "public-natgw"<br> vnet_key = "transit-vnet"<br> subnet_keys = ["public"]<br> zone = 1<br> }<br>}</pre> | `any` | `{}` | no | -| <a name="input_load_balancers"></a> [load\_balancers](#input\_load\_balancers) | A map containing configuration for all (private and public) Load Balancer that will be created in this deployment.<br><br>Following properties are available (for details refer to module's documentation):<br><br>- `name`: name of the Load Balancer resource.<br>- `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.<br>- `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.<br>- `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).<br>- `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`.<br>- `network_security_allow_source_ips`: (public LB) a list of IP addresses that will used in the ingress rules.<br>- `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).<br>- `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:<br> - `create_public_ip`: (public LB) defaults to `false`, when set to `true` a Public IP will be created and associated with a listener<br> - `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<br> - `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<br> - `private_ip_address`: (private LB) defaults to `null`, specify a static IP address that will be used by a listener<br> - `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<br> - `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 VM-Series this should be a internal/trust subnet<br> - `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:<br> - `protocol`: protocol used by the rule, can be one the following: `TCP`, `UDP` or `All` when creating an HA PORTS rule<br> - `port`: port used by the rule, for HA PORTS rule set this to `0`<br><br>Example of a public Load Balancer:<pre>"public_lb" = {<br> name = "https_app_lb"<br> network_security_group_name = "untrust_nsg"<br> network_security_allow_source_ips = ["1.2.3.4"]<br> avzones = ["1", "2", "3"]<br> frontend_ips = {<br> "https_app_1" = {<br> create_public_ip = true<br> rules = {<br> "balanceHttps" = {<br> protocol = "Tcp"<br> port = 443<br> }<br> }<br> }<br> }<br>}</pre>Example of a private Load Balancer with HA PORTS rule:<pre>"private_lb" = {<br> name = "ha_ports_internal_lb<br> frontend_ips = {<br> "ha-ports" = {<br> vnet_key = "trust_vnet"<br> subnet_key = "trust_snet"<br> private_ip_address = "10.0.0.1"<br> rules = {<br> HA_PORTS = {<br> port = 0<br> protocol = "All"<br> }<br> }<br> }<br> }<br>}</pre> | `map` | `{}` | no | -| <a name="input_application_insights"></a> [application\_insights](#input\_application\_insights) | A map defining Azure Application Insights. There are three ways to use this variable:<br><br>* when the value is set to `null` (default) no AI is created<br>* when the value is a map containing `name` key (other keys are optional) a single AI instance will be created under the name that is the value of the `name` key<br>* when the value is an empty map or a map w/o the `name` key, an AI instance per each VM-Series VM will be created. All instances will share the same configuration. All instances will have names corresponding to their VM name.<br><br>Names for all AI instances are prefixed with `var.name_prefix`.<br><br>Properties supported (for details on each property see [modules documentation](../../modules/application\_insights/README.md)):<br><br>- `name` : (optional, string) a name of a single AI instance<br>- `workspace_mode` : (optional, bool) defaults to `true`, use AI Workspace mode instead of the Classical (deprecated)<br>- `workspace_name` : (optional, string) defaults to AI name suffixed with `-wrkspc`, name of the Log Analytics Workspace created when AI is deployed in Workspace mode<br>- `workspace_sku` : (optional, string) defaults to PerGB2018, SKU used by WAL, see module documentation for details<br>- `metrics_retention_in_days` : (optional, number) defaults to current Azure default value, see module documentation for details<br><br>Example of an AIs created per VM, in Workspace mode, with metrics retention set to 1 year:<pre>vmseries = {<br> 'vm-1' = {<br> ....<br> }<br> 'vm-2' = {<br> ....<br> }<br>}<br><br>application_insights = {<br> metrics_retention_in_days = 365<br>}</pre> | `map(string)` | `null` | no | -| <a name="input_vmseries_version"></a> [vmseries\_version](#input\_vmseries\_version) | VM-Series PAN-OS version - list available with `az vm image list -o table --all --publisher paloaltonetworks`. It's also possible to specify the Pan-OS version per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | -| <a name="input_vmseries_vm_size"></a> [vmseries\_vm\_size](#input\_vmseries\_vm\_size) | Azure VM size (type) to be created. Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. It's also possible to specify the the VM size per Scale Set, see `var.vmss` variable. | `string` | n/a | yes | -| <a name="input_vmseries_sku"></a> [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_vmss"></a> [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)<br><br>Following properties are available:<br>- `name` : (string\|required) name of the Virtual Machine Scale Set.<br>- `vm_size` : size of the VM-Series virtual machines created with this Scale Set, when specified overrides `var.vmseries_vm_size`.<br>- `version` : PAN-OS version, when specified overrides `var.vmseries_version`.<br>- `vnet_key` : (string\|required) a key of a VNET defined in the `var.vnets` map.<br>- `bootstrap_options` : (string\|`''`) bootstrap options passed to every VM instance upon creation.<br>- `zones` : (list(string)\|`[]`) a list of Availability Zones to use for Zone redundancy<br>- `encryption_at_host_enabled` : (bool\|`null` - Azure defaults) should all of the disks attached to this Virtual Machine be encrypted<br>- `overprovision` : (bool\|`null` - module defaults) when provisioning new VM, multiple will be provisioned but the 1st one to run will be kept<br>- `platform_fault_domain_count` : (number\|`null` - Azure defaults) number of fault domains to use<br>- `proximity_placement_group_id` : (string\|`null`) ID of a proximity placement group the VMSS should be placed in<br>- `scale_in_policy` : (string\|`null` - Azure defaults) policy of removing VMs when scaling in<br>- `scale_in_force_deletion` : (bool\|`null` - module default) forces (`true`) deletion of VMs during scale in<br>- `single_placement_group` : (bool\|`null` - Azure defaults) limit the Scale Set to one Placement Group<br>- `storage_account_type` : (string\|`null` - module defaults) type of managed disk that will be used on all VMs<br>- `disk_encryption_set_id` : (string\|`null`) the ID of the Disk Encryption Set which should be used to encrypt this Data Disk<br>- `accelerated_networking` : (bool\|`null`- module defaults) enable Azure accelerated networking for all dataplane network interfaces<br>- `use_custom_image` : (bool\|`false`) <br>- `custom_image_id` : (string\|reqquired when `use_custom_image` is `true`) absolute ID of your own Custom Image to be used for creating new VM-Series<br>- `application_insights_id` : (string\|`null`) ID of Application Insights instance that should be used to provide metrics for autoscaling<br>- `interfaces` : (list(string)\|`[]`) configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name` : (string\|required) string that will form the NIC name<br> - `subnet_key` : (string\|required) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (bool\|`false`) flag to create Public IP for an interface, defaults to `false`<br> - `load_balancer_key` : (string\|`null`) key of a Load Balancer defined in the `var.loadbalancers` variable<br> - `application_gateway_key` : (string\|`null`) key of an Application Gateway defined in the `var.appgws`<br> - `pip_domain_name_label` : (string\|`null`) prefix which should be used for the Domain Name Label for each VM instance<br>- `autoscale_config` : (map\|`{}`) map containing basic autoscale configuration<br> - `count_default` : (number\|`null` - module defaults) default number or instances when autoscalling is not available<br> - `count_minimum` : (number\|`null` - module defaults) minimum number of instances to reach when scaling in<br> - `count_maximum` : (number\|`null` - module defaults) maximum number of instances when scaling out<br> - `notification_emails` : (list(string)\|`null` - module defaults) a list of e-mail addresses to notify about scaling events<br>- `autoscale_metrics` : (map\|`{}`) metrics and thresholds used to trigger scaling events, see module documentation for details<br>- `scaleout_config` : (map\|`{}`) scale out configuration, for details see module documentation<br> - `statistic` : (string\|`null` - module defaults) aggregation method for statistics coming from different VMs<br> - `time_aggregation` : (string\|`null` - module defaults) aggregation method applied to statistics in time window<br> - `window_minutes` : (string\|`null` - module defaults) time windows used to analyze statistics<br> - `cooldown_minutes` : (string\|`null` - module defaults) time to wait after a scaling event before analyzing the statistics again<br>- `scalein_config` : (map\|`{}`) scale in configuration, same properties supported as for `scaleout_config`<br><br>Example, no auto scaling:<pre>{<br>"vmss" = {<br> name = "ngfw-vmss"<br> vnet_key = "transit"<br> bootstrap_options = "type=dhcp-client"<br><br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "management"<br> },<br> {<br> name = "private"<br> subnet_key = "private"<br> },<br> {<br> name = "public"<br> subnet_key = "public"<br> load_balancer_key = "public"<br> application_gateway_key = "public"<br> }<br> ]<br>}</pre> | `any` | `{}` | no | -| <a name="input_appgws"></a> [appgws](#input\_appgws) | A map defining all Application Gateways in the current deployment.<br><br>For detailed documentation on how to configure this resource, for available properties, especially for the defaults and the `rules` property refer to [module documentation](../../modules/appgw/README.md).<br><br>Following properties are supported:<br>- `name` : name of the Application Gateway.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `vnet_key` : a key of a VNET defined in the `var.vnets` map.<br>- `subnet_key` : a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2.<br>- `zones` : for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW.<br>- `capacity` : (optional) number of Application Gateway instances, not used when autoscalling is enabled (see `capacity_min`)<br>- `capacity_min` : (optional) when set enables autoscaling and becomes the minimum capacity<br>- `capacity_max` : (optional) maximum capacity for autoscaling<br>- `enable_http2` : enable HTTP2 support on the Application Gateway<br>- `waf_enabled` : (optional) enables WAF Application Gateway, defining WAF rules is not supported, defaults to `false`<br>- `vmseries_public_nic_name` : name of the public VM-Series interface as defined in `interfaces` property.<br>- `managed_identities` : (optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault<br>- `ssl_policy_type` : (optional) type of an SSL policy, defaults to `Predefined`<br>- `ssl_policy_name` : (optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined`<br>- `ssl_policy_min_protocol_version` : (optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom`<br>- `ssl_policy_cipher_suites` : (optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom`<br>- `ssl_profiles` : (optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_metrics_instrumentation_keys"></a> [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| <a name="output_lb_frontend_ips"></a> [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. + + +## 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. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial firewall administrative usernames for all deployed Scale Sets. +`passwords` | Initial firewall administrative passwords for all deployed Scale Sets. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. + +## Module's Nameplate + + + + +Providers used in this module: + +- `random` +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`appgw` | - | ../../modules/appgw | +`vmss` | - | ../../modules/vmss | + + +Resources used in this module: + +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### natgws + +A map defining NAT Gateways. + +Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one +explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. +For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + +Following properties are supported: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### load_balancers + +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. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. +- `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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. +- `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, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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_name = 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### ngfw_metrics + +A map controlling metrics-relates resources. + +When set to explicit `null` (default) it will disable any metrics resources in this deployment. + +When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each +Scale Set). All instances will be automatically connected to the workspace. +The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + +All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + +Following properties are available: + +- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace +- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace +- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### scale_sets + +A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + +For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. + +The basic Scale Set configuration properties are as follows: + +- `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` +- `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. + + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. + + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: + + - do not specify anything else - a random password will be generated for you + - specify at least one of `password` or `ssh_keys` properties. + + For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image + + For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). + +- `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set + configuration options. + + Below we present only the most important ones, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): + + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported + - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from + this Scale Set will be created + - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values) + - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series + instance + +- `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics thresholds, etc) + + Below we present only the most important properties, for the rest please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in + the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare + the metrics to the thresholds + +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set +- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. Following properties are available: + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets` + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be + added to the Load Balancer's backend pool + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance + +- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + configuration please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) + + + +Type: + +```hcl +map(object({ + name = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + vnet_key = string + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index abac7db7..9fd03fde 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -47,11 +47,6 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } - "appgw_blackhole" = { - name = "appgw-blackhole-udr" - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "private" = { @@ -73,11 +68,6 @@ vnets = { address_prefix = "10.0.0.32/28" next_hop_type = "None" } - "appgw_blackhole" = { - name = "appgw-blackhole-udr" - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "public" = { @@ -115,10 +105,6 @@ vnets = { network_security_group_key = "public" route_table_key = "public" } - "appgw" = { - name = "appgw-snet" - address_prefixes = ["10.0.0.48/28"] - } } } } @@ -141,8 +127,10 @@ natgws = { # --- LOAD BALANCING PART --- # load_balancers = { "public" = { - name = "public-lb" - zones = null + name = "public-lb" + load_balancer = { + zones = null + } nsg_auto_rules_settings = { nsg_vnet_key = "transit" nsg_key = "public" @@ -164,12 +152,14 @@ load_balancers = { } } "private" = { - name = "private-lb" - zones = null + name = "private-lb" + load_balancer = { + zones = null + } + vnet_key = "transit" frontend_ips = { "ha-ports" = { name = "private-vmseries" - vnet_key = "transit" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { @@ -199,13 +189,13 @@ scale_sets = { disable_password_authentication = false } virtual_machine_scale_set = { - vnet_key = "transit" bootstrap_options = "type=dhcp-client" zones = null } autoscaling_configuration = { default_count = 2 } + vnet_key = "transit" interfaces = [ { name = "management" @@ -231,13 +221,13 @@ scale_sets = { disable_password_authentication = false } virtual_machine_scale_set = { - vnet_key = "transit" bootstrap_options = "type=dhcp-client" zones = null } autoscaling_configuration = { default_count = 2 } + vnet_key = "transit" interfaces = [ { name = "management" diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index bea154c9..4f7c5f84 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -74,7 +74,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) @@ -98,6 +98,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -122,8 +123,8 @@ module "load_balancer" { 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) + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : v.public_ip_name, + subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) } ) } @@ -162,29 +163,32 @@ module "appgw" { for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners - backends = each.value.backends + backend_pool = each.value.backend_pool + backend_settings = each.value.backend_settings probes = each.value.probes rewrites = each.value.rewrites - rules = each.value.rules redirects = each.value.redirects url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + rules = each.value.rules tags = var.tags depends_on = [module.vnet] @@ -206,7 +210,7 @@ module "vmss" { interfaces = [ for v in each.value.interfaces : { name = v.name - subnet_id = module.vnet[each.value.virtual_machine_scale_set.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip pip_domain_name_label = v.pip_domain_name_label lb_backend_pool_ids = try([module.load_balancer[v.load_balancer_key].backend_pool_id], []) @@ -220,5 +224,6 @@ module "vmss" { ) autoscaling_profiles = each.value.autoscaling_profiles - tags = var.tags + tags = var.tags + depends_on = [module.vnet, module.load_balancer, module.appgw] } diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 1d451e72..5c90903a 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -13,16 +13,17 @@ 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: ``` 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. + **Note!** \ + 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 @@ -30,9 +31,10 @@ variable "name_prefix" { 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. + 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 @@ -76,8 +78,7 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -95,7 +96,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -189,40 +191,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -244,7 +248,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -343,8 +346,6 @@ variable "scale_sets" { Below we present only the most important ones, for the rest please refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from @@ -365,6 +366,8 @@ variable "scale_sets" { the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare the metrics to the thresholds + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets + used to deploy network interfaces for VMs in this Scale Set - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> interface should be the management one. Following properties are available: - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) @@ -404,17 +407,20 @@ variable "scale_sets" { custom_id = optional(string) }) virtual_machine_scale_set = optional(object({ - vnet_key = string - bootstrap_options = optional(string) - size = optional(string) - zones = optional(list(string)) - disk_type = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool) - platform_fault_domain_count = optional(number) - disk_encryption_set_id = optional(string) - allow_extension_operations = optional(bool) + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) @@ -423,6 +429,7 @@ variable "scale_sets" { notification_emails = optional(list(string), []) webhooks_uris = optional(map(string), {}) }), {}) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -476,105 +483,123 @@ variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF - default = {} - nullable = false type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -582,50 +607,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) } diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index c8fd8993..edc98d0c 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -318,42 +318,44 @@ This is a brief description of available properties. For a detailed one please r 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 +- `name` - (`string`, required) a name of the Load Balancer. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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, two other properties were introduced instead. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. Type: ```hcl map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -375,7 +377,6 @@ map(object({ public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -808,5 +809,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index b6026240..820b29d2 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -77,6 +77,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -101,7 +102,7 @@ module "load_balancer" { 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}", + 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) } diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index 23d3690d..0528cb25 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -126,40 +126,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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, two other properties were introduced instead. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -181,7 +183,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) diff --git a/examples/standalone_panorama/.header.md b/examples/standalone_panorama/.header.md new file mode 100644 index 00000000..39b84dd9 --- /dev/null +++ b/examples/standalone_panorama/.header.md @@ -0,0 +1,120 @@ +--- +short_title: Standalone Panorama Deployment +type: example +show_in_hub: true +--- +# Standalone Panorama Deployment + +Panorama is a centralized management system that provides global visibility and control over multiple Palo Alto Networks Next +Generation Firewalls through an easy to use web-based interface. Panorama enables administrators to view aggregate or +device-specific application, user, and content data and manage multiple Palo Alto Networks firewalls — all from a central +location. + +The Terraform code presented here will deploy Palo Alto Networks Panorama management platform in Azure in management only mode +(without additional logging disks). For option on how to add additional logging disks - please refer to panorama +[module documentation](../../modules/panorama/README.md#input_logging_disks). + +## Topology + +This is a non zonal deployment. The deployed infrastructure consists of: + +- a VNET containing: + - one subnet dedicated to host Panorama appliances + - a Network Security Group to give access to Panorama's public interface +- a Panorama appliance with a public IP assigned to the management interface + +![standalone-panorama](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/a2394f73-c0a8-4878-8693-825356abbd23) + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto Networks Panorama images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/panorama/README.md#accept-azure-marketplace-terms)) + +**Note!** + +- after the deployment Panorama remains not licensed and not configured. +- keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. + +## Usage + +### Deployment Steps + +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: + + ```bash + terraform init + ``` + +- (optional) plan you infrastructure to see what will be actually deployed: + + ```bash + terraform plan + ``` + +- deploy the infrastructure (you will have to confirm it with typing in `yes`): + + ```bash + terraform apply + ``` + + The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: + + ```console + Apply complete! Resources: 10 added, 0 changed, 0 destroyed. + + Outputs: + + panorama_mgmt_ips = { + "pn-1" = "1.2.3.4" + } + password = <sensitive> + username = "panadmin" + ``` + +- at this stage you have to wait couple of minutes for the Panorama to bootstrap. + +### Post deploy + +Panorama in this example is configured with password authentication. To retrieve the initial credentials run: + +- for username: + + ```bash + terraform output username + ``` + +- for password: + + ```bash + terraform output password + ``` + +The management public IP addresses are available in the `panorama_mgmt_ips`: + +```bash +terraform output panorama_mgmt_ips +``` + +You can now login to the devices using either: + +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +You can now proceed with licensing and configuring the devices. + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` \ No newline at end of file diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 585e0bdb..d629bcf8 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -1,22 +1,28 @@ +<!-- BEGIN_TF_DOCS --> --- -short_title: Standalone Panorama Deployment +short\_title: Standalone Panorama Deployment type: example -show_in_hub: true +show\_in\_hub: true --- # Standalone Panorama Deployment -Panorama is a centralized management system that provides global visibility and control over multiple Palo Alto Networks next generation firewalls through an easy to use web-based interface. Panorama enables administrators to view aggregate or device-specific application, user, and content data and manage multiple Palo Alto Networks firewalls—all from a central location. +Panorama is a centralized management system that provides global visibility and control over multiple Palo Alto Networks Next +Generation Firewalls through an easy to use web-based interface. Panorama enables administrators to view aggregate or +device-specific application, user, and content data and manage multiple Palo Alto Networks firewalls — all from a central +location. -The Terraform code presented here will deploy Palo Alto Networks Panorama management platform in Azure in management only mode (without additional logging disks). For option on how to add additional logging disks - please refer to panorama [module documentation](../../modules/panorama/README.md#input_logging_disks) +The Terraform code presented here will deploy Palo Alto Networks Panorama management platform in Azure in management only mode +(without additional logging disks). For option on how to add additional logging disks - please refer to panorama +[module documentation](../../modules/panorama/README.md#input\_logging\_disks). ## Topology This is a non zonal deployment. The deployed infrastructure consists of: -* a VNET containing: - * one subnet dedicated to host Panorama appliances - * a Network Security Group to give access to Panorama's public interface -* a Panorama appliance with a public IP assigned to the management interface +- a VNET containing: + - one subnet dedicated to host Panorama appliances + - a Network Security Group to give access to Panorama's public interface +- a Panorama appliance with a public IP assigned to the management interface ![standalone-panorama](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/a2394f73-c0a8-4878-8693-825356abbd23) @@ -24,70 +30,85 @@ This is a non zonal deployment. The deployed infrastructure consists of: A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -* (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) -* if you have not run Palo Alto Networks Panorama images in a subscription it might be necessary to accept the license first ([see this note](../../modules/panorama/README.md#accept-azure-marketplace-terms)) +- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto Networks Panorama images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/panorama/README.md#accept-azure-marketplace-terms)) -**NOTE:** +**Note!** -* after the deployment Panorama remains not licensed and not configured. -* keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. +- after the deployment Panorama remains not licensed and not configured. +- keep in mind that **this code** is **only an example**. It's main purpose is to introduce the Terraform modules. ## Usage ### Deployment Steps -* checkout the code locally (if you haven't done so yet) -* copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -* (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary -* initialize the Terraform module: +- checkout the code locally (if you haven't done so yet) +- copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer + look at the `TODO` markers) +- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- initialize the Terraform module: - terraform init + ```bash + terraform init + ``` -* (optional) plan you infrastructure to see what will be actually deployed: +- (optional) plan you infrastructure to see what will be actually deployed: - terraform plan + ```bash + terraform plan + ``` -* deploy the infrastructure (you will have to confirm it with typing in `yes`): +- deploy the infrastructure (you will have to confirm it with typing in `yes`): - terraform apply + ```bash + terraform apply + ``` The deployment takes couple of minutes. Observe the output. At the end you should see a summary similar to this: - Apply complete! Resources: 10 added, 0 changed, 0 destroyed. + ```console + Apply complete! Resources: 10 added, 0 changed, 0 destroyed. - Outputs: + Outputs: - panorama_mgmt_ips = { - "pn-1" = "1.2.3.4" - } - password = <sensitive> - username = "panadmin" + panorama_mgmt_ips = { + "pn-1" = "1.2.3.4" + } + password = <sensitive> + username = "panadmin" + ``` -* at this stage you have to wait couple of minutes for the Panorama to bootstrap. +- at this stage you have to wait couple of minutes for the Panorama to bootstrap. ### Post deploy Panorama in this example is configured with password authentication. To retrieve the initial credentials run: -* for username: +- for username: - terraform output username + ```bash + terraform output username + ``` -* for password: +- for password: - terraform output password + ```bash + terraform output password + ``` The management public IP addresses are available in the `panorama_mgmt_ips`: -```sh +```bash terraform output panorama_mgmt_ips ``` You can now login to the devices using either: -* cli - ssh client is required -* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. +- cli - ssh client is required +- Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. You can now proceed with licensing and configuring the devices. @@ -95,63 +116,376 @@ You can now proceed with licensing and configuring the devices. To remove the deployed infrastructure run: -```sh +```bash terraform destroy ``` -## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_panorama"></a> [panorama](#module\_panorama) | ../../modules/panorama | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to . | `string` | n/a | yes | -| <a name="input_enable_zones"></a> [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs. A key is the VNET name, value is a set of properties like described below.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : a name of a Virtual Network<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_vmseries_username"></a> [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| <a name="input_vmseries_password"></a> [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| <a name="input_panorama_version"></a> [panorama\_version](#input\_panorama\_version) | Panorama PAN-OS version. It's also possible to specify the Pan-OS version per Panorama (in case you would like to deploy more than one), see `var.panoramas` variable. | `string` | n/a | yes | -| <a name="input_panorama_sku"></a> [panorama\_sku](#input\_panorama\_sku) | Panorama SKU, basically a type of licensing used in Azure. | `string` | `"byol"` | no | -| <a name="input_panorama_size"></a> [panorama\_size](#input\_panorama\_size) | A size of a VM that will run Panorama. It's also possible to specify the the VM size per Panorama, see `var.panoramas` variable. | `string` | `"Standard_D5_v2"` | no | -| <a name="input_panoramas"></a> [panoramas](#input\_panoramas) | A map containing Panorama definitions.<br><br>All definitions share a VM size, SKU and PAN-OS version (`panorama_size`, `panorama_sku`, `panorama_version` respectively). Defining more than one Panorama makes sense when creating for example HA pairs. <br><br>Following properties are available:<br><br>- `name` : a name of a Panorama VM<br>- `size` : size of the Panorama virtual machine, when specified overrides `var.panorama_size`.<br>- `version` : PAN-OS version, when specified overrides `var.panorama_version`.<br>- `vnet_key`: a VNET used to host Panorama VM, this is a key from a VNET definition stored in `vnets` variable<br>- `subnet_key`: a Subnet inside a VNET used to host Panorama VM, this is a key from a Subnet definition stored inside a VNET definition references by the `vnet_key` property<br>- `avzone`: when `enable_zones` is `true` this specifies the zone in which Panorama will be deployed<br>- `avzones`: when `enable_zones` is `true` these are availability zones used by Panorama's public IPs<br>- `custom_image_id`: a custom build of Panorama to use, overrides the stock image version.<br><br>- `interfaces` : configuration of all NICs assigned to a VM. A list of maps, each map is a NIC definition. Notice that the order DOES matter. NICs are attached to VMs in Azure in the order they are defined in this list, therefore the management interface has to be defined first. Following properties are available:<br> - `name`: string that will form the NIC name<br> - `subnet_key` : (string) a key of a subnet as defined in `var.vnets`<br> - `create_pip` : (boolean) flag to create Public IP for an interface, defaults to `false`<br> - `public_ip_name` : (string) when `create_pip` is set to `false` a name of a Public IP resource that should be associated with this Network Interface<br> - `public_ip_resource_group` : (string) when associating an existing Public IP resource, name of the Resource Group the IP is placed in, defaults to the `var.resource_group_name`<br> - `private_ip_address` : (string) a static IP address that should be assigned to an interface, defaults to `null` (in that case DHCP is used)<br><br>- `logging_disks` : a map containing configuration of additional disks that should be attached to a Panorama appliance. Following properties are available:<br> - `size` : size of a disk, 2TB by default<br> - `lun` : slot to which the disk should be attached<br> - `disk_type` : type of a disk, determines throughput, `Standard_LRS` by default.<br><br>Example:<pre>{<br> "pn-1" = {<br> name = "panorama01"<br> vnet_key = "vnet"<br> interfaces = [<br> {<br> name = "management"<br> subnet_key = "panorama"<br> private_ip_address = "10.1.0.10"<br> create_pip = true<br> },<br> ]<br> }<br> }</pre> | `any` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Initial administrative username to use for VM-Series. | -| <a name="output_password"></a> [password](#output\_password) | Initial administrative password to use for VM-Series. | -| <a name="output_panorama_mgmt_ips"></a> [panorama\_mgmt\_ips](#output\_panorama\_mgmt\_ips) | n/a | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +## 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. +[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`panoramas`](#panoramas) | `map` | A map defining Azure Virtual Machine based on Palo Alto Networks Panorama image. + + + +## Module's Outputs + +Name | Description +--- | --- +`username` | Initial administrative username to use for VM-Series. +`password` | Initial administrative password to use for VM-Series. +`panorama_mgmt_ips` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.2, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`panorama` | - | ../../modules/panorama | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +name_prefix = "test-" +``` + +**Note!** \ +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### enable_zones + +If `true`, enable zone support for resources. + +Type: bool + +Default value: `true` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### availability_sets + +A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + +Following properties are supported: +- `name` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially small ones, w/o any Availability Zones). +Please verify how many update and fault domains are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### panoramas + +A map defining Azure Virtual Machine based on Palo Alto Networks Panorama image. + +For details and defaults for available options please refer to the [`panorama`](../../modules/panorama/README.md) module. + +The basic Panorama VM configuration properties are as follows: + +- `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + Following properties are available: + + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. +- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> + interface should be the management one. + + Following properties are available: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). + +- `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. + + Following properties are available: + + - `name` - (`string`, required) the Managed Disk name. + - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). + + +Type: + +```hcl +map(object({ + name = string + authentication = object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + size = optional(string) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + }) + vnet_key = string + interfaces = list(object({ + name = string + subnet_key = string + private_ip_address = optional(string) + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + })) + logging_disks = optional(map(object({ + name = string + size = optional(string) + lun = string + disk_type = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 2693c794..3d32e6cd 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -57,11 +57,11 @@ panoramas = { version = "10.2.3" } virtual_machine = { - vnet_key = "vnet" size = "Standard_D5_v2" zone = null disk_name = "panorama-os-disk" } + vnet_key = "vnet" interfaces = [ { name = "management" diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index 9b8992a4..d12684b4 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -102,7 +102,7 @@ module "panorama" { interfaces = [for v in each.value.interfaces : { name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${each.value.name}-pip")}" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 571792d0..3e91c4cb 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -173,8 +173,6 @@ variable "panoramas" { Following properties are available: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. @@ -183,6 +181,8 @@ variable "panoramas" { For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> interface should be the management one. @@ -223,7 +223,6 @@ variable "panoramas" { custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) zone = string disk_type = optional(string) @@ -235,6 +234,7 @@ variable "panoramas" { identity_type = optional(string) identity_ids = optional(list(string)) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 63d639fe..f6296ff3 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -118,6 +118,7 @@ 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. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -127,14 +128,12 @@ 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. -[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -209,7 +208,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -290,6 +288,176 @@ map(object({ +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> @@ -344,16 +512,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> -#### enable_zones - -If `true`, enable zone support for resources. - -Type: bool - -Default value: `true` - -<sup>[back to list](#modules-optional-inputs)</sup> - #### natgws @@ -435,42 +593,44 @@ This is a brief description of available properties. For a detailed one please r 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 +- `name` - (`string`, required) a name of the Load Balancer. +- `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`. +- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. +- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. Type: ```hcl map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -492,7 +652,6 @@ map(object({ public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -712,8 +871,6 @@ The most basic properties are as follows: The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -775,6 +932,9 @@ The most basic properties are as follows: For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -784,15 +944,16 @@ The most basic properties are as follows: The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. @@ -816,7 +977,6 @@ map(object({ custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -829,18 +989,20 @@ map(object({ ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -849,173 +1011,8 @@ map(object({ public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) - })) - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). - -Following properties are supported: -- `name` - (`string`, required) name of the Application Gateway. -- `public_ip` - (`string`, required) public IP address. -- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. -- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. -- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. -- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) -- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported -- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway -- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. -- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name -- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool -- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) -- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) -- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) -- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) -- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) -- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) -- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` -- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` -- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` -- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property - - -Type: - -```hcl -map(object({ - name = string - public_ip = object({ - name = string - resource_group_name = optional(string) - create = optional(bool, true) - }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ - static = optional(number) - autoscale = optional(object({ - min = optional(number) - max = optional(number) - })) - }) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string, "OWASP") - rule_set_version = optional(string) - })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") - listeners = map(object({ - name = string - port = number - protocol = optional(string, "Http") - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) - })) - backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) - })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - })), {}) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) - match_code = optional(list(number)) - match_body = optional(string) - })), {}) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) - }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) + application_gateway_key = optional(string) })) - redirects = optional(map(object({ - name = string - type = string - target_listener = optional(string) - target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) - url_path_maps = optional(map(object({ - name = string - backend = string - path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) - }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) ``` diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 2fe69507..972460b8 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -50,9 +50,9 @@ vmseries = { } virtual_machine = { bootstrap_options = "type=dhcp-client" - vnet_key = "transit" zone = null } + vnet_key = "transit" interfaces = [ { name = "vm-mgmt" diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 031912b2..7335a3ba 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -74,7 +74,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) location = var.location zone = try(each.value.zone, null) @@ -99,6 +99,7 @@ module "load_balancer" { location = var.location resource_group_name = local.resource_group.name zones = each.value.zones + backend_name = each.value.backend_name health_probes = each.value.health_probes @@ -123,8 +124,8 @@ module "load_balancer" { 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) + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : v.public_ip_name, + subnet_id = try(module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key], null) } ) } @@ -295,7 +296,7 @@ module "vmseries" { interfaces = [for v in each.value.interfaces : { name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name @@ -314,43 +315,59 @@ module "vmseries" { ] } +# Create Application Gateway + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + module "appgw" { source = "../../modules/appgw" for_each = var.appgws - name = each.value.name - public_ip = each.value.public_ip + name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name location = var.location subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - managed_identities = each.value.managed_identities - capacity = each.value.capacity - waf = each.value.waf - enable_http2 = each.value.enable_http2 - zones = each.value.zones - + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles frontend_ip_configuration_name = each.value.frontend_ip_configuration_name listeners = each.value.listeners - backend_pool = { - name = "vmseries" - vmseries_ips = [ - for k, v in var.vmseries : module.vmseries[k].interfaces[ - "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name - ].private_ip_address if try(v.add_to_appgw_backend, false) - ] - } - backends = each.value.backends - probes = each.value.probes - rewrites = each.value.rewrites - rules = each.value.rules - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - - ssl_global = each.value.ssl_global - ssl_profiles = each.value.ssl_profiles + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules tags = var.tags - depends_on = [module.vnet] -} \ No newline at end of file + depends_on = [module.vnet, module.vmseries] +} diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index a0bf9103..d401eaab 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -45,12 +45,6 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool -} - ### VNET @@ -197,40 +191,42 @@ variable "load_balancers" { 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 + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + configurations. + - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - `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 + 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` + in the `var.vnets` map that stores the NSG described by `nsg_key`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map. - `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. + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been 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` + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. EOF default = {} nullable = false type = map(object({ - name = string - zones = optional(list(string), ["1", "2", "3"]) + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) health_probes = optional(map(object({ name = string protocol = string @@ -252,7 +248,6 @@ variable "load_balancers" { public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - vnet_key = optional(string) subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) @@ -447,8 +442,6 @@ variable "vmseries" { The most often used option are as follows: - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) @@ -510,6 +503,9 @@ variable "vmseries" { For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `interfaces` - (`list`, required) configuration of all network interfaces **Note!** \ @@ -519,15 +515,16 @@ variable "vmseries" { The most important ones are listed below: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load + Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. EOF default = {} @@ -549,7 +546,6 @@ variable "vmseries" { custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -562,18 +558,20 @@ variable "vmseries" { ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) + vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -582,7 +580,7 @@ variable "vmseries" { public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - add_to_appgw_backend = optional(bool, false) + application_gateway_key = optional(string) })) })) validation { @@ -608,105 +606,123 @@ variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). - Following properties are supported: - - `name` - (`string`, required) name of the Application Gateway. - - `public_ip` - (`string`, required) public IP address. - - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. - - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. - - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. - - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) - - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported - - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway - - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. - - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name - - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool - - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) - - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) - - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) - - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) - - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) - - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) - - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` - - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` - - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. EOF - default = {} - nullable = false type = map(object({ - name = string + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) public_ip = object({ name = string - resource_group_name = optional(string) create = optional(bool, true) + resource_group_name = optional(string) }) - vnet_key = string - subnet_key = string - managed_identities = optional(list(string)) - capacity = object({ + domain_name_label = optional(string) + capacity = optional(object({ static = optional(number) autoscale = optional(object({ - min = optional(number) - max = optional(number) + min = number + max = number })) - }) + })) + enable_http2 = optional(bool) waf = optional(object({ prevention_mode = bool - rule_set_type = optional(string, "OWASP") + rule_set_type = optional(string) rule_set_version = optional(string) })) - enable_http2 = optional(bool) - zones = list(string) - frontend_ip_configuration_name = optional(string, "public_ipconfig") - vmseries_public_nic_name = optional(string, "public") + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) listeners = map(object({ name = string port = number - protocol = optional(string, "Http") + protocol = optional(string) host_names = optional(list(string)) ssl_profile_name = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string), {}) + custom_error_pages = optional(map(string)) })) backend_pool = optional(object({ - name = string - vmseries_ips = optional(list(string), []) + name = optional(string) + vmseries_ips = optional(list(string)) })) - backends = optional(map(object({ - name = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) root_certs = optional(map(object({ name = string path = string - })), {}) + }))) }))) probes = optional(map(object({ name = string path = string host = optional(string) port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) match_code = optional(list(number)) match_body = optional(string) - })), {}) + }))) rewrites = optional(map(object({ name = optional(string) rules = optional(map(object({ @@ -714,50 +730,38 @@ variable "appgws" { sequence = number conditions = optional(map(object({ pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) }))) - })), {}) - rules = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) + }))) redirects = optional(map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })), {}) + include_path = optional(bool) + include_query_string = optional(bool) + }))) url_path_maps = optional(map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) }))) - })), {}) - ssl_global = optional(object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })), {}) })) -} +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/.header.md b/examples/virtual_network_gateway/.header.md new file mode 100644 index 00000000..190b9594 --- /dev/null +++ b/examples/virtual_network_gateway/.header.md @@ -0,0 +1,5 @@ +# VNG module sample + +A sample of using a VNG module with the new variables layout and usage of `optional` keyword. + +The `README` is also in new, document-style format. diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md new file mode 100644 index 00000000..df4acc39 --- /dev/null +++ b/examples/virtual_network_gateway/README.md @@ -0,0 +1,310 @@ +<!-- BEGIN_TF_DOCS --> +# VNG module sample + +A sample of using a VNG 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. +[`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of virtual_network_gateways to create. + + + +## Module's Outputs + +Name | Description +--- | --- +`vng_public_ips` | IP Addresses of the VNGs. +`vng_ipsec_policy` | IPsec policy used for Virtual Network Gateway connection + +## Module's Nameplate + + + + +Providers used in this module: + +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | +`vng` | - | ../../modules/virtual_network_gateway | Create virtual network gateway + + +Resources used in this module: + +- `resource_group` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### virtual_network_gateways + +Map of virtual_network_gateways to create. + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + edge_zone = optional(string) + instance_settings = object({ + type = optional(string) + vpn_type = optional(string) + generation = optional(string) + sku = optional(string) + active_active = optional(bool) + }) + ip_configurations = object({ + primary = object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + })) + }) + private_ip_address_enabled = optional(bool) + default_local_network_gateway_id = optional(string) + azure_bgp_peer_addresses = optional(map(string)) + bgp = optional(object({ + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) + })) + })) + local_network_gateways = optional(map(object({ + name = string + remote_bgp_settings = optional(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })) + gateway_address = optional(string) + address_space = optional(list(string), []) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + type = optional(string) + mode = optional(string) + shared_key = optional(string) + }) + })), {}) + vpn_clients = optional(map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + custom_routes = optional(map(list(string))) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars new file mode 100644 index 00000000..b774e6b2 --- /dev/null +++ b/examples/virtual_network_gateway/example.tfvars @@ -0,0 +1,232 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "vng" +name_prefix = "example-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + + +# --- VNET PART --- # +vnets = { + transit = { + name = "transit" + address_space = ["10.0.0.0/24"] + network_security_groups = {} + route_tables = { + "rt" = { + name = "rt" + routes = { + "udr" = { + name = "udr" + address_prefix = "10.0.0.0/8" + next_hop_type = "None" + } + } + } + } + subnets = { + vpn = { + name = "GatewaySubnet" + address_prefixes = ["10.0.0.0/25"] + route_table_key = "rt" + } + } + } + er = { + name = "vpn" + address_space = ["10.0.1.0/24"] + network_security_groups = {} + route_tables = { + "rt" = { + name = "rt-er" + routes = { + "udr" = { + name = "udr" + address_prefix = "10.0.0.0/8" + next_hop_type = "None" + } + } + } + } + subnets = { + vpn = { + name = "GatewaySubnet" + address_prefixes = ["10.0.1.0/25"] + route_table_key = "rt" + } + } + } +} + +# --- VNG PART --- # +virtual_network_gateways = { + expressroute = { + name = "expressroute" + vnet_key = "transit" + subnet_key = "vpn" + zones = ["1"] + + instance_settings = { + type = "ExpressRoute" + vpn_type = "RouteBased" + generation = "Generation1" + sku = "Standard" + } + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "expressroute_pip" + } + } + } + expressroute_policy_based = { + name = "er_policy" + vnet_key = "er" + subnet_key = "vpn" + zones = ["1"] + + instance_settings = { + type = "ExpressRoute" + vpn_type = "PolicyBased" + generation = "Generation2" + sku = "Standard" + } + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "er_policy_pip" + } + } + } + vpn_simple = { + name = "simple-vpn" + vnet_key = "er" + subnet_key = "vpn" + zones = [] + + instance_settings = { + type = "Vpn" + vpn_type = "PolicyBased" + generation = "Generation1" + sku = "VpnGw1" + } + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "simple_vpn_pip" + } + } + } + "vng" = { + name = "vng" + vnet_key = "transit" + subnet_key = "vpn" + zones = ["1", "2", "3"] + + instance_settings = { + type = "Vpn" + generation = "Generation2" + sku = "VpnGw2AZ" + active_active = true + } + ip_configurations = { + primary = { + name = "primary" + create_public_ip = true + public_ip_name = "vng-primary-pip" + } + secondary = { + name = "secondary" + create_public_ip = true + public_ip_name = "vng-secondary-pip" + } + } + azure_bgp_peer_addresses = { + one_primary = "169.254.21.2" + one_secondary = "169.254.22.2" + two_primary = "169.254.21.12" + two_secondary = "169.254.22.12" + three_primary = "169.254.21.22" + three_secondary = "169.254.22.22" + } + bgp = { + enable = true + configuration = { + asn = "65002" + primary_peering_addresses = { + name = "primary" + apipa_address_keys = ["one_primary", "two_primary", "three_primary"] + } + secondary_peering_addresses = { + name = "secondary" + apipa_address_keys = ["one_secondary", "two_secondary", "three_secondary"] + } + } + } + local_network_gateways = { + lg1 = { + name = "local_gw_1" + gateway_address = "8.8.8.8" + remote_bgp_settings = { + asn = "65000" + bgp_peering_address = "169.254.21.1" + } + connection = { + name = "connection_1" + custom_bgp_addresses = { + primary_key = "one_primary" + secondary_key = "one_secondary" + } + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } + } + lg2 = { + name = "local_gw_2" + gateway_address = "4.4.4.4" + remote_bgp_settings = { + asn = "65000" + bgp_peering_address = "169.254.22.1" + } + connection = { + name = "connection_2" + custom_bgp_addresses = { + primary_key = "two_primary" + secondary_key = "two_secondary" + } + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } + } + } + } +} diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf new file mode 100644 index 00000000..fdb9a1ee --- /dev/null +++ b/examples/virtual_network_gateway/main.tf @@ -0,0 +1,67 @@ +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} + +# Create virtual network gateway +module "vng" { + source = "../../modules/virtual_network_gateway" + + for_each = var.virtual_network_gateways + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + zones = each.value.zones + edge_zone = each.value.edge_zone + instance_settings = each.value.instance_settings + ip_configurations = each.value.ip_configurations + private_ip_address_enabled = each.value.private_ip_address_enabled + default_local_network_gateway_id = each.value.default_local_network_gateway_id + + azure_bgp_peer_addresses = each.value.azure_bgp_peer_addresses + bgp = each.value.bgp + local_network_gateways = each.value.local_network_gateways + vpn_clients = each.value.vpn_clients + + tags = var.tags +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/main_test.go b/examples/virtual_network_gateway/main_test.go new file mode 100644 index 00000000..97d2facd --- /dev/null +++ b/examples/virtual_network_gateway/main_test.go @@ -0,0 +1,62 @@ +package vng + +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/virtual_network_gateway/outputs.tf b/examples/virtual_network_gateway/outputs.tf new file mode 100644 index 00000000..469cc0fd --- /dev/null +++ b/examples/virtual_network_gateway/outputs.tf @@ -0,0 +1,9 @@ +output "vng_public_ips" { + description = "IP Addresses of the VNGs." + value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.public_ip } : null +} + +output "vng_ipsec_policy" { + description = "IPsec policy used for Virtual Network Gateway connection" + value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.ipsec_policy } : null +} \ No newline at end of file diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf new file mode 100644 index 00000000..7a933bc1 --- /dev/null +++ b/examples/virtual_network_gateway/variables.tf @@ -0,0 +1,209 @@ +### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +} + +### Virtual Network Gateway +variable "virtual_network_gateways" { + description = "Map of virtual_network_gateways to create." + default = {} + nullable = false + type = map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + edge_zone = optional(string) + instance_settings = object({ + type = optional(string) + vpn_type = optional(string) + generation = optional(string) + sku = optional(string) + active_active = optional(bool) + }) + ip_configurations = object({ + primary = object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + })) + }) + private_ip_address_enabled = optional(bool) + default_local_network_gateway_id = optional(string) + azure_bgp_peer_addresses = optional(map(string)) + bgp = optional(object({ + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) + })) + })) + local_network_gateways = optional(map(object({ + name = string + remote_bgp_settings = optional(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })) + gateway_address = optional(string) + address_space = optional(list(string), []) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + type = optional(string) + mode = optional(string) + shared_key = optional(string) + }) + })), {}) + vpn_clients = optional(map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + custom_routes = optional(map(list(string))) + })), {}) + })) +} diff --git a/examples/virtual_network_gateway/versions.tf b/examples/virtual_network_gateway/versions.tf new file mode 100644 index 00000000..49009426 --- /dev/null +++ b/examples/virtual_network_gateway/versions.tf @@ -0,0 +1,22 @@ +terraform { + # required_version = ">= 1.2, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} diff --git a/modules/appgw/.header.md b/modules/appgw/.header.md index 102577ff..ce2014ee 100644 --- a/modules/appgw/.header.md +++ b/modules/appgw/.header.md @@ -54,6 +54,7 @@ application gateways definitions. ### Example 1 Application Gateway with: + * new public IP * HTTP listener * static capacity @@ -108,6 +109,7 @@ appgws = { ### Example 2 Application Gateway with: + * existing public IP * HTTP listener * static capacity @@ -172,6 +174,7 @@ appgws = { ### Example 3 Application Gateway with: + * new public IP * HTTP listener * autoscaling @@ -222,6 +225,7 @@ appgws = { ### Example 4 Application Gateway with: + * new public IP * WAF enabled * HTTP listener @@ -294,18 +298,23 @@ If you need to test example for Application Gateway with SSL, you need to create and create keys and certs using commands: 1. Create CA private key and certificate: + ```bash openssl genrsa 2048 > ca-key1.pem openssl req -new -x509 -nodes -days 365000 -key ca-key1.pem -out ca-cert1.pem openssl genrsa 2048 > ca-key2.pem openssl req -new -x509 -nodes -days 365000 -key ca-key2.pem -out ca-cert2.pem ``` + 2. Create server certificate: + ```bash openssl req -newkey rsa:2048 -nodes -keyout test1.key -x509 -days 365 -CA ca-cert1.pem -CAkey ca-key1.pem -out test1.crt openssl req -newkey rsa:2048 -nodes -keyout test2.key -x509 -days 365 -CA ca-cert2.pem -CAkey ca-key2.pem -out test2.crt ``` + 3. Create PFX file with key and certificate: + ```bash openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx @@ -314,6 +323,7 @@ and create keys and certs using commands: ### Example 5 Application Gateway with: + * new public IP * multi site HTTPS listener (many host names on port 443) * static capacity @@ -462,6 +472,7 @@ appgws = { ### Example 6 Application Gateway with: + * new public IP * multiple listener: * HTTP diff --git a/modules/appgw/README.md b/modules/appgw/README.md index daa2d5fd..03187352 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -55,6 +55,7 @@ application gateways definitions. ### Example 1 Application Gateway with: + * new public IP * HTTP listener * static capacity @@ -109,6 +110,7 @@ appgws = { ### Example 2 Application Gateway with: + * existing public IP * HTTP listener * static capacity @@ -173,6 +175,7 @@ appgws = { ### Example 3 Application Gateway with: + * new public IP * HTTP listener * autoscaling @@ -223,6 +226,7 @@ appgws = { ### Example 4 Application Gateway with: + * new public IP * WAF enabled * HTTP listener @@ -295,18 +299,23 @@ If you need to test example for Application Gateway with SSL, you need to create and create keys and certs using commands: 1. Create CA private key and certificate: + ```bash openssl genrsa 2048 > ca-key1.pem openssl req -new -x509 -nodes -days 365000 -key ca-key1.pem -out ca-cert1.pem openssl genrsa 2048 > ca-key2.pem openssl req -new -x509 -nodes -days 365000 -key ca-key2.pem -out ca-cert2.pem ``` + 2. Create server certificate: + ```bash openssl req -newkey rsa:2048 -nodes -keyout test1.key -x509 -days 365 -CA ca-cert1.pem -CAkey ca-key1.pem -out test1.crt openssl req -newkey rsa:2048 -nodes -keyout test2.key -x509 -days 365 -CA ca-cert2.pem -CAkey ca-key2.pem -out test2.crt ``` + 3. Create PFX file with key and certificate: + ```bash openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx @@ -315,6 +324,7 @@ and create keys and certs using commands: ### Example 5 Application Gateway with: + * new public IP * multi site HTTPS listener (many host names on port 443) * static capacity @@ -463,6 +473,7 @@ appgws = { ### Example 6 Application Gateway with: + * new public IP * multiple listener: * HTTP @@ -806,15 +817,10 @@ Name | Type | Description [`name`](#name) | `string` | The name of the Application Gateway. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. [`location`](#location) | `string` | The name of the Azure region to deploy the resources in. -[`public_ip`](#public_ip) | `object` | A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. -[`subnet_id`](#subnet_id) | `string` | An ID of a subnet that will host the Application Gateway. -[`ssl_profiles`](#ssl_profiles) | `map` | A map of SSL profiles. +[`subnet_id`](#subnet_id) | `string` | An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway. +[`public_ip`](#public_ip) | `object` | A map defining listener's public IP configuration. [`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. -[`probes`](#probes) | `map` | A map of probes for the Application Gateway. -[`rewrites`](#rewrites) | `map` | A map of rewrites for the Application Gateway. [`rules`](#rules) | `map` | A map of rules for the Application Gateway. -[`redirects`](#redirects) | `map` | A map of redirects for the Application Gateway. -[`url_path_maps`](#url_path_maps) | `map` | A map of URL path maps for the Application Gateway. ## Module's Optional Inputs @@ -823,15 +829,20 @@ Name | Type | Description --- | --- | --- [`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`zones`](#zones) | `list` | A list of zones the Application Gateway should be available in. -[`domain_name_label`](#domain_name_label) | `string` | Label for the Domain Name. +[`domain_name_label`](#domain_name_label) | `string` | A label for the Domain Name. +[`capacity`](#capacity) | `object` | A map defining whether static or autoscale configuration is used. [`enable_http2`](#enable_http2) | `bool` | Enable HTTP2 on the Application Gateway. -[`waf`](#waf) | `object` | Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. -[`capacity`](#capacity) | `object` | Capacity configuration for Application Gateway. +[`waf`](#waf) | `object` | A map defining only the SKU and providing basic WAF (Web Application Firewall) configuration for Application Gateway. [`managed_identities`](#managed_identities) | `list` | A list of existing User-Assigned Managed Identities. -[`ssl_global`](#ssl_global) | `object` | Global SSL settings. -[`frontend_ip_configuration_name`](#frontend_ip_configuration_name) | `string` | Frontend IP configuration name. -[`backend_pool`](#backend_pool) | `object` | Backend pool. -[`backends`](#backends) | `map` | A map of backend settings for the Application Gateway. +[`global_ssl_policy`](#global_ssl_policy) | `object` | A map defining global SSL settings. +[`ssl_profiles`](#ssl_profiles) | `map` | A map of SSL profiles. +[`frontend_ip_configuration_name`](#frontend_ip_configuration_name) | `string` | A frontend IP configuration name. +[`backend_pool`](#backend_pool) | `object` | A map defining a backend pool, when skipped will create an empty backend. +[`backend_settings`](#backend_settings) | `map` | A map of backend settings for the Application Gateway. +[`probes`](#probes) | `map` | A map of probes for the Application Gateway. +[`rewrites`](#rewrites) | `map` | A map of rewrites for the Application Gateway. +[`redirects`](#redirects) | `map` | A map of redirects for the Application Gateway. +[`url_path_maps`](#url_path_maps) | `map` | A map of URL path maps for the Application Gateway. @@ -849,12 +860,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -895,17 +906,24 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### subnet_id + +An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + #### public_ip -A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. +A map defining listener's public IP configuration. Following properties are available: - -- `name` - (`string`, required) name of the created or source Public IP resource -- `create` - (`bool`, optional, defaults to `true`) controls if the public IP is created or sourced. -- `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the - existing Public IP resource +- `name` - (`string`, required) name of the Public IP resource. +- `create` - (`bool`, optional, defaults to `true`) controls if the Public IP resource is created or sourced. +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group hosting the Public IP resource, + used only for sourced resources. Type: @@ -926,47 +944,7 @@ object({ -#### subnet_id - -An ID of a subnet that will host the Application Gateway. - -Keep in mind that this subnet can contain only AppGWs and only of the same type. - - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### ssl_profiles - -A map of SSL profiles. - -SSL profiles can be later on referenced in HTTPS listeners by providing a name of the profile in the `name` property. -For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` -variables as SSL profile is a named SSL policy - same properties apply. -The only difference is that you cannot name an SSL policy inside an SSL profile. - -Every SSL profile contains attributes: -- `name` - (`string`, required) name of the SSL profile -- `ssl_policy_name` - (`string`, optional) name of predefined policy -- `ssl_policy_min_protocol_version` - (`string`, optional) the minimal TLS version. -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. - - -Type: - -```hcl -map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> #### listeners @@ -974,18 +952,20 @@ map(object({ A map of listeners for the Application Gateway. Every listener contains attributes: -- `name` - (`string`, required) The name for this Frontend Port. -- `port` - (`string`, required) The port used for this Frontend Port. -- `protocol` - (`string`, optional) The Protocol to use for this HTTP Listener. -- `host_names` - (`list`, optional) A list of Hostname(s) should be used for this HTTP Listener. - It allows special wildcard characters. -- `ssl_profile_name` - (`string`, optional) The name of the associated SSL Profile which should be used - for this HTTP Listener. -- `ssl_certificate_path` - (`string`, optional) Path to the file with tThe base64-encoded PFX certificate data. -- `ssl_certificate_pass` - (`string`, optional) Password for the pfx file specified in data. -- `ssl_certificate_vault_id` - (`string`, optional) Secret Id of (base-64 encoded unencrypted pfx) Secret + +- `name` - (`string`, required) the name for this Frontend Port. +- `port` - (`string`, required) the port used for this Frontend Port. +- `protocol` - (`string`, optional, defaults to `Https`) the Protocol to use for this HTTP Listener. +- `host_names` - (`list`, optional, defaults to `null`) A list of Hostname(s) should be used for this HTTP + Listener, it allows special wildcard characters. +- `ssl_profile_name` - (`string`, optional, defaults to `null`) the name of the associated SSL Profile which should be + used for this HTTP Listener. +- `ssl_certificate_vault_id` - (`string`, optional, defaults to `null`) Secret Id of (base-64 encoded unencrypted pfx) Secret or Certificate object stored in Azure KeyVault. -- `custom_error_pages` - (`map`, optional) Map of string, where key is HTTP status code and value is +- `ssl_certificate_path` - (`string`, optional, defaults to `null`) Path to the file with tThe base64-encoded PFX + certificate data. +- `ssl_certificate_pass` - (`string`, optional, defaults to `null`) Password for the pfx file specified in data. +- `custom_error_pages` - (`map`, optional, defaults to `{}`) Map of string, where key is HTTP status code and value is error page URL of the application gateway customer error. @@ -998,9 +978,9 @@ map(object({ protocol = optional(string, "Http") host_names = optional(list(string)) ssl_profile_name = optional(string) + ssl_certificate_vault_id = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) custom_error_pages = optional(map(string), {}) })) ``` @@ -1010,178 +990,43 @@ map(object({ -#### probes - -A map of probes for the Application Gateway. -Every probe contains attributes: -- `name` - (`string`, required) The name used for this Probe -- `path` - (`string`, required) The path used for this Probe -- `host` - (`string`, optional) The hostname used for this Probe -- `port` - (`number`, optional) Custom port which will be used for probing the backend servers. -- `protocol` - (`string`, optional, defaults `Http`) The protocol which should be used. -- `interval` - (`number`, optional, defaults `5`) The interval between two consecutive probes in seconds. -- `timeout` - (`number`, optional, defaults `30`) The timeout used for this Probe, - which indicates when a probe becomes unhealthy. -- `threshold` - (`number`, optional, defaults `2`) The unhealthy Threshold for this Probe, which indicates - the amount of retries which should be attempted before a node is deemed unhealthy. -- `match_code` - (`list`, optional) The list of allowed status codes for this Health Probe. -- `match_body` - (`string`, optional) A snippet from the Response Body which must be present in the Response. -Type: - -```hcl -map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string, "Http") - interval = optional(number, 5) - timeout = optional(number, 30) - threshold = optional(number, 2) - match_code = optional(list(number)) - match_body = optional(string) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - -#### rewrites - -A map of rewrites for the Application Gateway. - -Every rewrite contains attributes: -- `name` - (`string`) Rewrite Rule Set name -- `rules` - (`object`, optional) Rewrite Rule Set defined with attributes: - - `name` - (`string`, required) Rewrite Rule name. - - `sequence` - (`number`, required) Rule sequence of the rewrite rule that determines - the order of execution in a set. - - `conditions` - (`map`, optional) One or more condition blocks as defined below: - - `pattern` - (`string`, required) The pattern, either fixed string or regular expression, - that evaluates the truthfulness of the condition. - - `ignore_case` - (`string`, optional, defaults to `false`) Perform a case in-sensitive comparison. - - `negate` - (`bool`, optional, defaults to `false`) Negate the result of the condition evaluation. - - `request_headers` - (`map`, optional) Map of request header, where header name is the key, - header value is the value of the object in the map. - - `response_headers`- (`map`, optional) Map of response header, where header name is the key, - header value is the value of the object in the map. - - -Type: - -```hcl -map(object({ - name = string - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool, false) - negate = optional(bool, false) - })), {}) - request_headers = optional(map(string), {}) - response_headers = optional(map(string), {}) - }))) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> #### rules -A map of rules for the Application Gateway. +A map of rules for the Application Gateway. A rule combines backend's, listener's, rewrites' and redirects' configurations. -A rule combines, backend, listener, rewrites and redirects configurations. -A key is an application name that is used to prefix all components inside Application Gateway +A key is an application name that is used to prefix all components inside an Application Gateway that are created for this application. -Every rule contains attributes: -- `name` - (`string`, required) Rule name. -- `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value - from 1 to 20000 with 1 being the highest priority and 20000 being the lowest priority. -- `backend` - (`string`, optional) Backend settings` key -- `listener` - (`string`, required) Listener's key -- `rewrite` - (`string`, optional) Rewrite's key -- `url_path_map` - (`string`, optional) URL Path Map's key -- `redirect` - (`string`, optional) Redirect's key - - -Type: - -```hcl -map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - -#### redirects - -A map of redirects for the Application Gateway. - -Every redirect contains attributes: -- `name` - (`string`, required) The name of redirect. -- `type` - (`string`, required) The type of redirect. - Possible values are Permanent, Temporary, Found and SeeOther -- `target_listener` - (`string`, optional) The name of the listener to redirect to. -- `target_url` - (`string`, optional) The URL to redirect the request to. -- `include_path` - (`bool`, optional) Whether or not to include the path in the redirected URL. -- `include_query_string` - (`bool`, optional) Whether or not to include the query string in the redirected URL. - - -Type: - -```hcl -map(object({ - name = string - type = string - target_listener = optional(string) - target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - -#### url_path_maps +Every rule contains following attributes: -A map of URL path maps for the Application Gateway. - -Every URL path map contains attributes: -- `name` - (`string`, required) The name of redirect. -- `backend` - (`string`, required) The default backend for redirect. -- `path_rules` - (`map`, optional) The map of rules, where every object has attributes: - - `paths` - (`list`, required) List of paths - - `backend` - (`string`, optional) Backend's key - - `redirect` - (`string`, optional) Redirect's key +- `name` - (`string`, required) Rule name. +- `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value from 1 to + 20000 with 1 being the highest priority and 20000 being the lowest priority. +- `listener_key` - (`string`, required) a key identifying a listener config defined in `var.listeners`. +- `backend_key` - (`string`, optional, mutually exclusive with `url_path_map_key` and `redirect_key`) a key identifying a + backend config defined in `var.backend_settings`. +- `rewrite_key` - (`string`, optional, defaults to `null`) a key identifying a rewrite config defined in `var.rewrites`. +- `url_path_map_key` - (`string`, optional, mutually exclusive with `backend_key` and `redirect_key`) a key identifying a + url_path_map config defined in `var.url_path_maps`. +- `redirect_key` - (`string`, optional, mutually exclusive with `url_path_map_key` and `backend_key`) a key identifying a + redirect config defined in `var.redirects`. Type: ```hcl map(object({ - name = string - backend = string - path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) - }))) + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) })) ``` @@ -1206,25 +1051,23 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### zones -A list of zones the Application Gateway should be available in. +A list of zones the Application Gateway should be available in. For non-zonal deployments this should be set to an empty list, +as `null` will enforce the default value. **Note!** \ -This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, -pinned to a single zone or zone-redundant (so available in all zones in a region). -Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, -but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during -next `terraform apply` as there will be difference between the state and the actual configuration. +This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to +a single zone or zone-redundant (so available in all zones in a region). + +Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the +Public IP will be created in all zones anyway. This fact will cause Terraform to recreate the IP resource during next +`terraform apply` as there will be difference between the state and the actual configuration. For details on zones currently available in a region of your choice refer to [Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). -Example: -``` -zones = ["1","2","3"] -``` - Type: list(string) @@ -1235,10 +1078,8 @@ Default value: `[1 2 3]` #### domain_name_label -Label for the Domain Name. - -Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created -for the public IP in the Microsoft Azure DNS system." +A label for the Domain Name. Will be used to make up the FQDN. +If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system. Type: string @@ -1247,6 +1088,35 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> +#### capacity + +A map defining whether static or autoscale configuration is used. + +Following properties are available: +- `static` - (`number`, optional, defaults to `2`) static number of Application Gateway instances, takes values bewteen 1 + and 125. +- `autoscale` - (`map`, optional, defaults to `null`) autoscaling configuration, when specified `static` is being ignored: + - `min` - (`number`, required) minimum number of instances during autoscaling. + - `max` - (`number`, required) maximum number of instances during autoscaling. + + +Type: + +```hcl +object({ + static = optional(number, 2) + autoscale = optional(object({ + min = number + max = number + })) + }) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + #### enable_http2 Enable HTTP2 on the Application Gateway. @@ -1259,13 +1129,13 @@ Default value: `false` #### waf -Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. +A map defining only the SKU and providing basic WAF (Web Application Firewall) configuration for Application Gateway. This +module does not support WAF rules configuration and advanced WAF settings. -This module does not support WAF rules configuration and advanced WAF settings. -Only below attributes are allowed: -- `prevention_mode` - (`bool`, required) `true` if WAF mode is Prevention, `false` for Detection mode -- `rule_set_type` - (`string`, optional, defaults to `OWASP`) The Type of the Rule Set used for this Web Application Firewall -- `rule_set_version` - (`string`, optional) The Version of the Rule Set used for this Web Application Firewall +Following properties are available: +- `prevention_mode` - (`bool`, required) `true` sets WAF mode to `Prevention` mode, `false` to `Detection` mode. +- `rule_set_type` - (`string`, optional, defaults to `OWASP`) the type of the Rule Set used for this WAF. +- `rule_set_version` - (`string`, optional, defaults to Azure defaults) the version of the Rule Set used for this WAF. Type: @@ -1283,96 +1153,97 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> -#### capacity +#### managed_identities -Capacity configuration for Application Gateway. +A list of existing User-Assigned Managed Identities. + +**Note!** \ +Application Gateway uses Managed Identities to retrieve certificates from a Key Vault. These identities have to have at least +`GET` access to Key Vault's secrets. Otherwise Application Gateway will not be able to use certificates stored in the Vault. -Object defines static or autoscale configuration using attributes: -- `static` - (`number`, optional) A static number of Application Gateway instances. A value bewteen 1 and 125 - or null, if autoscale configuration is provided -- `autoscale` - (`object`, optional) Autoscaling configuration (used only, if static is null) with attributes: - - `min` - (`number`, optional) Minimum capacity for autoscaling. - - `max` - (`number`, optional) Maximum capacity for autoscaling. +Type: list(string) -Type: +Default value: `&{}` -```hcl -object({ - static = optional(number) - autoscale = optional(object({ - min = optional(number) - max = optional(number) - })) - }) -``` +<sup>[back to list](#modules-optional-inputs)</sup> +#### global_ssl_policy -Default value: `map[static:2]` +A map defining global SSL settings. -<sup>[back to list](#modules-optional-inputs)</sup> +Following properties are available: +- `type` - (`string`, required, but defaults to `Predefined`) type of an SSL policy, possible values include: + `Predefined`, `Custom` or `CustomV2`. +- `name` - (`string`, optional, defaults to `AppGwSslPolicy20220101S`) name of an SSL policy, supported only + for `type` set to `Predefined`. + + **Note!** \ + Normally you can set it also for `Custom` policies but the name is discarded on Azure side causing an update to Application + Gateway each time Terraform code is run. Therefore this property is omitted in the code for `Custom` policies. -#### managed_identities + For the `Predefined` policies, check the + [Microsoft documentation](https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview) + for possible values as they tend to change over time. The default value is currently (Q1 2023) is also Microsoft's default. -A list of existing User-Assigned Managed Identities. +- `min_protocol_version` - (`string`, optional, defaults to `null`) minimum version of the TLS protocol for SSL Policy, + required only for `type` set to `Custom`. +- `cipher_suites` - (`list`, optional, defaults to `[]`) a list of accepted cipher suites, required only for `type` set + to `Custom`. For possible values see [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites). -Application Gateway uses Managed Identities to retrieve certificates from Key Vault. -These identities have to have at least `GET` access to Key Vault's secrets. -Otherwise Application Gateway will not be able to use certificates stored in the Vault. +Type: -Type: list(string) +```hcl +object({ + type = optional(string, "Predefined") + name = optional(string, "AppGwSslPolicy20220101S") + min_protocol_version = optional(string) + cipher_suites = optional(list(string), []) + }) +``` -Default value: `&{}` + +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### ssl_profiles + +A map of SSL profiles. -#### ssl_global +SSL profiles can be later on referenced in HTTPS listeners by providing a name of the profile in the `name` property. +For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` properties +as SSL profile is a named SSL policy - same properties apply. +The only difference is that you cannot name an SSL policy inside an SSL profile. -Global SSL settings. +Every SSL profile contains following attributes: -SSL settings are defined by attributes: -- `ssl_policy_type` - (`string`, required) type of an SSL policy. Possible values are `Predefined` - or `Custom` or `CustomV2`. If the value is `Custom` the following values are mandatory: - `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. -- `ssl_policy_name` - (`string`, optional) name of an SSL policy. - Supported only for `ssl_policy_type` set to `Predefined`. - Normally you can set it also for `Custom` policies but the name is discarded - on Azure side causing an update to Application Gateway each time terraform code is run. - Therefore this property is omitted in the code for `Custom` policies. - For the `Predefined` policies, check the Microsoft documentation - https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview - for possible values as they tend to change over time. - The default value is currently (Q1 2023) a Microsoft's default. -- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy. - Required only for `ssl_policy_type` set to `Custom`. -- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. - Required only for `ssl_policy_type` set to `Custom`. - For possible values see documentation: - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites +- `name` - (`string`, required) name of the SSL profile. +- `ssl_policy_name` - (`string`, optional, defaults to `null`) name of predefined policy. +- `ssl_policy_min_protocol_version` - (`string`, optional, defaults to `null`) the minimal TLS version. +- `ssl_policy_cipher_suites` - (`list`, optional, defaults to `null`) a list of accepted cipher suites. Type: ```hcl -object({ - ssl_policy_type = string +map(object({ + name = string ssl_policy_name = optional(string) ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) - }) + })) ``` -Default value: `map[ssl_policy_cipher_suites:[] ssl_policy_min_protocol_version:<nil> ssl_policy_name:AppGwSslPolicy20220101S ssl_policy_type:Predefined]` +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### frontend_ip_configuration_name -Frontend IP configuration name +A frontend IP configuration name. Type: string @@ -1383,60 +1254,69 @@ Default value: `public_ipconfig` #### backend_pool -Backend pool. - -Object contains attributes: -- `name` - (`string`, required) name of the backend pool. -- `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backends +A map defining a backend pool, when skipped will create an empty backend. + +Following properties are available: +- `name` - (`string`, optional, defaults to `vmseries`) name of the backend pool. +- `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backend nodes for the Application Gateway. + Type: ```hcl object({ - name = string + name = optional(string, "vmseries") vmseries_ips = optional(list(string), []) }) ``` -Default value: `map[name:vmseries]` +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### backends +#### backend_settings A map of backend settings for the Application Gateway. Every backend contains attributes: -- `name` - (`string`, optional) The name of the backend settings -- `path` - (`string`, optional) The Path which should be used as a prefix for all HTTP requests. -- `hostname_from_backend` - (`bool`, optional) Whether host header should be picked from the host name of the backend server. -- `hostname` - (`string`, optional) Host header to be sent to the backend servers. -- `port` - (`number`, optional) The port which should be used for this Backend HTTP Settings Collection. -- `protocol` - (`string`, optional) The Protocol which should be used. Possible values are Http and Https. -- `timeout` - (`number`, optional) The request timeout in seconds, which must be between 1 and 86400 seconds. -- `cookie_based_affinity` - (`string`, optional) Is Cookie-Based Affinity enabled? Possible values are Enabled and Disabled. -- `affinity_cookie_name` - (`string`, optional) The name of the affinity cookie. -- `probe` - (`string`, optional) Probe's key. -- `root_certs` - (`map`, optional) A list of trusted_root_certificate names. + +- `name` - (`string`, required) the name of the backend settings. +- `port` - (`number`, required) the port which should be used for this Backend HTTP Settings Collection. +- `protocol` - (`string`, required) the Protocol which should be used. Possible values are Http and Https. +- `path` - (`string`, optional, defaults to `null`) the Path which should be used as a prefix for all HTTP + requests. +- `hostname_from_backend` - (`bool`, optional, defaults to `false`) whether host header should be picked from the host name + of the backend server. +- `hostname` - (`string`, optional, defaults to `null`) host header to be sent to the backend servers. +- `timeout` - (`number`, optional, defaults to `60`) the request timeout in seconds, which must be between 1 + and 86400 seconds. +- `use_cookie_based_affinity` - (`bool`, optional, defaults to `true`) when set to `true` enables Cookie-Based Affinity. +- `affinity_cookie_name` - (`string`, optional, defaults to Azure defaults) the name of the affinity cookie. +- `probe_key` - (`string`, optional, defaults to `null`) a key identifying a Probe definition in the + `var.probes`. +- `root_certs` - (`map`, optional, defaults to `{}`) a map of objects defining paths to trusted root + certificates (`PEM` format), each map contains 2 properties: + - `name` - (`string`, required) a name of the certificate. + - `path` - (`string`, required) path to a file on a local file system containing the root cert. Type: ```hcl map(object({ - name = optional(string) - path = optional(string) - hostname_from_backend = optional(bool, false) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(bool, false) + hostname = optional(string) + timeout = optional(number, 60) + use_cookie_based_affinity = optional(bool, true) + affinity_cookie_name = optional(string) + probe_key = optional(string) root_certs = optional(map(object({ name = string path = string @@ -1445,14 +1325,166 @@ map(object({ ``` -Default value: `map[minimum:map[name:minimum]]` +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### probes + +A map of probes for the Application Gateway. + +Every probe contains attributes: +- `name` - (`string`, required) the name used for this Probe. +- `path` - (`string`, required) the path used for this Probe. +- `host` - (`string`, optional, defaults to `null`) the hostname used for this Probe. +- `port` - (`number`, optional, defaults to `null`) custom port which will be used for probing the backend servers, when + skipped a default port for `protocol` will be used. +- `protocol` - (`string`, optional, defaults `Http`) the protocol which should be used, possible values are `Http` or `Https`. +- `interval` - (`number`, optional, defaults `5`) the interval between two consecutive probes in seconds. +- `timeout` - (`number`, optional, defaults `30`) the timeout after which a single probe is marked unhealthy. +- `threshold` - (`number`, optional, defaults `2`) the unhealthy Threshold for this Probe, which indicates the amount of + retries which should be attempted before a node is deemed unhealthy. +- `match_code` - (`list`, optional, defaults to `null`) custom list of allowed status codes for this Health Probe. +- `match_body` - (`string`, optional, defaults to `null`) a custom snippet from the Response Body which must be present to + treat a single probe as healthy. +Type: +```hcl +map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### rewrites + +A map of rewrites for the Application Gateway. + +Every rewrite contains attributes: + +- `name` - (`string`, required) Rewrite Rule Set name. +- `rules` - (`map`, required) rewrite Rule Set defined with following attributes available: + - `name` - (`string`, required) Rewrite Rule name. + - `sequence` - (`number`, required) determines the order of rule execution in a set. + - `conditions` - (`map`, optional, defaults to `{}`) one or more condition blocks as defined below: + - `pattern` - (`string`, required) the pattern, either fixed string or regular expression, that evaluates the + truthfulness of the condition. + - `ignore_case` - (`string`, optional, defaults to `false`) perform a case in-sensitive comparison. + - `negate` - (`bool`, optional, defaults to `false`) negate the result of the condition evaluation. + - `request_headers` - (`map`, optional, defaults to `{}`) map of request headers, where header name is the key, header value + is the value. + - `response_headers` - (`map`, optional, defaults to `{}`) map of response header, where header name is the key, header value + is the value. + + +Type: + +```hcl +map(object({ + name = string + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### redirects + +A map of redirects for the Application Gateway. + +Every redirect contains attributes: +- `name` - (`string`, required) the name of redirect. +- `type` - (`string`, required) the type of redirect, possible values are `Permanent`, `Temporary`, `Found` and + `SeeOther`. +- `target_listener_key` - (`string`, optional, mutually exclusive with `target_url`) a key identifying a backend config + defined in `var.listeners`. +- `target_url` - (`string`, optional, mutually exclusive with `target_listener`) the URL to redirect to. +- `include_path` - (`bool`, optional, defaults to Azure defaults) whether or not to include the path in the redirected + URL. +- `include_query_string` - (`bool`, optional, defaults to Azure defaults) whether or not to include the query string in the + redirected URL. + + +Type: + +```hcl +map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### url_path_maps + +A map of URL path maps for the Application Gateway. + +Every URL path map contains attributes: +- `name` - (`string`, required) the name of redirect. +- `backend_key` - (`string`, required) a key identifying the default backend for redirect defined in `var.backend_settings`. +- `path_rules` - (`map`, optional, defaults to `{}`) the map of rules, where every object has attributes: + - `paths` - (`list`, required) a list of paths. + - `backend_key` - (`string`, optional, mutually exclusive with `redirect_key`) a key identifying a backend config defined + in `var.backend_settings`. + - `redirect_key` - (`string`, optional, mutually exclusive with `backend_key`) a key identifying a redirect config defined + in `var.redirects`. + + +Type: + +```hcl +map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index e520ab16..aacbc7fc 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -7,7 +7,7 @@ locals { # Calculate a flat map of all backend's trusted root certificates. # Root certs are created upfront and then referenced in a single list in the http setting's config. root_certs_flat_list = flatten([ - for k, v in var.backends : [ + for k, v in var.backend_settings : [ for key, root_cert in v.root_certs : root_cert ] ]) @@ -16,14 +16,16 @@ locals { # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "this" { - count = var.public_ip.create ? 0 : 1 + count = var.public_ip.create ? 0 : 1 + name = var.public_ip.name resource_group_name = coalesce(var.public_ip.resource_group_name, var.resource_group_name) } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - count = var.public_ip.create ? 1 : 0 + count = var.public_ip.create ? 1 : 0 + name = var.public_ip.name resource_group_name = var.resource_group_name location = var.location @@ -47,29 +49,29 @@ resource "azurerm_application_gateway" "this" { sku { name = var.waf != null ? "WAF_v2" : "Standard_v2" tier = var.waf != null ? "WAF_v2" : "Standard_v2" - capacity = var.capacity.static != null ? var.capacity.static : null - } - - dynamic "autoscale_configuration" { - for_each = var.capacity.autoscale != null ? [1] : [] - - content { - min_capacity = var.capacity.autoscale.min - max_capacity = var.capacity.autoscale.max - } + capacity = var.capacity.autoscale == null ? var.capacity.static : null } dynamic "waf_configuration" { for_each = var.waf != null ? [1] : [] content { - enabled = var.waf != null + enabled = true firewall_mode = var.waf.prevention_mode ? "Prevention" : "Detection" rule_set_type = var.waf.rule_set_type rule_set_version = var.waf.rule_set_version } } + dynamic "autoscale_configuration" { + for_each = var.capacity.autoscale != null ? [1] : [] + + content { + min_capacity = var.capacity.autoscale.min + max_capacity = var.capacity.autoscale.max + } + } + dynamic "identity" { for_each = var.managed_identities != null ? [1] : [] @@ -86,20 +88,20 @@ resource "azurerm_application_gateway" "this" { frontend_ip_configuration { name = var.frontend_ip_configuration_name - public_ip_address_id = var.public_ip.create ? azurerm_public_ip.this[0].id : data.azurerm_public_ip.this[0].id + public_ip_address_id = try(azurerm_public_ip.this[0].id, data.azurerm_public_ip.this[0].id) } - # There is only a single backend - the VM-Series private IPs assigned to untrusted NICs + # There is only a single backend - the VMSeries private IPs assigned to untrusted NICs backend_address_pool { name = var.backend_pool.name ip_addresses = var.backend_pool.vmseries_ips } ssl_policy { - policy_name = var.ssl_global.ssl_policy_type == "Predefined" ? var.ssl_global.ssl_policy_name : null - policy_type = var.ssl_global.ssl_policy_type - min_protocol_version = var.ssl_global.ssl_policy_min_protocol_version - cipher_suites = var.ssl_global.ssl_policy_cipher_suites + policy_name = var.global_ssl_policy.type == "Predefined" ? var.global_ssl_policy.name : null + policy_type = var.global_ssl_policy.type + min_protocol_version = var.global_ssl_policy.min_protocol_version + cipher_suites = var.global_ssl_policy.cipher_suites } # The following block is supported only in v2 Application Gateways. @@ -109,8 +111,8 @@ resource "azurerm_application_gateway" "this" { content { name = ssl_profile.value.name ssl_policy { - policy_name = var.ssl_global.ssl_policy_type == "Predefined" ? ssl_profile.value.ssl_policy_name : null - policy_type = var.ssl_global.ssl_policy_type + policy_name = var.global_ssl_policy.type == "Predefined" ? ssl_profile.value.ssl_policy_name : null + policy_type = var.global_ssl_policy.type min_protocol_version = ssl_profile.value.ssl_policy_min_protocol_version cipher_suites = ssl_profile.value.ssl_policy_cipher_suites } @@ -161,7 +163,7 @@ resource "azurerm_application_gateway" "this" { } dynamic "backend_http_settings" { - for_each = var.backends + for_each = var.backend_settings content { name = backend_http_settings.value.name @@ -171,9 +173,9 @@ resource "azurerm_application_gateway" "this" { host_name = backend_http_settings.value.hostname path = backend_http_settings.value.path request_timeout = backend_http_settings.value.timeout - probe_name = (backend_http_settings.value.probe != null && var.probes != null ? - var.probes[backend_http_settings.value.probe].name : null) - cookie_based_affinity = backend_http_settings.value.cookie_based_affinity + probe_name = (backend_http_settings.value.probe_key != null && var.probes != null ? + var.probes[backend_http_settings.value.probe_key].name : null) + cookie_based_affinity = backend_http_settings.value.use_cookie_based_affinity ? "Enabled" : "Disabled" affinity_cookie_name = backend_http_settings.value.affinity_cookie_name trusted_root_certificate_names = [for k, v in backend_http_settings.value.root_certs : v.name] } @@ -220,8 +222,8 @@ resource "azurerm_application_gateway" "this" { content { name = redirect_configuration.value.name redirect_type = redirect_configuration.value.type - target_listener_name = (redirect_configuration.value.target_listener != null ? - var.listeners[redirect_configuration.value.target_listener].name : null) + target_listener_name = (redirect_configuration.value.target_listener_key != null ? + var.listeners[redirect_configuration.value.target_listener_key].name : null) target_url = redirect_configuration.value.target_url include_path = redirect_configuration.value.include_path include_query_string = redirect_configuration.value.include_query_string @@ -277,7 +279,7 @@ resource "azurerm_application_gateway" "this" { content { name = url_path_map.value.name default_backend_address_pool_name = var.backend_pool.name - default_backend_http_settings_name = var.backends[url_path_map.value.backend].name + default_backend_http_settings_name = var.backend_settings[url_path_map.value.backend_key].name dynamic "path_rule" { for_each = url_path_map.value.path_rules @@ -285,9 +287,9 @@ resource "azurerm_application_gateway" "this" { content { name = path_rule.key paths = path_rule.value.paths - backend_address_pool_name = path_rule.value.backend != null ? var.backend_pool.name : null - backend_http_settings_name = path_rule.value.backend != null ? var.backends[path_rule.value.backend].name : null - redirect_configuration_name = path_rule.value.redirect != null ? var.redirects[path_rule.value.redirect].name : null + backend_address_pool_name = path_rule.value.backend_key != null ? var.backend_pool.name : null + backend_http_settings_name = path_rule.value.backend_key != null ? var.backend_settings[path_rule.value.backend_key].name : null + redirect_configuration_name = path_rule.value.redirect_key != null ? var.redirects[path_rule.value.redirect_key].name : null } } } @@ -298,24 +300,24 @@ resource "azurerm_application_gateway" "this" { content { name = request_routing_rule.value.name - rule_type = request_routing_rule.value.url_path_map != null ? "PathBasedRouting" : "Basic" + rule_type = request_routing_rule.value.url_path_map_key != null ? "PathBasedRouting" : "Basic" priority = request_routing_rule.value.priority - http_listener_name = var.listeners[request_routing_rule.value.listener].name + http_listener_name = var.listeners[request_routing_rule.value.listener_key].name backend_address_pool_name = ( - request_routing_rule.value.backend != null ? var.backend_pool.name : null + request_routing_rule.value.backend_key != null ? var.backend_pool.name : null ) backend_http_settings_name = ( - request_routing_rule.value.backend != null ? var.backends[request_routing_rule.value.backend].name : null + request_routing_rule.value.backend_key != null ? var.backend_settings[request_routing_rule.value.backend_key].name : null ) redirect_configuration_name = ( - request_routing_rule.value.redirect != null ? var.redirects[request_routing_rule.value.redirect].name : null + request_routing_rule.value.redirect_key != null ? var.redirects[request_routing_rule.value.redirect_key].name : null ) rewrite_rule_set_name = ( - request_routing_rule.value.rewrite != null ? var.rewrites[request_routing_rule.value.rewrite].name : null + request_routing_rule.value.rewrite_key != null ? var.rewrites[request_routing_rule.value.rewrite_key].name : null ) url_path_map_name = ( - request_routing_rule.value.url_path_map != null ? var.url_path_maps[request_routing_rule.value.url_path_map].name : null + request_routing_rule.value.url_path_map_key != null ? var.url_path_maps[request_routing_rule.value.url_path_map_key].name : null ) } } @@ -324,12 +326,12 @@ resource "azurerm_application_gateway" "this" { precondition { condition = var.probes != null ? alltrue(flatten([ for k, probe in var.probes : probe.host != null || alltrue(flatten([ - for b, backend in var.backends : backend.probe == k ? backend.hostname != null || backend.hostname_from_backend : true + for b, backend in var.backend_settings : backend.probe_key == k ? backend.hostname != null || backend.hostname_from_backend : true ])) ])) : true error_message = <<EOF -Custom health probes needs to have defined host or backend settings needs to -contain overriden host name or enabled option to pick host name from backend target. + Custom health probes needs to have defined host or backend settings needs to + contain overriden host name or enabled option to pick host name from backend target. EOF } } diff --git a/modules/appgw/outputs.tf b/modules/appgw/outputs.tf index 7bbe8f5a..50eb35b9 100644 --- a/modules/appgw/outputs.tf +++ b/modules/appgw/outputs.tf @@ -1,11 +1,11 @@ output "public_ip" { description = "A public IP assigned to the Application Gateway." - value = var.public_ip.create ? azurerm_public_ip.this[0].ip_address : data.azurerm_public_ip.this[0].ip_address + value = try(azurerm_public_ip.this[0].ip_address, data.azurerm_public_ip.this[0].ip_address) } output "public_domain_name" { description = "Public domain name assigned to the Application Gateway." - value = var.public_ip.create ? azurerm_public_ip.this[0].fqdn : data.azurerm_public_ip.this[0].fqdn + value = try(azurerm_public_ip.this[0].fqdn, data.azurerm_public_ip.this[0].fqdn) } output "backend_pool_id" { diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index d18e7f85..cc71f8c2 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -1,10 +1,8 @@ -# Main resource variable "name" { description = "The name of the Application Gateway." type = string } -# Common settings variable "resource_group_name" { description = "The name of the Resource Group to use." type = string @@ -21,44 +19,40 @@ variable "tags" { type = map(string) } -# Application Gateway +variable "subnet_id" { + description = "An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway." + type = string +} + variable "zones" { description = <<-EOF - A list of zones the Application Gateway should be available in. + A list of zones the Application Gateway should be available in. For non-zonal deployments this should be set to an empty list, + as `null` will enforce the default value. **Note!** \ - This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, - pinned to a single zone or zone-redundant (so available in all zones in a region). - Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, - but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during - next `terraform apply` as there will be difference between the state and the actual configuration. + This is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to + a single zone or zone-redundant (so available in all zones in a region). + + Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the + Public IP will be created in all zones anyway. This fact will cause Terraform to recreate the IP resource during next + `terraform apply` as there will be difference between the state and the actual configuration. For details on zones currently available in a region of your choice refer to [Microsoft's documentation](https://docs.microsoft.com/en-us/azure/availability-zones/az-region). - - Example: - ``` - zones = ["1","2","3"] - ``` EOF default = ["1", "2", "3"] type = list(string) - validation { - condition = var.zones == null || length(setsubtract(var.zones, ["1", "2", "3"])) == 0 - error_message = "The `var.zones` can either be a non empty list of Availability Zones or explicit `null`." - } } variable "public_ip" { description = <<-EOF - A map defining a Public IP address resource that the Application Gateway will use to listen for incoming requests. + A map defining listener's public IP configuration. Following properties are available: - - - `name` - (`string`, required) name of the created or source Public IP resource - - `create` - (`bool`, optional, defaults to `true`) controls if the public IP is created or sourced. - - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group hosting the - existing Public IP resource + - `name` - (`string`, required) name of the Public IP resource. + - `create` - (`bool`, optional, defaults to `true`) controls if the Public IP resource is created or sourced. + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group hosting the Public IP resource, + used only for sourced resources. EOF type = object({ name = string @@ -69,31 +63,71 @@ variable "public_ip" { variable "domain_name_label" { description = <<-EOF - Label for the Domain Name. - - Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created - for the public IP in the Microsoft Azure DNS system." + A label for the Domain Name. Will be used to make up the FQDN. + If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system. EOF default = null type = string } +variable "capacity" { + description = <<-EOF + A map defining whether static or autoscale configuration is used. + + Following properties are available: + - `static` - (`number`, optional, defaults to `2`) static number of Application Gateway instances, takes values bewteen 1 + and 125. + - `autoscale` - (`map`, optional, defaults to `null`) autoscaling configuration, when specified `static` is being ignored: + - `min` - (`number`, required) minimum number of instances during autoscaling. + - `max` - (`number`, required) maximum number of instances during autoscaling. + EOF + default = {} + nullable = false + type = object({ + static = optional(number, 2) + autoscale = optional(object({ + min = number + max = number + })) + }) + validation { # static + condition = var.capacity.static >= 1 && var.capacity.static <= 125 + error_message = <<-EOF + The `capacity.static` property can take values between 1 and 125. + EOF + } + validation { # autoscale + condition = var.capacity.autoscale == null ? true : ( + ( + var.capacity.autoscale.min >= 1 && var.capacity.autoscale.min <= 125 + ) && ( + var.capacity.autoscale.max >= 1 && var.capacity.autoscale.max <= 125 + ) && ( + var.capacity.autoscale.min < var.capacity.autoscale.max + ) + ) + error_message = <<-EOF + The `min` and `max` properties of the `capacity.autoscale` property can take values between 1 and 125 and `min` value has to + be lower then `max`. + EOF + } +} + variable "enable_http2" { description = "Enable HTTP2 on the Application Gateway." default = false - nullable = false type = bool } variable "waf" { description = <<-EOF - Object sets only the SKU and provide basic WAF (Web Application Firewall) configuration for Application Gateway. + A map defining only the SKU and providing basic WAF (Web Application Firewall) configuration for Application Gateway. This + module does not support WAF rules configuration and advanced WAF settings. - This module does not support WAF rules configuration and advanced WAF settings. - Only below attributes are allowed: - - `prevention_mode` - (`bool`, required) `true` if WAF mode is Prevention, `false` for Detection mode - - `rule_set_type` - (`string`, optional, defaults to `OWASP`) The Type of the Rule Set used for this Web Application Firewall - - `rule_set_version` - (`string`, optional) The Version of the Rule Set used for this Web Application Firewall + Following properties are available: + - `prevention_mode` - (`bool`, required) `true` sets WAF mode to `Prevention` mode, `false` to `Detection` mode. + - `rule_set_type` - (`string`, optional, defaults to `OWASP`) the type of the Rule Set used for this WAF. + - `rule_set_version` - (`string`, optional, defaults to Azure defaults) the version of the Rule Set used for this WAF. EOF default = null type = object({ @@ -101,120 +135,87 @@ variable "waf" { rule_set_type = optional(string, "OWASP") rule_set_version = optional(string) }) - validation { - condition = var.waf != null ? contains(["OWASP", "Microsoft_BotManagerRuleSet"], var.waf.rule_set_type) : true - error_message = "For `rule_set_type` possible values are OWASP and Microsoft_BotManagerRuleSet" - } - validation { - condition = var.waf != null ? contains( - ["0.1", "1.0", "2.2.9", "3.0", "3.1", "3.2"], coalesce(var.waf.rule_set_version, "3.2")) : true - error_message = "For `rule_set_version` possible values are 0.1, 1.0, 2.2.9, 3.0, 3.1 and 3.2" - } -} - -variable "capacity" { - description = <<-EOF - Capacity configuration for Application Gateway. - - Object defines static or autoscale configuration using attributes: - - `static` - (`number`, optional) A static number of Application Gateway instances. A value bewteen 1 and 125 - or null, if autoscale configuration is provided - - `autoscale` - (`object`, optional) Autoscaling configuration (used only, if static is null) with attributes: - - `min` - (`number`, optional) Minimum capacity for autoscaling. - - `max` - (`number`, optional) Maximum capacity for autoscaling. - EOF - default = { - static = 2 - } - nullable = false - type = object({ - static = optional(number) - autoscale = optional(object({ - min = optional(number) - max = optional(number) - })) - }) - validation { - condition = coalesce(var.capacity.static, 1) >= 1 && coalesce(var.capacity.static, 125) <= 125 - error_message = "Static number of Application Gateway instances must be between 1 to 125." + validation { # rule_set_type + condition = var.waf == null ? true : contains( + ["OWASP", "Microsoft_BotManagerRuleSet"], + var.waf.rule_set_type + ) + error_message = <<-EOF + For `waf.rule_set_type` possible values are \"OWASP\" and \"Microsoft_BotManagerRuleSet\". + EOF } - validation { - condition = (var.capacity.static != null && var.capacity.autoscale == null - || var.capacity.static == null && var.capacity.autoscale != null) - error_message = "Only 1 capacity configuration can be used - static or autoscale." + validation { # rule_set_version + condition = try(var.waf.rule_set_version, null) == null ? true : contains( + ["0.1", "1.0", "2.2.9", "3.0", "3.1", "3.2"], + var.waf.rule_set_version + ) + error_message = <<-EOF + The `waf.rule_set_version` property can be one of \"0.1\", \"1.0\", \"2.2.9\", \"3.0\", \"3.1\" or \"3.2\". + EOF } } variable "managed_identities" { description = <<-EOF A list of existing User-Assigned Managed Identities. - - Application Gateway uses Managed Identities to retrieve certificates from Key Vault. - These identities have to have at least `GET` access to Key Vault's secrets. - Otherwise Application Gateway will not be able to use certificates stored in the Vault. + + **Note!** \ + Application Gateway uses Managed Identities to retrieve certificates from a Key Vault. These identities have to have at least + `GET` access to Key Vault's secrets. Otherwise Application Gateway will not be able to use certificates stored in the Vault. EOF default = null type = list(string) } -variable "subnet_id" { +variable "global_ssl_policy" { description = <<-EOF - An ID of a subnet that will host the Application Gateway. - - Keep in mind that this subnet can contain only AppGWs and only of the same type. - EOF - type = string -} + A map defining global SSL settings. -variable "ssl_global" { - description = <<-EOF - Global SSL settings. - - SSL settings are defined by attributes: - - `ssl_policy_type` - (`string`, required) type of an SSL policy. Possible values are `Predefined` - or `Custom` or `CustomV2`. If the value is `Custom` the following values are mandatory: - `ssl_policy_cipher_suites` and `ssl_policy_min_protocol_version`. - - `ssl_policy_name` - (`string`, optional) name of an SSL policy. - Supported only for `ssl_policy_type` set to `Predefined`. - Normally you can set it also for `Custom` policies but the name is discarded - on Azure side causing an update to Application Gateway each time terraform code is run. - Therefore this property is omitted in the code for `Custom` policies. - For the `Predefined` policies, check the Microsoft documentation - https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview - for possible values as they tend to change over time. - The default value is currently (Q1 2023) a Microsoft's default. - - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy. - Required only for `ssl_policy_type` set to `Custom`. - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. - Required only for `ssl_policy_type` set to `Custom`. - For possible values see documentation: - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites + Following properties are available: + - `type` - (`string`, required, but defaults to `Predefined`) type of an SSL policy, possible values include: + `Predefined`, `Custom` or `CustomV2`. + - `name` - (`string`, optional, defaults to `AppGwSslPolicy20220101S`) name of an SSL policy, supported only + for `type` set to `Predefined`. + + **Note!** \ + Normally you can set it also for `Custom` policies but the name is discarded on Azure side causing an update to Application + Gateway each time Terraform code is run. Therefore this property is omitted in the code for `Custom` policies. + + For the `Predefined` policies, check the + [Microsoft documentation](https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-ssl-policy-overview) + for possible values as they tend to change over time. The default value is currently (Q1 2023) is also Microsoft's default. + + - `min_protocol_version` - (`string`, optional, defaults to `null`) minimum version of the TLS protocol for SSL Policy, + required only for `type` set to `Custom`. + - `cipher_suites` - (`list`, optional, defaults to `[]`) a list of accepted cipher suites, required only for `type` set + to `Custom`. For possible values see [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway#cipher_suites). EOF - default = { - ssl_policy_type = "Predefined" - ssl_policy_name = "AppGwSslPolicy20220101S" - ssl_policy_min_protocol_version = null - ssl_policy_cipher_suites = [] - } - nullable = false + default = {} + nullable = false type = object({ - ssl_policy_type = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) + type = optional(string, "Predefined") + name = optional(string, "AppGwSslPolicy20220101S") + min_protocol_version = optional(string) + cipher_suites = optional(list(string), []) }) - validation { - condition = contains(["Predefined", "Custom", "CustomV2"], var.ssl_global.ssl_policy_type) - error_message = "For global SSL settings possible types are Predefined, Custom and CustomV2." - } - validation { - condition = contains(["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], - coalesce(var.ssl_global.ssl_policy_min_protocol_version, "TLSv1_3")) - error_message = "For global SSL settings possible min protocol versions are TLSv1_0, TLSv1_1, TLSv1_2 and TLSv1_3." - } - validation { - condition = length(setsubtract(coalesce(var.ssl_global.ssl_policy_cipher_suites, []), - ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + validation { # type + condition = contains(["Predefined", "Custom", "CustomV2"], var.global_ssl_policy.type) + error_message = <<-EOF + The `global_ssl_policy.type` property can be one of: \"Predefined\", \"Custom\" and \"CustomV2\". + EOF + } + validation { # min_protocol_version + condition = var.global_ssl_policy.min_protocol_version == null ? true : contains( + ["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], var.global_ssl_policy.min_protocol_version + ) + error_message = <<-EOF + The `global_ssl_policy.min_protocol_version` property can be one of: \"TLSv1_0\", \"TLSv1_1\", \"TLSv1_2\" and \"TLSv1_3\". + EOF + } + validation { # cipher_suites + condition = length(setsubtract(var.global_ssl_policy.cipher_suites, + [ + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", @@ -224,18 +225,21 @@ variable "ssl_global" { "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384"])) == 0 + "TLS_RSA_WITH_AES_256_GCM_SHA384" + ]) + ) == 0 error_message = <<-EOF - For global SSL settings possible cipher suites are: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, - TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, - TLS_RSA_WITH_AES_256_CBC_SHA256 and TLS_RSA_WITH_AES_256_GCM_SHA384." + For global SSL settings possible cipher suites are: \"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA\", + \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA\", \"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256\", \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA\", + \"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256\", \"TLS_DHE_RSA_WITH_AES_128_CBC_SHA\", \"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256\", + \"TLS_DHE_RSA_WITH_AES_256_CBC_SHA\", \"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA\", + \"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256\", + \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA\", \"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384\", + \"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA\", + \"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256\", \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA\", + \"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384\", \"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384\", \"TLS_RSA_WITH_3DES_EDE_CBC_SHA\", + \"TLS_RSA_WITH_AES_128_CBC_SHA\", \"TLS_RSA_WITH_AES_128_CBC_SHA256\", \"TLS_RSA_WITH_AES_128_GCM_SHA256\", + \"TLS_RSA_WITH_AES_256_CBC_SHA\", \"TLS_RSA_WITH_AES_256_CBC_SHA256\", \"TLS_RSA_WITH_AES_256_GCM_SHA384\"." EOF } } @@ -245,47 +249,62 @@ variable "ssl_profiles" { A map of SSL profiles. SSL profiles can be later on referenced in HTTPS listeners by providing a name of the profile in the `name` property. - For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` - variables as SSL profile is a named SSL policy - same properties apply. + For possible values check the: `ssl_policy_type`, `ssl_policy_min_protocol_version` and `ssl_policy_cipher_suites` properties + as SSL profile is a named SSL policy - same properties apply. The only difference is that you cannot name an SSL policy inside an SSL profile. - Every SSL profile contains attributes: - - `name` - (`string`, required) name of the SSL profile - - `ssl_policy_name` - (`string`, optional) name of predefined policy - - `ssl_policy_min_protocol_version` - (`string`, optional) the minimal TLS version. - - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites. + Every SSL profile contains following attributes: + + - `name` - (`string`, required) name of the SSL profile. + - `ssl_policy_name` - (`string`, optional, defaults to `null`) name of predefined policy. + - `ssl_policy_min_protocol_version` - (`string`, optional, defaults to `null`) the minimal TLS version. + - `ssl_policy_cipher_suites` - (`list`, optional, defaults to `null`) a list of accepted cipher suites. EOF + default = {} + nullable = false type = map(object({ name = string ssl_policy_name = optional(string) ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) })) - validation { + validation { # name + condition = (length(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])) == + length(distinct(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])))) + error_message = <<-EOF + The `name` property has to be unique among all SSL profiles. + EOF + } + validation { # ssl_policy_min_protocol_version condition = alltrue(flatten([ - for _, ssl_profile in var.ssl_profiles : [ - contains(["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], coalesce(ssl_profile.ssl_policy_min_protocol_version, "TLSv1_3")) - ]])) - error_message = "Possible values for `ssl_policy_min_protocol_version` are TLSv1_0, TLSv1_1, TLSv1_2 and TLSv1_3." + for _, ssl_profile in var.ssl_profiles : + contains(["TLSv1_0", "TLSv1_1", "TLSv1_2", "TLSv1_3"], ssl_profile.ssl_policy_min_protocol_version) + if ssl_profile.ssl_policy_min_protocol_version != null + ])) + error_message = <<-EOF + Possible values for `ssl_policy_min_protocol_version` are TLSv1_0, TLSv1_1, TLSv1_2 and TLSv1_3. + EOF } - validation { + validation { # ssl_policy_cipher_suites condition = alltrue(flatten([ - for _, ssl_profile in var.ssl_profiles : [ - length(setsubtract(coalesce(ssl_profile.ssl_policy_cipher_suites, []), - ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", - "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", - "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384"] - )) == 0 - ]])) + for _, ssl_profile in var.ssl_profiles : + length(setsubtract(ssl_profile.ssl_policy_cipher_suites, + [ + "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", + "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384" + ]) + ) == 0 + if ssl_profile.ssl_policy_cipher_suites != null + ])) error_message = <<-EOF Possible values for `ssl_policy_cipher_suites` are TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, @@ -299,15 +318,10 @@ variable "ssl_profiles" { TLS_RSA_WITH_AES_256_CBC_SHA256 and TLS_RSA_WITH_AES_256_GCM_SHA384. EOF } - validation { - condition = (length(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])) == - length(distinct(flatten([for _, ssl_profile in var.ssl_profiles : ssl_profile.name])))) - error_message = "The `name` property has to be unique among all SSL profiles." - } } variable "frontend_ip_configuration_name" { - description = "Frontend IP configuration name" + description = "A frontend IP configuration name." default = "public_ipconfig" type = string } @@ -317,18 +331,20 @@ variable "listeners" { A map of listeners for the Application Gateway. Every listener contains attributes: - - `name` - (`string`, required) The name for this Frontend Port. - - `port` - (`string`, required) The port used for this Frontend Port. - - `protocol` - (`string`, optional) The Protocol to use for this HTTP Listener. - - `host_names` - (`list`, optional) A list of Hostname(s) should be used for this HTTP Listener. - It allows special wildcard characters. - - `ssl_profile_name` - (`string`, optional) The name of the associated SSL Profile which should be used - for this HTTP Listener. - - `ssl_certificate_path` - (`string`, optional) Path to the file with tThe base64-encoded PFX certificate data. - - `ssl_certificate_pass` - (`string`, optional) Password for the pfx file specified in data. - - `ssl_certificate_vault_id` - (`string`, optional) Secret Id of (base-64 encoded unencrypted pfx) Secret + + - `name` - (`string`, required) the name for this Frontend Port. + - `port` - (`string`, required) the port used for this Frontend Port. + - `protocol` - (`string`, optional, defaults to `Https`) the Protocol to use for this HTTP Listener. + - `host_names` - (`list`, optional, defaults to `null`) A list of Hostname(s) should be used for this HTTP + Listener, it allows special wildcard characters. + - `ssl_profile_name` - (`string`, optional, defaults to `null`) the name of the associated SSL Profile which should be + used for this HTTP Listener. + - `ssl_certificate_vault_id` - (`string`, optional, defaults to `null`) Secret Id of (base-64 encoded unencrypted pfx) Secret or Certificate object stored in Azure KeyVault. - - `custom_error_pages` - (`map`, optional) Map of string, where key is HTTP status code and value is + - `ssl_certificate_path` - (`string`, optional, defaults to `null`) Path to the file with tThe base64-encoded PFX + certificate data. + - `ssl_certificate_pass` - (`string`, optional, defaults to `null`) Password for the pfx file specified in data. + - `custom_error_pages` - (`map`, optional, defaults to `{}`) Map of string, where key is HTTP status code and value is error page URL of the application gateway customer error. EOF type = map(object({ @@ -337,135 +353,151 @@ variable "listeners" { protocol = optional(string, "Http") host_names = optional(list(string)) ssl_profile_name = optional(string) + ssl_certificate_vault_id = optional(string) ssl_certificate_path = optional(string) ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) custom_error_pages = optional(map(string), {}) })) - validation { - condition = alltrue(flatten([ - for _, listener in var.listeners : [ - contains(["Http", "Https"], listener.protocol) - ]])) - error_message = "Possible values for `protocol` are `Http` and `Https`." + validation { # name + condition = (length(flatten([for _, listener in var.listeners : listener.name])) == + length(distinct(flatten([for _, listener in var.listeners : listener.name])))) + error_message = <<-EOF + The `name` property has to be unique among all listeners. + EOF } - validation { + validation { # port condition = alltrue(flatten([ for _, listener in var.listeners : (listener.port >= 1 && listener.port <= 65535) ])) - error_message = "The listener `port` should be a valid TCP port number from 1 to 65535." + error_message = <<-EOF + The listener `port` should be a valid TCP port number from 1 to 65535. + EOF + } + validation { # protocol + condition = alltrue(flatten([ + for _, listener in var.listeners : [ + contains(["Http", "Https"], listener.protocol) + ]])) + error_message = <<-EOF + Possible values for `protocol` are `Http` and `Https`. + EOF } - validation { + validation { # ssl_certificate_vault_id & ssl_certificate_path condition = alltrue(flatten([ for _, listener in var.listeners : (listener.protocol == "Https" ? try(length(coalesce(listener.ssl_certificate_vault_id, listener.ssl_certificate_path)), -1) > 0 : true) ])) - error_message = "If `Https` protocol is used, then SSL certificate (from file or Azure Key Vault) is required" + error_message = <<-EOF + If `Https` protocol is used, then SSL certificate (from file or Azure Key Vault) is required. + EOF } - validation { + validation { # ssl_certificate_pass condition = alltrue(flatten([ for _, listener in var.listeners : (listener.protocol == "Https" ? try(length(listener.ssl_certificate_pass), -1) >= 0 : true) ])) - error_message = "If `Https` protocol is used, then SSL certificate password is required" - } - validation { - condition = (length(flatten([for _, listener in var.listeners : listener.name])) == - length(distinct(flatten([for _, listener in var.listeners : listener.name])))) - error_message = "The `name` property has to be unique among all listeners." + error_message = <<-EOF + If `Https` protocol is used, then SSL certificate password is required. + EOF } } variable "backend_pool" { description = <<-EOF - Backend pool. - - Object contains attributes: - - `name` - (`string`, required) name of the backend pool. - - `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backends + A map defining a backend pool, when skipped will create an empty backend. + + Following properties are available: + - `name` - (`string`, optional, defaults to `vmseries`) name of the backend pool. + - `vmseries_ips` - (`list`, optional, defaults to `[]`) IP addresses of VM-Series' interfaces that will serve as backend nodes for the Application Gateway. + EOF - default = { - name = "vmseries" - } - nullable = false + default = {} + nullable = false type = object({ - name = string + name = optional(string, "vmseries") vmseries_ips = optional(list(string), []) }) } -variable "backends" { +variable "backend_settings" { description = <<-EOF A map of backend settings for the Application Gateway. Every backend contains attributes: - - `name` - (`string`, optional) The name of the backend settings - - `path` - (`string`, optional) The Path which should be used as a prefix for all HTTP requests. - - `hostname_from_backend` - (`bool`, optional) Whether host header should be picked from the host name of the backend server. - - `hostname` - (`string`, optional) Host header to be sent to the backend servers. - - `port` - (`number`, optional) The port which should be used for this Backend HTTP Settings Collection. - - `protocol` - (`string`, optional) The Protocol which should be used. Possible values are Http and Https. - - `timeout` - (`number`, optional) The request timeout in seconds, which must be between 1 and 86400 seconds. - - `cookie_based_affinity` - (`string`, optional) Is Cookie-Based Affinity enabled? Possible values are Enabled and Disabled. - - `affinity_cookie_name` - (`string`, optional) The name of the affinity cookie. - - `probe` - (`string`, optional) Probe's key. - - `root_certs` - (`map`, optional) A list of trusted_root_certificate names. + + - `name` - (`string`, required) the name of the backend settings. + - `port` - (`number`, required) the port which should be used for this Backend HTTP Settings Collection. + - `protocol` - (`string`, required) the Protocol which should be used. Possible values are Http and Https. + - `path` - (`string`, optional, defaults to `null`) the Path which should be used as a prefix for all HTTP + requests. + - `hostname_from_backend` - (`bool`, optional, defaults to `false`) whether host header should be picked from the host name + of the backend server. + - `hostname` - (`string`, optional, defaults to `null`) host header to be sent to the backend servers. + - `timeout` - (`number`, optional, defaults to `60`) the request timeout in seconds, which must be between 1 + and 86400 seconds. + - `use_cookie_based_affinity` - (`bool`, optional, defaults to `true`) when set to `true` enables Cookie-Based Affinity. + - `affinity_cookie_name` - (`string`, optional, defaults to Azure defaults) the name of the affinity cookie. + - `probe_key` - (`string`, optional, defaults to `null`) a key identifying a Probe definition in the + `var.probes`. + - `root_certs` - (`map`, optional, defaults to `{}`) a map of objects defining paths to trusted root + certificates (`PEM` format), each map contains 2 properties: + - `name` - (`string`, required) a name of the certificate. + - `path` - (`string`, required) path to a file on a local file system containing the root cert. EOF - default = { - "minimum" = { - name = "minimum" - } - } - nullable = false + default = {} + nullable = false type = map(object({ - name = optional(string) - path = optional(string) - hostname_from_backend = optional(bool, false) - hostname = optional(string) - port = optional(number, 80) - protocol = optional(string, "Http") - timeout = optional(number, 60) - cookie_based_affinity = optional(string, "Enabled") - affinity_cookie_name = optional(string) - probe = optional(string) + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(bool, false) + hostname = optional(string) + timeout = optional(number, 60) + use_cookie_based_affinity = optional(bool, true) + affinity_cookie_name = optional(string) + probe_key = optional(string) root_certs = optional(map(object({ name = string path = string })), {}) })) - validation { - condition = alltrue(flatten([ - for _, backend in var.backends : [ - contains(["Http", "Https"], backend.protocol) - ]])) - error_message = "Possible values for `protocol` are `Http` and `Https`." + validation { # name + condition = (length(flatten([for _, backend in var.backend_settings : backend.name])) == + length(distinct(flatten([for _, backend in var.backend_settings : backend.name])))) + error_message = <<-EOF + The `name` property has to be unique among all backends. + EOF } - validation { + validation { # port condition = alltrue(flatten([ - for _, backend in var.backends : [ - contains(["Enabled", "Disabled"], backend.cookie_based_affinity) - ]])) - error_message = "Possible values for `cookie_based_affinity` are Enabled and Disabled." + for _, backend in var.backend_settings : (backend.port >= 1 && backend.port <= 65535) + ])) + error_message = <<-EOF + The backend `port` should be a valid TCP port number from 1 to 65535. + EOF } - validation { + validation { # protocol condition = alltrue(flatten([ - for _, backend in var.backends : (backend.port >= 1 && backend.port <= 65535) - ])) - error_message = "The backend `port` should be a valid TCP port number from 1 to 65535." + for _, backend in var.backend_settings : [ + contains(["Http", "Https"], backend.protocol) + ]])) + error_message = <<-EOF + Possible values for `protocol` are `Http` and `Https`. + EOF } - validation { + validation { # timeout condition = alltrue(flatten([ - for _, backend in var.backends : (backend.timeout != null ? backend.timeout >= 1 && backend.timeout <= 86400 : true) + for _, backend in var.backend_settings : ( + backend.timeout != null ? backend.timeout >= 1 && backend.timeout <= 86400 : true + ) ])) - error_message = "The backend `timeout` property should can take values between 1 and 86400 (seconds)." - } - validation { - condition = (length(flatten([for _, backend in var.backends : backend.name])) == - length(distinct(flatten([for _, backend in var.backends : backend.name])))) - error_message = "The `name` property has to be unique among all backends." + error_message = <<-EOF + The backend `timeout` property should can take values between 1 and 86400 (seconds). + EOF } } @@ -474,19 +506,23 @@ variable "probes" { A map of probes for the Application Gateway. Every probe contains attributes: - - `name` - (`string`, required) The name used for this Probe - - `path` - (`string`, required) The path used for this Probe - - `host` - (`string`, optional) The hostname used for this Probe - - `port` - (`number`, optional) Custom port which will be used for probing the backend servers. - - `protocol` - (`string`, optional, defaults `Http`) The protocol which should be used. - - `interval` - (`number`, optional, defaults `5`) The interval between two consecutive probes in seconds. - - `timeout` - (`number`, optional, defaults `30`) The timeout used for this Probe, - which indicates when a probe becomes unhealthy. - - `threshold` - (`number`, optional, defaults `2`) The unhealthy Threshold for this Probe, which indicates - the amount of retries which should be attempted before a node is deemed unhealthy. - - `match_code` - (`list`, optional) The list of allowed status codes for this Health Probe. - - `match_body` - (`string`, optional) A snippet from the Response Body which must be present in the Response. + + - `name` - (`string`, required) the name used for this Probe. + - `path` - (`string`, required) the path used for this Probe. + - `host` - (`string`, optional, defaults to `null`) the hostname used for this Probe. + - `port` - (`number`, optional, defaults to `null`) custom port which will be used for probing the backend servers, when + skipped a default port for `protocol` will be used. + - `protocol` - (`string`, optional, defaults `Http`) the protocol which should be used, possible values are `Http` or `Https`. + - `interval` - (`number`, optional, defaults `5`) the interval between two consecutive probes in seconds. + - `timeout` - (`number`, optional, defaults `30`) the timeout after which a single probe is marked unhealthy. + - `threshold` - (`number`, optional, defaults `2`) the unhealthy Threshold for this Probe, which indicates the amount of + retries which should be attempted before a node is deemed unhealthy. + - `match_code` - (`list`, optional, defaults to `null`) custom list of allowed status codes for this Health Probe. + - `match_body` - (`string`, optional, defaults to `null`) a custom snippet from the Response Body which must be present to + treat a single probe as healthy. EOF + default = {} + nullable = false type = map(object({ name = string path = string @@ -499,41 +535,53 @@ variable "probes" { match_code = optional(list(number)) match_body = optional(string) })) - validation { - condition = var.probes != null ? alltrue(flatten([ - for _, backend in var.probes : [ - contains(["Http", "Https"], backend.protocol) - ]])) : true - error_message = "Possible values for `protocol` are `Http` and `Https`." - } - validation { + validation { # name condition = (length(flatten([for _, probe in var.probes : probe.name])) == length(distinct(flatten([for _, probe in var.probes : probe.name])))) - error_message = "The `name` property has to be unique among all probes." + error_message = <<-EOF + The `name` property has to be unique among all probes. + EOF } - validation { + validation { # port condition = alltrue(flatten([ for _, probe in var.probes : ((coalesce(probe.port, 80)) >= 1 && (coalesce(probe.port, 80)) <= 65535) ])) - error_message = "The probe `port` should be a valid TCP port number from 1 to 65535." + error_message = <<-EOF + The probe `port` should be a valid TCP port number from 1 to 65535. + EOF } - validation { + validation { # protocol condition = alltrue(flatten([ - for _, probe in var.probes : (probe.timeout != null ? probe.timeout >= 1 && probe.timeout <= 86400 : true) - ])) - error_message = "The probe `timeout` property should can take values between 1 and 86400 (seconds)." + for _, probe in var.probes : [ + contains(["Http", "Https"], probe.protocol) + ]])) + error_message = <<-EOF + Possible values for `protocol` are `Http` and `Https`. + EOF } - validation { + validation { # interval condition = alltrue(flatten([ for _, probe in var.probes : (probe.interval != null ? probe.interval >= 1 && probe.interval <= 86400 : true) ])) - error_message = "The probe `interval` property should can take values between 1 and 86400 (seconds)." + error_message = <<-EOF + The probe `interval` property should can take values between 1 and 86400 (seconds). + EOF + } + validation { # timeout + condition = alltrue(flatten([ + for _, probe in var.probes : (probe.timeout != null ? probe.timeout >= 1 && probe.timeout <= 86400 : true) + ])) + error_message = <<-EOF + The probe `timeout` property should can take values between 1 and 86400 (seconds). + EOF } - validation { + validation { # threshold condition = alltrue(flatten([ for _, probe in var.probes : (probe.threshold != null ? probe.threshold >= 1 && probe.threshold <= 20 : true) ])) - error_message = "The probe `threshold` property should can take values between 1 and 20." + error_message = <<-EOF + The probe `threshold` property should can take values between 1 and 20. + EOF } } @@ -542,21 +590,23 @@ variable "rewrites" { A map of rewrites for the Application Gateway. Every rewrite contains attributes: - - `name` - (`string`) Rewrite Rule Set name - - `rules` - (`object`, optional) Rewrite Rule Set defined with attributes: - - `name` - (`string`, required) Rewrite Rule name. - - `sequence` - (`number`, required) Rule sequence of the rewrite rule that determines - the order of execution in a set. - - `conditions` - (`map`, optional) One or more condition blocks as defined below: - - `pattern` - (`string`, required) The pattern, either fixed string or regular expression, - that evaluates the truthfulness of the condition. - - `ignore_case` - (`string`, optional, defaults to `false`) Perform a case in-sensitive comparison. - - `negate` - (`bool`, optional, defaults to `false`) Negate the result of the condition evaluation. - - `request_headers` - (`map`, optional) Map of request header, where header name is the key, - header value is the value of the object in the map. - - `response_headers`- (`map`, optional) Map of response header, where header name is the key, - header value is the value of the object in the map. + + - `name` - (`string`, required) Rewrite Rule Set name. + - `rules` - (`map`, required) rewrite Rule Set defined with following attributes available: + - `name` - (`string`, required) Rewrite Rule name. + - `sequence` - (`number`, required) determines the order of rule execution in a set. + - `conditions` - (`map`, optional, defaults to `{}`) one or more condition blocks as defined below: + - `pattern` - (`string`, required) the pattern, either fixed string or regular expression, that evaluates the + truthfulness of the condition. + - `ignore_case` - (`string`, optional, defaults to `false`) perform a case in-sensitive comparison. + - `negate` - (`bool`, optional, defaults to `false`) negate the result of the condition evaluation. + - `request_headers` - (`map`, optional, defaults to `{}`) map of request headers, where header name is the key, header value + is the value. + - `response_headers` - (`map`, optional, defaults to `{}`) map of response header, where header name is the key, header value + is the value. EOF + default = {} + nullable = false type = map(object({ name = string rules = optional(map(object({ @@ -569,61 +619,14 @@ variable "rewrites" { })), {}) request_headers = optional(map(string), {}) response_headers = optional(map(string), {}) - }))) + })), {}) })) - validation { + validation { # name condition = (length(flatten([for _, rewrite in var.rewrites : rewrite.name])) == length(distinct(flatten([for _, rewrite in var.rewrites : rewrite.name])))) - error_message = "The `name` property has to be unique among all rewrites." - } -} - -variable "rules" { - description = <<-EOF - A map of rules for the Application Gateway. - - A rule combines, backend, listener, rewrites and redirects configurations. - A key is an application name that is used to prefix all components inside Application Gateway - that are created for this application. - - Every rule contains attributes: - - `name` - (`string`, required) Rule name. - - `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value - from 1 to 20000 with 1 being the highest priority and 20000 being the lowest priority. - - `backend` - (`string`, optional) Backend settings` key - - `listener` - (`string`, required) Listener's key - - `rewrite` - (`string`, optional) Rewrite's key - - `url_path_map` - (`string`, optional) URL Path Map's key - - `redirect` - (`string`, optional) Redirect's key - EOF - type = map(object({ - name = string - priority = number - backend = optional(string) - listener = string - rewrite = optional(string) - url_path_map = optional(string) - redirect = optional(string) - })) - validation { - condition = alltrue(flatten([ - for _, rule in var.rules : [ - rule.priority >= 1, rule.priority <= 20000 - ]])) - error_message = "Rule priority is integer value from 1 to 20000." - } - validation { - condition = (length(flatten([for _, rule in var.rules : rule.name])) == - length(distinct(flatten([for _, rule in var.rules : rule.name])))) - error_message = "The `name` property has to be unique among all rules." - } - validation { - condition = alltrue([for _, rule in var.rules : - rule.backend != null && rule.redirect == null && rule.url_path_map == null || - rule.backend == null && rule.redirect != null && rule.url_path_map == null || - rule.backend == null && rule.redirect == null && rule.url_path_map != null - ]) - error_message = "Either `backend`, `redirect` or `url_path_map` is required, but not all as they are mutually exclusive." + error_message = <<-EOF + The `name` property has to be unique among all rewrites. + EOF } } @@ -632,33 +635,51 @@ variable "redirects" { A map of redirects for the Application Gateway. Every redirect contains attributes: - - `name` - (`string`, required) The name of redirect. - - `type` - (`string`, required) The type of redirect. - Possible values are Permanent, Temporary, Found and SeeOther - - `target_listener` - (`string`, optional) The name of the listener to redirect to. - - `target_url` - (`string`, optional) The URL to redirect the request to. - - `include_path` - (`bool`, optional) Whether or not to include the path in the redirected URL. - - `include_query_string` - (`bool`, optional) Whether or not to include the query string in the redirected URL. + - `name` - (`string`, required) the name of redirect. + - `type` - (`string`, required) the type of redirect, possible values are `Permanent`, `Temporary`, `Found` and + `SeeOther`. + - `target_listener_key` - (`string`, optional, mutually exclusive with `target_url`) a key identifying a backend config + defined in `var.listeners`. + - `target_url` - (`string`, optional, mutually exclusive with `target_listener`) the URL to redirect to. + - `include_path` - (`bool`, optional, defaults to Azure defaults) whether or not to include the path in the redirected + URL. + - `include_query_string` - (`bool`, optional, defaults to Azure defaults) whether or not to include the query string in the + redirected URL. EOF + default = {} + nullable = false type = map(object({ name = string type = string - target_listener = optional(string) + target_listener_key = optional(string) target_url = optional(string) - include_path = optional(bool, false) - include_query_string = optional(bool, false) + include_path = optional(bool) + include_query_string = optional(bool) })) - validation { + validation { # name + condition = (length(flatten([for _, redirect in var.redirects : redirect.name])) == + length(distinct(flatten([for _, redirect in var.redirects : redirect.name])))) + error_message = <<-EOF + The `name` property has to be unique among all redirects. + EOF + } + validation { # type condition = var.redirects != null ? alltrue(flatten([ for _, redirect in var.redirects : [ contains(["Permanent", "Temporary", "Found", "SeeOther"], coalesce(redirect.type, "Permanent")) ]])) : true - error_message = "Possible values for `type` are Permanent, Temporary, Found and SeeOther." + error_message = <<-EOF + Possible values for `type` are \"Permanent\", \"Temporary\", \"Found\" and \"SeeOther\". + EOF } - validation { - condition = (length(flatten([for _, redirect in var.redirects : redirect.name])) == - length(distinct(flatten([for _, redirect in var.redirects : redirect.name])))) - error_message = "The `name` property has to be unique among all redirects." + validation { # target_listener_key & target_url + condition = alltrue(flatten([ + for _, r in var.redirects : + r.target_listener_key != null && r.target_url == null || r.target_listener_key == null && r.target_url != null + ])) + error_message = <<-EOF + At least one and only one property can be defined, either \"target_listener_key\" or \"target_url\". + EOF } } @@ -667,25 +688,123 @@ variable "url_path_maps" { A map of URL path maps for the Application Gateway. Every URL path map contains attributes: - - `name` - (`string`, required) The name of redirect. - - `backend` - (`string`, required) The default backend for redirect. - - `path_rules` - (`map`, optional) The map of rules, where every object has attributes: - - `paths` - (`list`, required) List of paths - - `backend` - (`string`, optional) Backend's key - - `redirect` - (`string`, optional) Redirect's key + - `name` - (`string`, required) the name of redirect. + - `backend_key` - (`string`, required) a key identifying the default backend for redirect defined in `var.backend_settings`. + - `path_rules` - (`map`, optional, defaults to `{}`) the map of rules, where every object has attributes: + - `paths` - (`list`, required) a list of paths. + - `backend_key` - (`string`, optional, mutually exclusive with `redirect_key`) a key identifying a backend config defined + in `var.backend_settings`. + - `redirect_key` - (`string`, optional, mutually exclusive with `backend_key`) a key identifying a redirect config defined + in `var.redirects`. EOF + default = {} + nullable = false type = map(object({ - name = string - backend = string + name = string + backend_key = string path_rules = optional(map(object({ - paths = list(string) - backend = optional(string) - redirect = optional(string) - }))) + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + })), {}) })) - validation { + validation { # name condition = (length(flatten([for _, url_path_map in var.url_path_maps : url_path_map.name])) == length(distinct(flatten([for _, url_path_map in var.url_path_maps : url_path_map.name])))) - error_message = "The `name` property has to be unique among all URL path maps." + error_message = <<-EOF + The `name` property has to be unique among all URL path maps. + EOF + } + validation { # path_rules + condition = alltrue(flatten([ + for _, url in var.url_path_maps : [ + for _, rule in url.path_rules : + rule.backend_key != null && rule.redirect_key == null || rule.backend_key == null && rule.redirect_key != null + ] + ])) + error_message = <<-EOF + At least one and only one property can be defined, either \"backend_key\" or \"redirect_key\". + EOF + } +} + +variable "rules" { + description = <<-EOF + A map of rules for the Application Gateway. A rule combines backend's, listener's, rewrites' and redirects' configurations. + + A key is an application name that is used to prefix all components inside an Application Gateway + that are created for this application. + + Every rule contains following attributes: + + - `name` - (`string`, required) Rule name. + - `priority` - (`string`, required) Rule evaluation order can be dictated by specifying an integer value from 1 to + 20000 with 1 being the highest priority and 20000 being the lowest priority. + - `listener_key` - (`string`, required) a key identifying a listener config defined in `var.listeners`. + - `backend_key` - (`string`, optional, mutually exclusive with `url_path_map_key` and `redirect_key`) a key identifying a + backend config defined in `var.backend_settings`. + - `rewrite_key` - (`string`, optional, defaults to `null`) a key identifying a rewrite config defined in `var.rewrites`. + - `url_path_map_key` - (`string`, optional, mutually exclusive with `backend_key` and `redirect_key`) a key identifying a + url_path_map config defined in `var.url_path_maps`. + - `redirect_key` - (`string`, optional, mutually exclusive with `url_path_map_key` and `backend_key`) a key identifying a + redirect config defined in `var.redirects`. + EOF + type = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + validation { # name + condition = (length(flatten([for _, rule in var.rules : rule.name])) == + length(distinct(flatten([for _, rule in var.rules : rule.name])))) + error_message = <<-EOF + The `name` property has to be unique among all rules. + EOF + } + validation { # priority + condition = alltrue(flatten([ + for _, rule in var.rules : [ + rule.priority >= 1, rule.priority <= 20000 + ]])) + error_message = <<-EOF + The `priority` property is an integer value from 1 to 20000. + EOF + } + validation { # priority + condition = alltrue([ + for _, v in var.rules : + !contains( + concat( + slice( + values(var.rules)[*].priority, + 0, + index(values(var.rules)[*].priority, v.priority) + ), + slice( + values(var.rules)[*].priority, + index(values(var.rules)[*].priority, v.priority) + 1, + length(values(var.rules)) + ) + ), + v.priority + ) + ]) + error_message = <<-EOF + The `priority` property has to be unique. + EOF + } + validation { # url_path_map_key + condition = alltrue([for _, rule in var.rules : + rule.backend_key != null && rule.redirect_key == null && rule.url_path_map_key == null || + rule.backend_key == null && rule.redirect_key != null && rule.url_path_map_key == null || + rule.backend_key == null && rule.redirect_key == null && rule.url_path_map_key != null + ]) + error_message = <<-EOF + Either `backend`, `redirect` or `url_path_map` is required, but not all as they are mutually exclusive. + EOF } } diff --git a/modules/appgw/versions.tf b/modules/appgw/versions.tf index 2c796798..9abec711 100644 --- a/modules/appgw/versions.tf +++ b/modules/appgw/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index 744187cc..b267f18b 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -252,12 +252,11 @@ Following properties are available: - `replication_type` - (`string`, optional, defaults to `LRS`) only for newly created Storage Accounts, defines the replication type used. Can be one of the following values: `LRS`, `GRS`, `RAGRS`, `ZRS`, `GZRS` or `RAGZRS`. - `kind` - (`string`, optional, defaults to `StorageV2`) only for newly created Storage Accounts, defines the - account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` or - `StorageV2`. -- `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account - tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or + account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` + or `StorageV2`. +- `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the + account tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or `FileStorage` the `tier` can only be set to `Premium`. - Type: @@ -288,7 +287,7 @@ File Shares. Files Shares API comes under this networks restrictions. Following properties are available: -- `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version +- `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version. - `allowed_public_ips` - (`list`, optional, defaults to `[]`) list of IP CIDR ranges that are allowed to access the Storage Account. Only public IPs are allowed, RFC1918 address space is not permitted. - `allowed_subnet_ids` - (`list`, optional, defaults to `[]`) list of the allowed VNet subnet ids. Note that this option @@ -297,7 +296,6 @@ Following properties are available: specific subnet. - Type: ```hcl @@ -325,9 +323,9 @@ Following options are available: `file_shares` variable are created or sourced, if the latter, the storage account also has to be sourced. - `disable_package_dirs_creation` - (`bool`, optional, defaults to `false`) for sourced File Shares, controls if the bootstrap - package folder structure is created + package folder structure is created. - `quota` - (`number`, optional, defaults to `10`) maximum size of a File Share in GB, a value between - 1 and 5120 (5TB) + 1 and 5120 (5TB). - `access_tier` - (`string`, optional, defaults to `Cool`) access tier for a File Share, can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". @@ -360,11 +358,11 @@ at the same time. Following properties are available per each File Share definition: -- `name` - (`string`, required) name of the File Share +- `name` - (`string`, required) name of the File Share. - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap package. - For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package) -- `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and build - the bootstrap package. + For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package). +- `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and + build the bootstrap package. Keys are local paths, values - remote. Only Unix like directory separator (`/`) is supported. If `bootstrap_package_path` is also specified, these files will overwrite any file uploaded from that path. @@ -382,10 +380,9 @@ Following properties are available per each File Share definition: Additionally you can override the default `quota` and `access_tier` properties per File Share (same restrictions apply): - `quota` - (`number`, optional, defaults to `var.file_shares_configuration.quota`) maximum size of a File Share in GB, - a value between 1 and 5120 (5TB) + a value between 1 and 5120 (5TB). - `access_tier` - (`string`, optional, defaults to `var.file_shares_configuration.access_tier`) access tier for a File Share, - can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". - + can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". Type: @@ -406,5 +403,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/variables.tf b/modules/bootstrap/variables.tf index feff85dc..f548355a 100644 --- a/modules/bootstrap/variables.tf +++ b/modules/bootstrap/variables.tf @@ -9,7 +9,9 @@ variable "name" { type = string validation { condition = can(regex("^[a-z0-9]{3,24}$", var.name)) - error_message = "A Storage Account name must be between 3 and 24 characters, only lower case letters and numbers are allowed." + error_message = <<-EOF + A Storage Account name must be between 3 and 24 characters, only lower case letters and numbers are allowed. + EOF } } @@ -40,12 +42,11 @@ variable "storage_account" { - `replication_type` - (`string`, optional, defaults to `LRS`) only for newly created Storage Accounts, defines the replication type used. Can be one of the following values: `LRS`, `GRS`, `RAGRS`, `ZRS`, `GZRS` or `RAGZRS`. - `kind` - (`string`, optional, defaults to `StorageV2`) only for newly created Storage Accounts, defines the - account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` or - `StorageV2`. - - `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account - tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or + account type. Can be one of the following: `BlobStorage`, `BlockBlobStorage`, `FileStorage`, `Storage` + or `StorageV2`. + - `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the + account tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or `FileStorage` the `tier` can only be set to `Premium`. - EOF default = {} nullable = false @@ -57,22 +58,31 @@ variable "storage_account" { }) validation { # replication_type condition = contains(["LRS", "GRS", "RAGRS", "ZRS", "GZRS", "RAGZRS"], var.storage_account.replication_type) - error_message = "The `replication_type` property can be one of: \"LRS\", \"GRS\", \"RAGRS\", \"ZRS\", \"GZRS\" or \"RAGZRS\"." + error_message = <<-EOF + The `replication_type` property can be one of: \"LRS\", \"GRS\", \"RAGRS\", \"ZRS\", \"GZRS\" or \"RAGZRS\". + EOF } validation { # kind - condition = contains(["BlobStorage", "BlockBlobStorage", "FileStorage", "Storage", "StorageV2"], var.storage_account.kind) + condition = contains( + ["BlobStorage", "BlockBlobStorage", "FileStorage", "Storage", "StorageV2"], var.storage_account.kind + ) error_message = <<-EOF - The `kind` property can be one of: \"BlobStorage\", \"BlockBlobStorage\", \"FileStorage\", \"Storage\" - or \"StorageV2\"." + The `kind` property can be one of: \"BlobStorage\", \"BlockBlobStorage\", \"FileStorage\", \"Storage\" or \"StorageV2\"." EOF } validation { # tier condition = contains(["Standard", "Premium"], var.storage_account.tier) - error_message = "The `tier` property can be one of: \"Standard\" or \"Premium\"." + error_message = <<-EOF + The `tier` property can be one of: \"Standard\" or \"Premium\". + EOF } - validation { # tier && kind - condition = contains(["BlockBlobStorage", "FileStorage"], var.storage_account.kind) ? var.storage_account.tier == "Premium" : true - error_message = "If the `kind` property is set to either \"BlockBlobStorage\" or \"FileStorage\", the `tier` has to be set to \"Premium\"." + validation { # kind & tier + condition = contains( + ["BlockBlobStorage", "FileStorage"], var.storage_account.kind + ) ? var.storage_account.tier == "Premium" : true + error_message = <<-EOF + If the `kind` property is set to either \"BlockBlobStorage\" or \"FileStorage\", the `tier` has to be set to \"Premium\"." + EOF } } @@ -88,14 +98,13 @@ variable "storage_network_security" { Following properties are available: - - `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version + - `min_tls_version` - (`string`, optional, defaults to `TLS1_2`) minimum supported TLS version. - `allowed_public_ips` - (`list`, optional, defaults to `[]`) list of IP CIDR ranges that are allowed to access the Storage Account. Only public IPs are allowed, RFC1918 address space is not permitted. - `allowed_subnet_ids` - (`list`, optional, defaults to `[]`) list of the allowed VNet subnet ids. Note that this option requires network service endpoint enabled for Microsoft Storage for the specified subnets. If you are using [vnet module](../vnet/README.md), set `storage_private_access` to true for the specific subnet. - EOF default = {} nullable = false @@ -104,9 +113,11 @@ variable "storage_network_security" { allowed_public_ips = optional(list(string), []) allowed_subnet_ids = optional(list(string), []) }) - validation { + validation { # min_tls_version condition = contains(["TLS1_0", "TLS1_1", "TLS1_2"], var.storage_network_security.min_tls_version) - error_message = "The `min_tls_version` property can be one of: \"TLS1_0\", \"TLS1_1\", \"TLS1_2\"." + error_message = <<-EOF + The `min_tls_version` property can be one of: \"TLS1_0\", \"TLS1_1\", \"TLS1_2\". + EOF } } @@ -122,9 +133,9 @@ variable "file_shares_configuration" { `file_shares` variable are created or sourced, if the latter, the storage account also has to be sourced. - `disable_package_dirs_creation` - (`bool`, optional, defaults to `false`) for sourced File Shares, controls if the bootstrap - package folder structure is created + package folder structure is created. - `quota` - (`number`, optional, defaults to `10`) maximum size of a File Share in GB, a value between - 1 and 5120 (5TB) + 1 and 5120 (5TB). - `access_tier` - (`string`, optional, defaults to `Cool`) access tier for a File Share, can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". EOF @@ -136,17 +147,25 @@ variable "file_shares_configuration" { quota = optional(number, 10) access_tier = optional(string, "Cool") }) - validation { + validation { # disable_package_dirs_creation + condition = ( + var.file_shares_configuration.create_file_shares ? !var.file_shares_configuration.disable_package_dirs_creation : true + ) + error_message = <<-EOF + The `disable_package_dirs_creation` cannot be set to true for newly created File Shares. + EOF + } + validation { # quota condition = var.file_shares_configuration.quota >= 1 && var.file_shares_configuration.quota <= 5120 - error_message = "The `quota` property can take values between 1 and 5120." + error_message = <<-EOF + The `quota` property can take values between 1 and 5120. + EOF } - validation { + validation { # access_tier condition = contains(["Cool", "Hot", "Premium", "TransactionOptimized"], var.file_shares_configuration.access_tier) - error_message = "The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\"." - } - validation { - condition = var.file_shares_configuration.create_file_shares ? !var.file_shares_configuration.disable_package_dirs_creation : true - error_message = "The `disable_package_dirs_creation` cannot be set to true for newly created File Shares." + error_message = <<-EOF + The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\". + EOF } } @@ -162,11 +181,11 @@ variable "file_shares" { Following properties are available per each File Share definition: - - `name` - (`string`, required) name of the File Share + - `name` - (`string`, required) name of the File Share. - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap package. - For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package) - - `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and build - the bootstrap package. + For details on the bootstrap package structure see [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/bootstrap-package). + - `bootstrap_files` - (`map`, optional, defaults to `{}`) a map of files that will be copied to the File Share and + build the bootstrap package. Keys are local paths, values - remote. Only Unix like directory separator (`/`) is supported. If `bootstrap_package_path` is also specified, these files will overwrite any file uploaded from that path. @@ -184,10 +203,9 @@ variable "file_shares" { Additionally you can override the default `quota` and `access_tier` properties per File Share (same restrictions apply): - `quota` - (`number`, optional, defaults to `var.file_shares_configuration.quota`) maximum size of a File Share in GB, - a value between 1 and 5120 (5TB) + a value between 1 and 5120 (5TB). - `access_tier` - (`string`, optional, defaults to `var.file_shares_configuration.access_tier`) access tier for a File Share, - can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". - + can be one of: "Cool", "Hot", "Premium", "TransactionOptimized". EOF default = {} nullable = false @@ -199,7 +217,7 @@ variable "file_shares" { quota = optional(number) access_tier = optional(string) })) - validation { + validation { # name condition = alltrue([ for _, v in var.file_shares : alltrue([ @@ -207,18 +225,25 @@ variable "file_shares" { can(regex("^([a-z0-9-]){3,63}$", v.name)) ]) ]) - error_message = "A File Share name must be between 3 and 63 characters, all lowercase numbers, letters or a dash, it must follow a valid URL schema." + error_message = <<-EOF + A File Share name must be between 3 and 63 characters, all lowercase numbers, letters or a dash, it must follow a valid URL + schema. + EOF } - validation { + validation { # quota condition = alltrue([for _, v in var.file_shares : v.quota >= 1 && v.quota <= 5120 if v.quota != null]) - error_message = "The `quota` property can take values between 1 and 5120." + error_message = <<-EOF + The `quota` property can take values between 1 and 5120. + EOF } - validation { + validation { # access_tier condition = alltrue([ for _, v in var.file_shares : contains(["Cool", "Hot", "Premium", "TransactionOptimized"], v.access_tier) if v.access_tier != null ]) - error_message = "The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\"." + error_message = <<-EOF + The `access_tier` property can take one of the following values: \"Cool\", \"Hot\", \"Premium\", \"TransactionOptimized\". + EOF } } diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index a51fb937..2e2c0df4 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -172,8 +172,8 @@ Following settings are available: - `name` - (`string`, required) name of the frontend IP configuration. `var.name` by default. - `subnet_id` - (`string`, required) id of a subnet to associate with the configuration. - `private_ip_address` - (`string`, optional) private IP address to assign. -- `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. - Can be one of "IPv4", "IPv6". +- `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. Can be + one of "IPv4", "IPv6". Type: @@ -235,9 +235,9 @@ Following settings are available: - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https". - `port` - (`number`, optional) port to run the probe against. - `probe_threshold` - (`number`, optional) number of consecutive probes that decide on forwarding traffic to an endpoint. -- `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5 -- `request_path` - (`string`, optional) used only for non `Tcp` probes, - the URI used to check the endpoint status when `protocol` is set to `Http(s)`. +- `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5. +- `request_path` - (`string`, optional) used only for non `Tcp` probes, the URI used to check the endpoint status when + `protocol` is set to `Http(s)`. Type: @@ -264,7 +264,7 @@ Map with backend configurations for the Gateway Load Balancer. Azure GWLB rule c Following settings are available: - `name` - (`string`, required) name of the backend. -- `tunnel_interfaces` - (`map`, required) map with tunnel interfaces. +- `tunnel_interfaces` - (`map`, required) map with tunnel interfaces: - `identifier` - (`number`, required) interface identifier. - `port` - (`number`, required) interface port. - `type` - (`string`, required) either "External" or "Internal". @@ -306,8 +306,8 @@ Load balancing rule configuration. Available options: - `name` - (`string`, optional) name for the rule. -- `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type - to be used by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". +- `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type to be used + by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". Type: @@ -324,5 +324,4 @@ Default value: `map[name:lb_rule]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/gwlb/variables.tf b/modules/gwlb/variables.tf index e6e31807..cc2c907a 100644 --- a/modules/gwlb/variables.tf +++ b/modules/gwlb/variables.tf @@ -39,8 +39,8 @@ variable "frontend_ip" { - `name` - (`string`, required) name of the frontend IP configuration. `var.name` by default. - `subnet_id` - (`string`, required) id of a subnet to associate with the configuration. - `private_ip_address` - (`string`, optional) private IP address to assign. - - `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. - Can be one of "IPv4", "IPv6". + - `private_ip_address_version` - (`string`, optional, defaults to `IPv4`) the IP version for the private IP address. Can be + one of "IPv4", "IPv6". EOF nullable = false type = object({ @@ -52,11 +52,15 @@ variable "frontend_ip" { validation { # private_ip_address condition = (var.frontend_ip.private_ip_address != null ? can(regex("^(\\d{1,3}\\.){3}\\d{1,3}$", var.frontend_ip.private_ip_address)) : true) - error_message = "The `private_ip_address` property should be in IPv4 format." + error_message = <<-EOF + The `private_ip_address` property should be in IPv4 format. + EOF } validation { # private_ip_address_version condition = contains(["IPv4", "IPv6"], var.frontend_ip.private_ip_address_version) - error_message = "The `private_ip_address_version` property can be one of \"IPv4\", \"IPv6\"." + error_message = <<-EOF + The `private_ip_address_version` property can be one of \"IPv4\", \"IPv6\". + EOF } } @@ -69,9 +73,9 @@ variable "health_probe" { - `protocol` - (`string`, required) protocol used by the health probe, can be one of "Tcp", "Http" or "Https". - `port` - (`number`, optional) port to run the probe against. - `probe_threshold` - (`number`, optional) number of consecutive probes that decide on forwarding traffic to an endpoint. - - `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5 - - `request_path` - (`string`, optional) used only for non `Tcp` probes, - the URI used to check the endpoint status when `protocol` is set to `Http(s)`. + - `interval_in_seconds` - (`number`, optional) interval in seconds between probes, with a minimal value of 5. + - `request_path` - (`string`, optional) used only for non `Tcp` probes, the URI used to check the endpoint status when + `protocol` is set to `Http(s)`. EOF default = { name = "health_probe" @@ -87,31 +91,43 @@ variable "health_probe" { interval_in_seconds = optional(number) request_path = optional(string, "/") }) + validation { # protocol + condition = contains(["Tcp", "Http", "Https"], var.health_probe.protocol) + error_message = <<-EOF + The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\". + EOF + } validation { # port condition = var.health_probe.protocol == "Tcp" ? var.health_probe.port != null : true - error_message = "The `port` property is required when protocol is set to \"Tcp\"." + error_message = <<-EOF + The `port` property is required when protocol is set to \"Tcp\". + EOF } validation { # port condition = var.health_probe.port != null ? var.health_probe.port >= 1 && var.health_probe.port <= 65535 : true - error_message = "The `port` property has to be a valid TCP port." + error_message = <<-EOF + The `port` property has to be a valid TCP port. + EOF } - validation { # protocol - condition = contains(["Tcp", "Http", "Https"], var.health_probe.protocol) - error_message = "The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\"." + validation { # probe_threshold + condition = (var.health_probe.probe_threshold != null ? + var.health_probe.probe_threshold >= 1 && var.health_probe.probe_threshold <= 100 : true) + error_message = <<-EOF + The `probe_threshold` property has to be between 1 and 100. + EOF } validation { # interval_in_seconds condition = (var.health_probe.interval_in_seconds != null ? var.health_probe.interval_in_seconds >= 5 && var.health_probe.interval_in_seconds <= 3600 : true) - error_message = "The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour)." - } - validation { # probe_threshold - condition = (var.health_probe.probe_threshold != null ? - var.health_probe.probe_threshold >= 1 && var.health_probe.probe_threshold <= 100 : true) - error_message = "The `probe_threshold` property has to be between 1 and 100." + error_message = <<-EOF + The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour). + EOF } validation { # request_path condition = var.health_probe.protocol != "Tcp" ? var.health_probe.request_path != null : true - error_message = "The `request_path` property is required when protocol is set to \"Http\" or \"Https\"." + error_message = <<-EOF + The `request_path` property is required when protocol is set to \"Http\" or \"Https\". + EOF } } @@ -121,7 +137,7 @@ variable "backends" { Following settings are available: - `name` - (`string`, required) name of the backend. - - `tunnel_interfaces` - (`map`, required) map with tunnel interfaces. + - `tunnel_interfaces` - (`map`, required) map with tunnel interfaces: - `identifier` - (`number`, required) interface identifier. - `port` - (`number`, required) interface port. - `type` - (`string`, required) either "External" or "Internal". @@ -166,21 +182,27 @@ variable "backends" { type = string })) })) + validation { # backends + condition = (var.backends == null ? true : length(var.backends) <= 2) + error_message = <<-EOF + Maximum allowed number of `backends` is 2. + EOF + } validation { # protocol condition = (var.backends == null ? true : alltrue(flatten([for k, v in var.backends : [for p, r in v.tunnel_interfaces : contains(["VXLAN"], r.protocol)]]))) - error_message = "The `protocol` property can be only \"VXLAN\"." + error_message = <<-EOF + The `protocol` property can be only \"VXLAN\". + EOF } validation { # type condition = (var.backends == null ? true : alltrue(flatten([for k, v in var.backends : [for p, r in v.tunnel_interfaces : contains(["Internal", "External"], r.type)]]))) - error_message = "The `type` property can be one of \"Internal\", \"External\"." - } - validation { # backends - condition = (var.backends == null ? true : length(var.backends) <= 2) - error_message = "Maximum allowed number of `backends` is 2." + error_message = <<-EOF + The `type` property can be one of \"Internal\", \"External\". + EOF } } @@ -190,8 +212,8 @@ variable "lb_rule" { Available options: - `name` - (`string`, optional) name for the rule. - - `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type - to be used by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". + - `load_distribution` - (`string`, optional, defaults to `Default`) specifies the load balancing distribution type to be used + by the Gateway Load Balancer. Can be one of "Default", "SourceIP", "SourceIPProtocol". EOF default = { name = "lb_rule" @@ -203,6 +225,8 @@ variable "lb_rule" { }) validation { # load_distribution condition = contains(["Default", "SourceIP", "SourceIPProtocol"], var.lb_rule.load_distribution) - error_message = "The `load_distribution` property can be one of \"Default\", \"SourceIP\", \"SourceIPProtocol\"." + error_message = <<-EOF + The `load_distribution` property can be one of \"Default\", \"SourceIP\", \"SourceIPProtocol\". + EOF } } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index a9c0d2fe..3e53bfb2 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -98,7 +98,7 @@ Name | Type | Description Name | Type | Description --- | --- | --- [`tags`](#tags) | `map` | The map of tags to assign to all created resources. -[`zones`](#zones) | `list` | Controls zones for Load Balancer's Fronted IP configurations. +[`zones`](#zones) | `list` | Controls zones for Load Balancer's fronted IP configurations. [`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. @@ -171,6 +171,7 @@ Type: string + #### frontend_ips A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. @@ -215,12 +216,12 @@ Below are the properties for the `in_rules` map: - `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) + 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. @@ -336,7 +337,6 @@ map(object({ - ### Optional Inputs @@ -355,19 +355,19 @@ Default value: `map[]` #### zones -Controls zones for Load Balancer's Fronted IP configurations. +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) +- public IPs - these are zones in which the public IP resource is available. +- private IPs - these are 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) @@ -376,7 +376,6 @@ Default value: `[1 2 3]` <sup>[back to list](#modules-optional-inputs)</sup> - #### backend_name The name of the backend pool to create. All frontends of the Load Balancer always use the same backend. @@ -387,6 +386,7 @@ Default value: `vmseries_backend` <sup>[back to list](#modules-optional-inputs)</sup> + #### health_probes Backend's health probe definition. @@ -461,5 +461,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/outputs.tf b/modules/loadbalancer/outputs.tf index 09eadcf4..a525587e 100644 --- a/modules/loadbalancer/outputs.tf +++ b/modules/loadbalancer/outputs.tf @@ -5,8 +5,7 @@ output "backend_pool_id" { output "frontend_ip_configs" { description = "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." - # value = local.output_ips - value = local.frontend_addresses + value = local.frontend_addresses } output "health_probe" { diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 9ca3d2e8..4f0bde88 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -21,25 +21,35 @@ variable "tags" { variable "zones" { description = <<-EOF - Controls zones for Load Balancer's Fronted IP configurations. + 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) + - public IPs - these are zones in which the public IP resource is available. + - private IPs - these are 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"] + nullable = false type = list(string) +} + +variable "backend_name" { + 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 validation { - condition = length(var.zones) > 0 || var.zones == null - error_message = "The `var.zones` can either be a non empty list of Availability Zones or explicit `null`." + condition = can(regex("^\\w[\\w_\\.-]{0,78}(\\w|_)$", var.backend_name)) + error_message = <<-EOF + The `backend_name` property can be maximum 80 chars long and most consist of word characters, dots, underscores and dashes. + It has to start with a word character and end with one or with an underscore. + EOF } } @@ -87,12 +97,12 @@ variable "frontend_ips" { - `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) + 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. @@ -203,17 +213,26 @@ variable "frontend_ips" { [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." + error_message = <<-EOF + All frontends have to be of the same type, either public or private. Please check module's documentation (Usage section) for + details. + EOF } 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." + condition = length(flatten([for _, v in var.frontend_ips : v.name])) == length( + distinct(flatten([for _, v in var.frontend_ips : v.name])) + ) + error_message = <<-EOF + The `name` property has to be unique among all frontend definitions. + EOF } 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." + error_message = <<-EOF + The `private_ip_address` id required for private Load Balancers. + EOF } validation { # private_ip_address condition = alltrue([ @@ -221,7 +240,9 @@ variable "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." + error_message = <<-EOF + The `private_ip_address` property should be in IPv4 format. + EOF } validation { # in_rule.name condition = length(flatten([ @@ -231,7 +252,9 @@ variable "frontend_ips" { 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." + error_message = <<-EOF + The `in_rule.name` property has to be unique among all in rules definitions. + EOF } validation { # in_rule.protocol condition = alltrue(flatten([ @@ -239,7 +262,9 @@ variable "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\"." + error_message = <<-EOF + The `in_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\". + EOF } validation { # in_rule.port condition = alltrue(flatten([ @@ -247,7 +272,9 @@ variable "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." + error_message = <<-EOF + The `in_rule.port` should be a valid TCP port number or `0` for all ports. + EOF } validation { # in_rule.backend_port condition = alltrue(flatten([ @@ -257,7 +284,9 @@ variable "frontend_ips" { if in_rule.backend_port != null ] ])) - error_message = "The `in_rule.backend_port` should be a valid TCP port number." + error_message = <<-EOF + The `in_rule.backend_port` should be a valid TCP port number. + EOF } validation { # in_rule.sessions_persistence condition = alltrue(flatten([ @@ -265,7 +294,9 @@ variable "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\"." + error_message = <<-EOF + The `in_rule.session_persistence` property should be one of: \"Default\", \"SourceIP\", \"SourceIPProtocol\". + EOF } validation { # in_rule.nsg_priority condition = alltrue(flatten([ @@ -275,7 +306,9 @@ variable "frontend_ips" { if in_rule.nsg_priority != null ] ])) - error_message = "The `in_rule.nsg_priority` property be a number between 100 and 4096." + error_message = <<-EOF + The `in_rule.nsg_priority` property be a number between 100 and 4096. + EOF } validation { # out_rule.name condition = length(flatten([ @@ -285,7 +318,9 @@ variable "frontend_ips" { 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." + error_message = <<-EOF + The `out_rule.name` property has to be unique among all in rules definitions. + EOF } validation { # out_rule.protocol condition = alltrue(flatten([ @@ -293,7 +328,9 @@ variable "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\"." + error_message = <<-EOF + The `out_rule.protocol` property should be one of: \"Tcp\", \"Udp\", \"All\". + EOF } validation { # out_rule.allocated_outbound_ports condition = alltrue(flatten([ @@ -303,7 +340,10 @@ variable "frontend_ips" { 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." + error_message = <<-EOF + The `out_rule.allocated_outbound_ports` property should can be either `0` or a valid TCP port number with the maximum value + of 64000. + EOF } validation { # out_rule.idle_timeout_in_minutes condition = alltrue(flatten([ @@ -313,17 +353,12 @@ variable "frontend_ips" { 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)." + error_message = <<-EOF + The `out_rule.idle_timeout_in_minutes` property should can take values between 4 and 120 (minutes). + EOF } } -variable "backend_name" { - 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 -} - variable "health_probes" { description = <<-EOF Backend's health probe definition. @@ -359,48 +394,76 @@ variable "health_probes" { })) 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\"." + error_message = <<-EOF + The key describing a health probe cannot be \"default\". + EOF } validation { # 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." + 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 = <<-EOF + The `name` property has to be unique among all health probe definitions. + EOF } 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\"." + condition = var.health_probes == null ? true : !anytrue( + [for _, v in var.health_probes : v.name == "default_vmseries_probe"] + ) + error_message = <<-EOF + The `name` property cannot be \"default_vmseries_probe\". + EOF } validation { # 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\"." + condition = var.health_probes == null ? true : alltrue( + [for k, v in var.health_probes : contains(["Tcp", "Http", "Https"], v.protocol)] + ) + error_message = <<-EOF + The `protocol` property can be one of \"Tcp\", \"Http\", \"Https\". + EOF } validation { # port - 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\"." + condition = var.health_probes == null ? true : alltrue( + [for k, v in var.health_probes : v.port != null if v.protocol == "Tcp"] + ) + error_message = <<-EOF + The `port` property is required when protocol is set to \"Tcp\". + EOF } validation { # port 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." + error_message = <<-EOF + The `port` property has to be a valid TCP port. + EOF } validation { # interval_in_seconds 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)." + error_message = <<-EOF + The `interval_in_seconds` property has to be between 5 and 3600 seconds (1 hour). + EOF } validation { # probe_threshold 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." + error_message = <<-EOF + The `probe_threshold` property has to be between 1 and 100. + EOF } - validation { # request - 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" + validation { # request_path + condition = var.health_probes == null ? true : alltrue( + [for k, v in var.health_probes : v.request_path != null if v.protocol != "Tcp"] + ) + error_message = <<-EOF + The `request_path` property must be set if `protocol` is different than TCP. + EOF } } @@ -430,13 +493,17 @@ variable "nsg_auto_rules_settings" { for ip in var.nsg_auto_rules_settings.source_ips : 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." + error_message = <<-EOF + The `source_ips` property can an IPv4 address or address space in CIDR notation. + EOF } 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`." + error_message = <<-EOF + The `base_priority` property can take only values between `100` and `4000`. + EOF } } diff --git a/modules/name_templater/.header.md b/modules/name_templater/.header.md new file mode 100644 index 00000000..348dcd82 --- /dev/null +++ b/modules/name_templater/.header.md @@ -0,0 +1,54 @@ +# Name Templater module + +A module to generate resource name template. + + +## Purpose + +There are situations where simple name prefixing is not enough. More complex structures are required. +This module generates a string template that can be used with Terraform's `format()` function to generate the actual resource name. + +## Usage + +A simple module invocation might look like the following: + +```hcl +module "name_templates" { + source = "../../modules/name_templater" + + resource_type = "vnet" + name_template = { + delimiter = "-" + parts = [ + { prefix = null }, + { bu = "rnd" }, + { randomize = "__random__" }, + { env = "prd" }, + { name = "%s" }, + { abbreviation = "__default__" }, + ] + } + name_prefix = "a_prefix" +} +``` + +The value the module will output for such invocation would be `"a_prefix-rnd-crediblefrog-prd-%s-vnet"`. + +As you can see: + +* all `parts` values are *glued* together to form a template name +* the `prefix` key is just a placeholder that eventually is replaced with the value of `name_prefix` +* the `__random__` string is replaced with a name of a [random pet](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) (in case you need to randomize some name, for testing purposes for example) +* the `__default__` string is replaced with a resource abbreviation. + + This abbreviations are defined with `var.abbreviations` variable. The module contains basic abbreviations following Microsoft suggestions, but they can be overriden with custom definitions. + + The important part is that the `resource_type` has to match an entry in `abbreviations` variable, otherwise the abbreviation will be replaced with an empty string. + +To create the actual resource name the following code can be used: + +```hcl +vnet_name = format(module.name_templates.template, "transit") +``` + +Following the values above the actual resource name would be `"a_prefix-rnd-crediblefrog-prd-transit-vnet"`. \ No newline at end of file diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index 253570e1..2f032626 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -1,8 +1,8 @@ +<!-- BEGIN_TF_DOCS --> # Name Templater module A module to generate resource name template. - ## Purpose There are situations where simple name prefixing is not enough. More complex structures are required. @@ -40,9 +40,7 @@ As you can see: * the `prefix` key is just a placeholder that eventually is replaced with the value of `name_prefix` * the `__random__` string is replaced with a name of a [random pet](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) (in case you need to randomize some name, for testing purposes for example) * the `__default__` string is replaced with a resource abbreviation. - This abbreviations are defined with `var.abbreviations` variable. The module contains basic abbreviations following Microsoft suggestions, but they can be overriden with custom definitions. - The important part is that the `resource_type` has to match an entry in `abbreviations` variable, otherwise the abbreviation will be replaced with an empty string. To create the actual resource name the following code can be used: @@ -53,44 +51,149 @@ vnet_name = format(module.name_templates.template, "transit") Following the values above the actual resource name would be `"a_prefix-rnd-crediblefrog-prd-transit-vnet"`. -## Reference +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`resource_type`](#resource_type) | `string` | A type of resource for which the name template will be created. +[`name_prefix`](#name_prefix) | `string` | Prefix used in names for the resources. +[`name_template`](#name_template) | `object` | A name template definition. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`abbreviations`](#abbreviations) | `map` | Map of abbreviations used for resources (placed in place of "__default__"). + + + +## Module's Outputs + +Name | Description +--- | --- +`template` | A template string that can be used with `format` function to generate a resource name. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `random`, version: ~> 3.5 + + +Providers used in this module: + +- `random`, version: ~> 3.5 + + + + +Resources used in this module: + +- `pet` (managed) + +## Inputs/Outpus details + +### Required Inputs + + +#### resource_type + +A type of resource for which the name template will be created. This should follow the abbreviations resource naming standard. + + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### name_prefix + +Prefix used in names for the resources. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### name_template + +A name template definition. + +Consist of two elements: + +- `delimiter` - (`string`, required) a string that will be used to separate the elements. +- `parts` - (`list`, required) a list of elements that will form the template name. + +There are couple of rules to be followed: + +- the order **DOES** matter. +- `parts` is a list of single element maps. +- keys in `parts` elements will be dropped, they are only informational, only values will be used. +- value for the `prefix` key will be replaced with the `var.name_prefix` value. +- a value of `__default__` will be replaced with an abbreviation defined in the `var.abbrevations` and matching + `var.resource_type`. +- since this module generates template name do **REMEMBER** to include a part with `%s` value. + +Example: + +``` +default = { + default = { + delimiter = "-" + parts = [ + { prefix = null }, + { bu = "rnd" }, + { env = "prd" }, + { name = "%s" }, + { abbreviation = "__default__" }, + ] + } + storage = { + delimiter = "" + parts = [ + { prefix = null }, + { org = "palo" }, + { env = "prd" }, + { name = "%s" }, + ] + } +} +``` + + +Type: + +```hcl +object({ + delimiter = string + parts = list(map(string)) + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + +### Optional Inputs -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | -| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.5 | -### Providers -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | ~> 3.5 | -### Modules +#### abbreviations -No modules. +Map of abbreviations used for resources (placed in place of "__default__"). -### Resources +These abbreviations are based on [Microsoft suggestions](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations). -| Name | Type | -|------|------| -| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource | -### Inputs +Type: map(string) -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_resource_type"></a> [resource\_type](#input\_resource\_type) | A type of resource for which the name template will be created. This should follow the abbreviations resource naming standard. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | Prefix used in names for the resources. | `string` | n/a | yes | -| <a name="input_name_template"></a> [name\_template](#input\_name\_template) | A name template definition.<br><br>Consist of two elements:<br><br>- `parts` - a list of elements that will form the template name<br>- `delimiter` - a string that will be used to separate the elements.<br><br>There are couple of rules to be followed:<br><br>- the order **DOES** matter<br>- `parts` is a list of single element maps<br>- keys in `parts` elements will be dropped, they are only informational, only values will be used<br>- value for the `prefix` key will be replaced with the `var.name_prefix` value<br>- a value of `__default__` will be replaced with an abbreviation defined in the `var.abbrevations` and matching `var.resource_type`.<br>- since this module generates template name do **REMEMBER** to include a part with `%s` value <br><br>Example:<pre>default = {<br> default = {<br> delimiter = "-"<br> parts = [<br> { prefix = null },<br> { bu = "rnd" },<br> { env = "prd" },<br> { name = "%s" },<br> { abbreviation = "__default__" },<br> ]<br> }<br> storage = {<br> delimiter = ""<br> parts = [<br> { prefix = null },<br> { org = "palo" },<br> { env = "prd" },<br> { name = "%s" },<br> ]<br> }<br>}</pre> | <pre>object({<br> delimiter = string<br> parts = list(map(string))<br> })</pre> | n/a | yes | -| <a name="input_abbreviations"></a> [abbreviations](#input\_abbreviations) | Map of abbreviations used for resources (placed in place of "\_\_default\_\_").<br><br>These abbreviations are based on [Microsoft suggestions](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations). | `map(string)` | <pre>{<br> "application_gw": "agw",<br> "application_insights": "appi",<br> "availability_set": "avail",<br> "bastion": "bas",<br> "data_disk": "disk",<br> "file_share": "share",<br> "load_balancer": "lb",<br> "log_analytics_workspace": "log",<br> "managed_identity": "id",<br> "nat_gw": "ng",<br> "network_interface": "nic",<br> "nsg": "nsg",<br> "nsg_rule": "nsgsr",<br> "os_disk": "osdisk",<br> "public_ip": "pip",<br> "public_ip_prefix": "ippre",<br> "resource_group": "rg",<br> "route_table": "rt",<br> "service_endpoint": "se",<br> "storage_account": "st",<br> "subnet": "snet",<br> "udr": "udr",<br> "virtual_machine": "vm",<br> "virtual_machine_scale_set": "vmss",<br> "virtual_network_gateway": "vgw",<br> "vnet": "vnet",<br> "vnet_peering": "peer"<br>}</pre> | no | +Default value: `map[application_gw:agw application_insights:appi availability_set:avail bastion:bas data_disk:disk file_share:share load_balancer:lb log_analytics_workspace:log managed_identity:id nat_gw:ng network_interface:nic nsg:nsg nsg_rule:nsgsr os_disk:osdisk public_ip:pip public_ip_prefix:ippre resource_group:rg route_table:rt service_endpoint:se storage_account:st subnet:snet udr:udr virtual_machine:vm virtual_machine_scale_set:vmss virtual_network_gateway:vgw vnet:vnet vnet_peering:peer]` -### Outputs +<sup>[back to list](#modules-optional-inputs)</sup> -| Name | Description | -|------|-------------| -| <a name="output_template"></a> [template](#output\_template) | A template string that can be used with `format` function to generate a resource name. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/name_templater/variables.tf b/modules/name_templater/variables.tf index fb87a847..44e90073 100644 --- a/modules/name_templater/variables.tf +++ b/modules/name_templater/variables.tf @@ -1,5 +1,7 @@ variable "resource_type" { - description = "A type of resource for which the name template will be created. This should follow the abbreviations resource naming standard." + description = <<-EOF + A type of resource for which the name template will be created. This should follow the abbreviations resource naming standard. + EOF type = string } @@ -14,17 +16,18 @@ variable "name_template" { Consist of two elements: - - `parts` - a list of elements that will form the template name - - `delimiter` - a string that will be used to separate the elements. + - `delimiter` - (`string`, required) a string that will be used to separate the elements. + - `parts` - (`list`, required) a list of elements that will form the template name. There are couple of rules to be followed: - - the order **DOES** matter - - `parts` is a list of single element maps - - keys in `parts` elements will be dropped, they are only informational, only values will be used - - value for the `prefix` key will be replaced with the `var.name_prefix` value - - a value of `__default__` will be replaced with an abbreviation defined in the `var.abbrevations` and matching `var.resource_type`. - - since this module generates template name do **REMEMBER** to include a part with `%s` value + - the order **DOES** matter. + - `parts` is a list of single element maps. + - keys in `parts` elements will be dropped, they are only informational, only values will be used. + - value for the `prefix` key will be replaced with the `var.name_prefix` value. + - a value of `__default__` will be replaced with an abbreviation defined in the `var.abbrevations` and matching + `var.resource_type`. + - since this module generates template name do **REMEMBER** to include a part with `%s` value. Example: @@ -51,7 +54,6 @@ variable "name_template" { } } ``` - EOF type = object({ delimiter = string diff --git a/modules/natgw/.header.md b/modules/natgw/.header.md index 9ddbc225..a48e0336 100644 --- a/modules/natgw/.header.md +++ b/modules/natgw/.header.md @@ -10,6 +10,14 @@ This module can be used to either create a new NAT Gateway or to connect an existing one with subnets deployed using (for example) the [VNET module](../vnet/README.md). +NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to +decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. Keep in mind +that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But +if that zone becomes unavailable, resources in other zones will lose internet connectivity. + +For design considerations, limitation and examples of zone-resiliency architecture please refer to +[Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-availability-zones). + ## Usage To deploy this resource in it's minimum configuration following code diff --git a/modules/natgw/README.md b/modules/natgw/README.md index 3b5aae7d..e47015ff 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -10,6 +10,14 @@ This module can be used to either create a new NAT Gateway or to connect an existing one with subnets deployed using (for example) the [VNET module](../vnet/README.md). +NAT Gateway is not zone-redundant. It is a zonal resource. It means that it's always deployed in a zone. It's up to the user to +decide if a zone will be specified during resource deployment or if Azure will take that decision for the user. Keep in mind +that regardless of the fact that NAT Gateway is placed in a specific zone it can serve traffic for resources in all zones. But +if that zone becomes unavailable, resources in other zones will lose internet connectivity. + +For design considerations, limitation and examples of zone-resiliency architecture please refer to +[Microsoft documentation](https://learn.microsoft.com/en-us/azure/virtual-network/nat-gateway/nat-availability-zones). + ## Usage To deploy this resource in it's minimum configuration following code @@ -48,7 +56,7 @@ Name | Type | Description [`tags`](#tags) | `map` | A map of tags that will be assigned to resources created by this module. [`create_natgw`](#create_natgw) | `bool` | Triggers creation of a NAT Gateway when set to `true`. [`zone`](#zone) | `string` | Controls whether the NAT Gateway will be bound to a specific zone or not. -[`idle_timeout`](#idle_timeout) | `number` | Connection IDLE timeout in minutes (up to 120, by default 4). +[`idle_timeout`](#idle_timeout) | `number` | Connection IDLE timeout in minutes (up to 120, defaults to Azure defaults). [`public_ip`](#public_ip) | `object` | A map defining a Public IP resource. [`public_ip_prefix`](#public_ip_prefix) | `object` | A map defining a Public IP Prefix resource. @@ -119,9 +127,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - - #### subnet_ids A map of subnet IDs what will be bound with this NAT Gateway. @@ -137,6 +142,9 @@ Type: map(string) + + + ### Optional Inputs @@ -153,6 +161,7 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### create_natgw Triggers creation of a NAT Gateway when set to `true`. @@ -188,15 +197,14 @@ Default value: `&{}` #### idle_timeout -Connection IDLE timeout in minutes (up to 120, by default 4). Only for newly created resources. +Connection IDLE timeout in minutes (up to 120, defaults to Azure defaults). Only for newly created resources. Type: number -Default value: `4` +Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - #### public_ip A map defining a Public IP resource. @@ -306,5 +314,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/main.tf b/modules/natgw/main.tf index f57e39fc..f90bccff 100644 --- a/modules/natgw/main.tf +++ b/modules/natgw/main.tf @@ -71,14 +71,6 @@ locals { pip = try(azurerm_public_ip.this[0], data.azurerm_public_ip.this[0], null) pip_prefix = try(azurerm_public_ip_prefix.this[0], data.azurerm_public_ip_prefix.this[0], null) - - /* pip = var.create_natgw ? ( - try(var.public_ip.create, false) ? azurerm_public_ip.this[0] : try(data.azurerm_public_ip.this[0], null) - ) : null - - pip_prefix = var.create_natgw ? ( - try(var.public_ip_prefix.create, false) ? azurerm_public_ip_prefix.this[0] : try(data.azurerm_public_ip_prefix.this[0], null) - ) : null */ } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_association diff --git a/modules/natgw/variables.tf b/modules/natgw/variables.tf index 8643de5a..296b31a1 100644 --- a/modules/natgw/variables.tf +++ b/modules/natgw/variables.tf @@ -19,6 +19,15 @@ variable "tags" { type = map(string) } +variable "subnet_ids" { + description = <<-EOF + A map of subnet IDs what will be bound with this NAT Gateway. + + Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name. + EOF + type = map(string) +} + variable "create_natgw" { description = <<-EOF Triggers creation of a NAT Gateway when set to `true`. @@ -46,29 +55,24 @@ variable "zone" { type = string validation { condition = (var.zone == null || can(regex("^[1-3]$", var.zone))) - error_message = "The `zone` variable should have value of either: \"1\", \"2\" or \"3\"." + error_message = <<-EOF + The `zone` variable should have value of either: \"1\", \"2\" or \"3\". + EOF } } variable "idle_timeout" { - description = "Connection IDLE timeout in minutes (up to 120, by default 4). Only for newly created resources." - default = 4 + description = "Connection IDLE timeout in minutes (up to 120, defaults to Azure defaults). Only for newly created resources." + default = null type = number validation { condition = (var.idle_timeout >= 1 && var.idle_timeout <= 120) - error_message = "The `idle_timeout` variable should be a number between 1 and 120." + error_message = <<-EOF + The `idle_timeout` variable should be a number between 1 and 120. + EOF } } -variable "subnet_ids" { - description = <<-EOF - A map of subnet IDs what will be bound with this NAT Gateway. - - Value is the subnet ID, key value does not matter but should be unique, typically it can be a subnet name. - EOF - type = map(string) -} - variable "public_ip" { description = <<-EOF A map defining a Public IP resource. @@ -160,9 +164,12 @@ variable "public_ip_prefix" { resource_group_name = optional(string) length = optional(number, 28) }) - validation { - condition = (var.public_ip_prefix == null || - (try(var.public_ip_prefix.length, -1) >= 0 && try(var.public_ip_prefix.length, 32) <= 31)) - error_message = "The `length` property should be a number between 0 and 31." + validation { # length + condition = var.public_ip_prefix == null || ( + try(var.public_ip_prefix.length, -1) >= 0 && try(var.public_ip_prefix.length, 32) <= 31 + ) + error_message = <<-EOF + The `length` property should be a number between 0 and 31. + EOF } } diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index a43f7726..269c8b1a 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -142,7 +142,7 @@ A map defining Application Insights instances. Following properties are available: -- `name` - (`string`, required) the name of the Application Insights instance +- `name` - (`string`, required) the name of the Application Insights instance. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that will host the Application Insights instance. @@ -201,9 +201,9 @@ Configuration of the log analytics workspace. Following properties are available: - `sku` - (`string`, optional, defaults to Azure defaults) the SKU of the Log Analytics Workspace. - - As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: - `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. + + As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: + `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. - `metrics_retention_in_days` - (`number`, optional, defaults to Azure defaults) workspace data retention in days, possible values are between 30 and 730. @@ -224,5 +224,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/ngfw_metrics/variables.tf b/modules/ngfw_metrics/variables.tf index ac1a4e20..f81f9adf 100644 --- a/modules/ngfw_metrics/variables.tf +++ b/modules/ngfw_metrics/variables.tf @@ -33,9 +33,9 @@ variable "log_analytics_workspace" { Following properties are available: - `sku` - (`string`, optional, defaults to Azure defaults) the SKU of the Log Analytics Workspace. - - As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: - `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. + + As of API version `2018-04-03` the Azure default value is `PerGB2018`, other possible values are: + `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation`. - `metrics_retention_in_days` - (`number`, optional, defaults to Azure defaults) workspace data retention in days, possible values are between 30 and 730. @@ -46,7 +46,7 @@ variable "log_analytics_workspace" { sku = optional(string) metrics_retention_in_days = optional(number) }) - validation { + validation { # sku condition = var.log_analytics_workspace.sku != null ? contains([ "Free", "PerNode", @@ -58,11 +58,18 @@ variable "log_analytics_workspace" { "PerGB2018"], var.log_analytics_workspace.sku ) : true - error_message = "The `var.log_analytics_workspace.sku` property has to have a value of either: `Free`, `PerNode`, `Premium`, `Standard`, `Standalone`, `Unlimited`, `CapacityReservation` or `PerGB2018`." + error_message = <<-EOF + The `var.log_analytics_workspace.sku` property has to have a value of either: `Free`, `PerNode`, `Premium`, `Standard`, + `Standalone`, `Unlimited`, `CapacityReservation` or `PerGB2018`. + EOF } - validation { - condition = var.log_analytics_workspace.metrics_retention_in_days != null ? var.log_analytics_workspace.metrics_retention_in_days >= 30 && var.log_analytics_workspace.metrics_retention_in_days <= 730 : true - error_message = "The `var.log_analytics_workspace.metrics_retention_in_days` property can take values between 30 and 730 (both inclusive)." + validation { # metrics_retention_in_days + condition = var.log_analytics_workspace.metrics_retention_in_days != null ? ( + var.log_analytics_workspace.metrics_retention_in_days >= 30 && var.log_analytics_workspace.metrics_retention_in_days <= 730 + ) : true + error_message = <<-EOF + The `var.log_analytics_workspace.metrics_retention_in_days` property can take values between 30 and 730 (both inclusive). + EOF } } @@ -72,7 +79,7 @@ variable "application_insights" { Following properties are available: - - `name` - (`string`, required) the name of the Application Insights instance + - `name` - (`string`, required) the name of the Application Insights instance. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of a Resource Group that will host the Application Insights instance. @@ -87,12 +94,14 @@ variable "application_insights" { resource_group_name = optional(string) metrics_retention_in_days = optional(number) })) - validation { + validation { # metrics_retention_in_days condition = alltrue([ for _, v in var.application_insights : v.metrics_retention_in_days >= 30 && v.metrics_retention_in_days <= 730 if v.metrics_retention_in_days != null ]) - error_message = "The `metrics_retention_in_days` property can take values between 30 and 730 (both inclusive)." + error_message = <<-EOF + The `metrics_retention_in_days` property can take values between 30 and 730 (both inclusive). + EOF } } diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 388faab0..b3f781c4 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -108,7 +108,8 @@ A map defining authentication settings (including username and password). Following properties are available: -- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama username. +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama + username. - `password` - (`string`, optional, defaults to `null`) the initial administrative Panorama password. - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. @@ -121,7 +122,6 @@ The `password` property is required when `ssh_keys` is not specified. If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - Type: ```hcl @@ -157,7 +157,6 @@ Following properties are available: **Important!** \ The `custom_id` and `version` properties are mutually exclusive. - Type: @@ -209,7 +208,6 @@ List of other, optional properties: assigned to this VM. Required only if `identity_type` is not "SystemAssigned". - Type: ```hcl @@ -280,7 +278,6 @@ Example: }, ] ``` - Type: @@ -352,7 +349,6 @@ Example: } } ``` - Type: @@ -371,5 +367,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/variables.tf b/modules/panorama/variables.tf index b4ea86f6..a8b18fca 100644 --- a/modules/panorama/variables.tf +++ b/modules/panorama/variables.tf @@ -25,7 +25,8 @@ variable "authentication" { Following properties are available: - - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama username. + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative Panorama + username. - `password` - (`string`, optional, defaults to `null`) the initial administrative Panorama password. - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. @@ -36,7 +37,6 @@ variable "authentication" { **Important!** \ `ssh_keys` property is a list of strings, so each item should be the actual public key value. If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - EOF type = object({ username = optional(string, "panadmin") @@ -44,9 +44,11 @@ variable "authentication" { disable_password_authentication = optional(bool, true) ssh_keys = optional(list(string), []) }) - validation { + validation { # password & ssh_keys condition = var.authentication.password != null || length(var.authentication.ssh_keys) > 0 - error_message = "Either `var.authentication.password`, `var.authentication.ssh_key` or both must be set in order to have access to the device." + error_message = <<-EOF + Either `var.authentication.password`, `var.authentication.ssh_key` or both must be set in order to have access to the device. + EOF } } @@ -71,7 +73,6 @@ variable "image" { **Important!** \ The `custom_id` and `version` properties are mutually exclusive. - EOF type = object({ version = optional(string) @@ -81,10 +82,12 @@ variable "image" { enable_marketplace_plan = optional(bool, true) custom_id = optional(string) }) - validation { + validation { # version & custom_id condition = (var.image.custom_id != null && var.image.version == null ) || (var.image.custom_id == null && var.image.version != null) - error_message = "Either `custom_id` or `version` has to be defined." + error_message = <<-EOF + Either `custom_id` or `version` has to be defined. + EOF } } @@ -119,7 +122,6 @@ variable "virtual_machine" { "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be assigned to this VM. Required only if `identity_type` is not "SystemAssigned". - EOF type = object({ size = optional(string, "Standard_D5_v2") @@ -134,17 +136,27 @@ variable "virtual_machine" { identity_type = optional(string, "SystemAssigned") identity_ids = optional(list(string), []) }) - validation { + validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) - error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + error_message = <<-EOF + The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. + EOF } - validation { - condition = contains(["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type) - error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + validation { # identity_type + condition = contains( + ["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type + ) + error_message = <<-EOF + The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\". + EOF } - validation { - condition = var.virtual_machine.identity_type == "SystemAssigned" ? length(var.virtual_machine.identity_ids) == 0 : length(var.virtual_machine.identity_ids) >= 0 - error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + validation { # identity_type & identity_ids + condition = var.virtual_machine.identity_type == "SystemAssigned" ? length(var.virtual_machine.identity_ids) == 0 : ( + length(var.virtual_machine.identity_ids) >= 0 + ) + error_message = <<-EOF + The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\". + EOF } } @@ -197,7 +209,6 @@ variable "interfaces" { }, ] ``` - EOF type = list(object({ name = string @@ -207,12 +218,14 @@ variable "interfaces" { public_ip_name = optional(string) public_ip_resource_group_name = optional(string) })) - validation { + validation { # public_ip_name condition = alltrue([ for v in var.interfaces : v.public_ip_name != null if v.create_public_ip ]) - error_message = "The `public_ip_name` property is required when `create_public_ip` is `true`." + error_message = <<-EOF + The `public_ip_name` property is required when `create_public_ip` is `true`. + EOF } } @@ -247,7 +260,6 @@ variable "logging_disks" { } } ``` - EOF default = {} nullable = false @@ -257,20 +269,26 @@ variable "logging_disks" { lun = string disk_type = optional(string, "StandardSSD_LRS") })) - validation { + validation { # size condition = alltrue([for _, v in var.logging_disks : contains(range(2048, 24577, 2048), parseint(v.size, 10))]) - error_message = "The `size` property value must be a multiple of `2048` but not higher than `24576` (24 TB)." + error_message = <<-EOF + The `size` property value must be a multiple of `2048` but not higher than `24576` (24 TB). + EOF } - validation { + validation { # lun condition = alltrue([ for _, v in var.logging_disks : (parseint(v.lun, 10) >= 0 && parseint(v.lun, 10) <= 63) if v.lun != null ]) - error_message = "The `lun` property value must be a number between `0` and `63`." + error_message = <<-EOF + The `lun` property value must be a number between `0` and `63`. + EOF } - validation { + validation { # disk_type condition = alltrue([ for _, v in var.logging_disks : contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS", "UltraSSD_LRS"], v.disk_type) ]) - error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS`, `Premium_LRS` or `UltraSSD_LRS`." + error_message = <<-EOF + The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS`, `Premium_LRS` or `UltraSSD_LRS`. + EOF } } diff --git a/modules/virtual_network_gateway/.header.md b/modules/virtual_network_gateway/.header.md index 1568cf58..f9dcf37a 100644 --- a/modules/virtual_network_gateway/.header.md +++ b/modules/virtual_network_gateway/.header.md @@ -4,7 +4,8 @@ A terraform module for deploying a VNG (Virtual Network Gateway) and its compone ## Usage -In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as +prerequisites. Then you can use below code as an example of calling module to create VNG: ```hcl @@ -13,35 +14,19 @@ module "vng" { for_each = var.virtual_network_gateways + name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - name = each.value.name - zones = each.value.avzones - type = each.value.type - vpn_type = each.value.vpn_type - sku = each.value.sku + network = each.value.network + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - active_active = each.value.active_active - default_local_network_gateway_id = each.value.default_local_network_gateway_id - edge_zone = each.value.edge_zone - enable_bgp = each.value.enable_bgp - generation = each.value.generation - private_ip_address_enabled = each.value.private_ip_address_enabled - ip_configuration = [ - for ip_configuration in each.value.ip_configuration : - merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) - ] - - vpn_client_configuration = each.value.vpn_client_configuration - azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses - local_bgp_settings = each.value.local_bgp_settings - custom_route = each.value.custom_route - ipsec_shared_key = each.value.ipsec_shared_key - local_network_gateways = each.value.local_network_gateways - connection_mode = each.value.connection_mode - ipsec_policy = each.value.ipsec_policy + virtual_network_gateway = each.value.virtual_network_gateway + azure_bgp_peer_addresses = each.value.azure_bgp_peer_addresses + bgp = each.value.bgp + local_network_gateways = each.value.local_network_gateways + vpn_clients = each.value.vpn_clients tags = var.tags } @@ -51,90 +36,279 @@ Below there are provided sample values for `virtual_network_gateways` map: ```hcl virtual_network_gateways = { + expressroute = { + name = "expressroute" + virtual_network_gateway = { + type = "ExpressRoute" + # vpn_type = "PolicyBased" + sku = "Standard" + # generation = "Generation1" + } + vnet_key = "transit" + subnet_key = "vpn" + network = { + public_ip_zones = ["1"] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "expressroute_pip" + } + } + } + } + expressroute_policy_based = { + name = "er_policy" + virtual_network_gateway = { + type = "ExpressRoute" + vpn_type = "PolicyBased" + sku = "Standard" + generation = "Generation2" + } + vnet_key = "er" + subnet_key = "vpn" + network = { + public_ip_zones = ["1"] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "er_policy_pip" + } + } + } + } + vpn_simple = { + name = "simple-vpn" + virtual_network_gateway = { + type = "Vpn" + # vpn_type = "PolicyBased" + sku = "VpnGw1" + generation = "Generation1" + } + vnet_key = "er" + subnet_key = "vpn" + network = { + public_ip_zones = [] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "simple_vpn_pip" + } + } + } + } "vng" = { - name = "vng" - type = "Vpn" - sku = "VpnGw2" - generation = "Generation2" - active_active = true - enable_bgp = true - ip_configuration = [ - { - name = "001" - create_public_ip = true - public_ip_name = "pip1" - vnet_key = "transit" - subnet_name = "GatewaySubnet" - }, - { - name = "002" - create_public_ip = true - public_ip_name = "pip2" - vnet_key = "transit" - subnet_name = "GatewaySubnet" + name = "vng" + virtual_network_gateway = { + type = "Vpn" + sku = "VpnGw2AZ" + generation = "Generation2" + active_active = true + } + vnet_key = "transit" + subnet_key = "vpn" + network = { + public_ip_zones = ["1", "2", "3"] + ip_configurations = { + primary = { + name = "primary" + create_public_ip = true + public_ip_name = "vng-primary-pip" + } + secondary = { + name = "secondary" + create_public_ip = true + public_ip_name = "vng-secondary-pip" + } } - ] - ipsec_shared_key = "test123" - azure_bgp_peers_addresses = { - primary_1 = "169.254.21.2" - secondary_1 = "169.254.22.2" } - local_bgp_settings = { - asn = "65002" - peering_addresses = { - "001" = { - apipa_addresses = ["primary_1"] - }, - "002" = { - apipa_addresses = ["secondary_1"] + azure_bgp_peer_addresses = { + one_primary = "169.254.21.2" + one_secondary = "169.254.22.2" + two_primary = "169.254.21.12" + two_secondary = "169.254.22.12" + three_primary = "169.254.21.22" + three_secondary = "169.254.22.22" + } + bgp = { + enable = true + configuration = { + asn = "65002" + primary_peering_addresses = { + name = "primary" + apipa_address_keys = ["one_primary", "two_primary", "three_primary"] + } + secondary_peering_addresses = { + name = "secondary" + apipa_address_keys = ["one_secondary", "two_secondary", "three_secondary"] } } } local_network_gateways = { - "lg1" = { - local_ng_name = "lg1" - connection_name = "cn1" + lg1 = { + name = "local_gw_1" gateway_address = "8.8.8.8" - remote_bgp_settings = [{ + remote_bgp_settings = { asn = "65000" bgp_peering_address = "169.254.21.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" + } + connection = { + name = "connection_1" + custom_bgp_addresses = { + primary_key = "one_primary" + secondary_key = "one_secondary" } - ] - }, - "lg2" = { - local_ng_name = "lg2" - connection_name = "cn2" + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } + } + lg2 = { + name = "local_gw_2" gateway_address = "4.4.4.4" - remote_bgp_settings = [{ + remote_bgp_settings = { asn = "65000" bgp_peering_address = "169.254.22.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" + } + connection = { + name = "connection_2" + custom_bgp_addresses = { + primary_key = "two_primary" + secondary_key = "two_secondary" } - ] + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } } } - connection_mode = "InitiatorOnly" - ipsec_policy = [ - { - dh_group = "ECP384" - ike_encryption = "AES256" - ike_integrity = "SHA256" - ipsec_encryption = "AES256" - ipsec_integrity = "SHA256" - pfs_group = "ECP384" - sa_datasize = "102400000" - sa_lifetime = "14400" - } - ] } } +``` + +To make defining the VNGs easy, you can use the following variable in *glue code*: + +```hcl +variable "virtual_network_gateways" { + description = "Map of virtual_network_gateways to create" + default = {} + nullable = false + type = map(object({ + name = string + virtual_network_gateway = object({ + type = optional(string) + vpn_type = optional(string) + sku = optional(string) + active_active = optional(bool) + generation = optional(string) + custom_routes = optional(map(list(string))) + }) + vnet_key = string + subnet_key = string + network = object({ + public_ip_zones = optional(list(string)) + ip_configurations = object({ + primary = object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + })) + }) + private_ip_address_enabled = optional(bool) + default_local_network_gateway_id = optional(string) + edge_zone = optional(string) + }) + azure_bgp_peer_addresses = optional(map(string)) + bgp = optional(object({ + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) + })) + })) + local_network_gateways = optional(map(object({ + name = string + remote_bgp_settings = optional(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })) + gateway_address = optional(string) + address_space = optional(list(string), []) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + type = optional(string) + mode = optional(string) + shared_key = optional(string) + }) + })), {}) + vpn_clients = optional(map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })), {}) + })) +} ``` \ No newline at end of file diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index a55ad6e4..eba170a6 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -5,7 +5,8 @@ A terraform module for deploying a VNG (Virtual Network Gateway) and its compone ## Usage -In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as prerequisites. +In order to use module `virtual_network_gateway`, you need to deploy `azurerm_resource_group` and use module `vnet` as +prerequisites. Then you can use below code as an example of calling module to create VNG: ```hcl @@ -14,35 +15,18 @@ module "vng" { for_each = var.virtual_network_gateways + name = "${var.name_prefix}${each.value.name}" location = var.location resource_group_name = local.resource_group.name - name = each.value.name - zones = each.value.avzones - - type = each.value.type - vpn_type = each.value.vpn_type - sku = each.value.sku - - active_active = each.value.active_active - default_local_network_gateway_id = each.value.default_local_network_gateway_id - edge_zone = each.value.edge_zone - enable_bgp = each.value.enable_bgp - generation = each.value.generation - private_ip_address_enabled = each.value.private_ip_address_enabled - - ip_configuration = [ - for ip_configuration in each.value.ip_configuration : - merge(ip_configuration, { subnet_id = module.vnet[ip_configuration.vnet_key].subnet_ids[ip_configuration.subnet_name] }) - ] - - vpn_client_configuration = each.value.vpn_client_configuration - azure_bgp_peers_addresses = each.value.azure_bgp_peers_addresses - local_bgp_settings = each.value.local_bgp_settings - custom_route = each.value.custom_route - ipsec_shared_key = each.value.ipsec_shared_key - local_network_gateways = each.value.local_network_gateways - connection_mode = each.value.connection_mode - ipsec_policy = each.value.ipsec_policy + + network = each.value.network + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + virtual_network_gateway = each.value.virtual_network_gateway + azure_bgp_peer_addresses = each.value.azure_bgp_peer_addresses + bgp = each.value.bgp + local_network_gateways = each.value.local_network_gateways + vpn_clients = each.value.vpn_clients tags = var.tags } @@ -52,94 +36,283 @@ Below there are provided sample values for `virtual_network_gateways` map: ```hcl virtual_network_gateways = { + expressroute = { + name = "expressroute" + virtual_network_gateway = { + type = "ExpressRoute" + # vpn_type = "PolicyBased" + sku = "Standard" + # generation = "Generation1" + } + vnet_key = "transit" + subnet_key = "vpn" + network = { + public_ip_zones = ["1"] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "expressroute_pip" + } + } + } + } + expressroute_policy_based = { + name = "er_policy" + virtual_network_gateway = { + type = "ExpressRoute" + vpn_type = "PolicyBased" + sku = "Standard" + generation = "Generation2" + } + vnet_key = "er" + subnet_key = "vpn" + network = { + public_ip_zones = ["1"] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "er_policy_pip" + } + } + } + } + vpn_simple = { + name = "simple-vpn" + virtual_network_gateway = { + type = "Vpn" + # vpn_type = "PolicyBased" + sku = "VpnGw1" + generation = "Generation1" + } + vnet_key = "er" + subnet_key = "vpn" + network = { + public_ip_zones = [] + ip_configurations = { + primary = { + create_public_ip = true + name = "primary" + public_ip_name = "simple_vpn_pip" + } + } + } + } "vng" = { - name = "vng" - type = "Vpn" - sku = "VpnGw2" - generation = "Generation2" - active_active = true - enable_bgp = true - ip_configuration = [ - { - name = "001" - create_public_ip = true - public_ip_name = "pip1" - vnet_key = "transit" - subnet_name = "GatewaySubnet" - }, - { - name = "002" - create_public_ip = true - public_ip_name = "pip2" - vnet_key = "transit" - subnet_name = "GatewaySubnet" + name = "vng" + virtual_network_gateway = { + type = "Vpn" + sku = "VpnGw2AZ" + generation = "Generation2" + active_active = true + } + vnet_key = "transit" + subnet_key = "vpn" + network = { + public_ip_zones = ["1", "2", "3"] + ip_configurations = { + primary = { + name = "primary" + create_public_ip = true + public_ip_name = "vng-primary-pip" + } + secondary = { + name = "secondary" + create_public_ip = true + public_ip_name = "vng-secondary-pip" + } } - ] - ipsec_shared_key = "test123" - azure_bgp_peers_addresses = { - primary_1 = "169.254.21.2" - secondary_1 = "169.254.22.2" } - local_bgp_settings = { - asn = "65002" - peering_addresses = { - "001" = { - apipa_addresses = ["primary_1"] - }, - "002" = { - apipa_addresses = ["secondary_1"] + azure_bgp_peer_addresses = { + one_primary = "169.254.21.2" + one_secondary = "169.254.22.2" + two_primary = "169.254.21.12" + two_secondary = "169.254.22.12" + three_primary = "169.254.21.22" + three_secondary = "169.254.22.22" + } + bgp = { + enable = true + configuration = { + asn = "65002" + primary_peering_addresses = { + name = "primary" + apipa_address_keys = ["one_primary", "two_primary", "three_primary"] + } + secondary_peering_addresses = { + name = "secondary" + apipa_address_keys = ["one_secondary", "two_secondary", "three_secondary"] } } } local_network_gateways = { - "lg1" = { - local_ng_name = "lg1" - connection_name = "cn1" + lg1 = { + name = "local_gw_1" gateway_address = "8.8.8.8" - remote_bgp_settings = [{ + remote_bgp_settings = { asn = "65000" bgp_peering_address = "169.254.21.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" + } + connection = { + name = "connection_1" + custom_bgp_addresses = { + primary_key = "one_primary" + secondary_key = "one_secondary" } - ] - }, - "lg2" = { - local_ng_name = "lg2" - connection_name = "cn2" + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } + } + lg2 = { + name = "local_gw_2" gateway_address = "4.4.4.4" - remote_bgp_settings = [{ + remote_bgp_settings = { asn = "65000" bgp_peering_address = "169.254.22.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" + } + connection = { + name = "connection_2" + custom_bgp_addresses = { + primary_key = "two_primary" + secondary_key = "two_secondary" } - ] + mode = "InitiatorOnly" + shared_key = "test123" + ipsec_policies = [ + { + dh_group = "ECP384" + ike_encryption = "AES256" + ike_integrity = "SHA256" + ipsec_encryption = "AES256" + ipsec_integrity = "SHA256" + pfs_group = "ECP384" + sa_datasize = "102400000" + sa_lifetime = "14400" + } + ] + } } } - connection_mode = "InitiatorOnly" - ipsec_policy = [ - { - dh_group = "ECP384" - ike_encryption = "AES256" - ike_integrity = "SHA256" - ipsec_encryption = "AES256" - ipsec_integrity = "SHA256" - pfs_group = "ECP384" - sa_datasize = "102400000" - sa_lifetime = "14400" - } - ] } } ``` +To make defining the VNGs easy, you can use the following variable in *glue code*: + +```hcl +variable "virtual_network_gateways" { + description = "Map of virtual_network_gateways to create" + default = {} + nullable = false + type = map(object({ + name = string + virtual_network_gateway = object({ + type = optional(string) + vpn_type = optional(string) + sku = optional(string) + active_active = optional(bool) + generation = optional(string) + custom_routes = optional(map(list(string))) + }) + vnet_key = string + subnet_key = string + network = object({ + public_ip_zones = optional(list(string)) + ip_configurations = object({ + primary = object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool) + public_ip_name = string + private_ip_address_allocation = optional(string) + })) + }) + private_ip_address_enabled = optional(bool) + default_local_network_gateway_id = optional(string) + edge_zone = optional(string) + }) + azure_bgp_peer_addresses = optional(map(string)) + bgp = optional(object({ + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) + })) + })) + local_network_gateways = optional(map(object({ + name = string + remote_bgp_settings = optional(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })) + gateway_address = optional(string) + address_space = optional(list(string), []) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string) + sa_lifetime = optional(string) + })) + type = optional(string) + mode = optional(string) + shared_key = optional(string) + }) + })), {}) + vpn_clients = optional(map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + })), {}) + })) +} +``` + ## Module's Required Inputs Name | Type | Description @@ -147,13 +320,9 @@ Name | Type | Description [`name`](#name) | `string` | The name of the Virtual Network Gateway. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. [`location`](#location) | `string` | The name of the Azure region to deploy the resources in. -[`default_local_network_gateway_id`](#default_local_network_gateway_id) | `string` | The ID of the local network gateway. -[`edge_zone`](#edge_zone) | `string` | Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. -[`vpn_client_configuration`](#vpn_client_configuration) | `list` | VPN client configurations (IPSec point-to-site connections). -[`local_bgp_settings`](#local_bgp_settings) | `object` | BGP settings. -[`local_network_gateways`](#local_network_gateways) | `map` | Map of local network gateways. -[`ipsec_shared_key`](#ipsec_shared_key) | `string` | The shared IPSec key. -[`ipsec_policies`](#ipsec_policies) | `list` | IPsec policies used for Virtual Network Connection. +[`subnet_id`](#subnet_id) | `string` | An ID of a Subnet in which the Virtual Network Gateway will be created. +[`instance_settings`](#instance_settings) | `object` | A map containing the basic Virtual Network Gateway instance settings. +[`ip_configurations`](#ip_configurations) | `object` | A map defining the Public IPs used by the Virtual Network Gateway. ## Module's Optional Inputs @@ -161,19 +330,14 @@ Name | Type | Description Name | Type | Description --- | --- | --- [`tags`](#tags) | `map` | The map of tags to assign to all created resources. -[`type`](#type) | `string` | The type of the Virtual Network Gateway. -[`vpn_type`](#vpn_type) | `string` | The routing type of the Virtual Network Gateway. -[`sku`](#sku) | `string` | Configuration of the size and capacity of the virtual network gateway. -[`active_active`](#active_active) | `bool` | Active-active Virtual Network Gateway. -[`enable_bgp`](#enable_bgp) | `bool` | Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. -[`generation`](#generation) | `string` | The Generation of the Virtual Network gateway. -[`private_ip_address_enabled`](#private_ip_address_enabled) | `bool` | Controls whether the private IP is enabled on the gateway. [`zones`](#zones) | `list` | After provider version 3. -[`ip_configuration`](#ip_configuration) | `list` | IP configurations. -[`azure_bgp_peers_addresses`](#azure_bgp_peers_addresses) | `map` | Map of IP addresses used on Azure side for BGP. -[`custom_route`](#custom_route) | `list` | List of custom routes. -[`connection_type`](#connection_type) | `string` | The type of VNG connection. -[`connection_mode`](#connection_mode) | `string` | The connection mode to use. +[`edge_zone`](#edge_zone) | `string` | Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. +[`private_ip_address_enabled`](#private_ip_address_enabled) | `bool` | Controls whether the private IP is enabled on the Virtual Netowkr Gateway. +[`default_local_network_gateway_id`](#default_local_network_gateway_id) | `string` | The ID of the local network gateway. +[`azure_bgp_peer_addresses`](#azure_bgp_peer_addresses) | `map` | Map of IP addresses used on Azure side for BGP. +[`bgp`](#bgp) | `object` | A map controlling the BGP configuration used by this Virtual Network Gateway. +[`local_network_gateways`](#local_network_gateways) | `map` | Map of local network gateways and their connections. +[`vpn_clients`](#vpn_clients) | `map` | VPN client configurations (IPSec point-to-site connections). @@ -189,13 +353,13 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 -- `azurerm`, version: ~> 3.25 +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -238,129 +402,125 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### subnet_id +An ID of a Subnet in which the Virtual Network Gateway will be created. +This has to be a dedicated Subnet names `GatewaySubnet`. -#### default_local_network_gateway_id - -The ID of the local network gateway. - -Outbound Internet traffic from the virtual network, in which the gateway is created, -will be routed through local network gateway(forced tunnelling)" - - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### edge_zone - -Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. - Type: string <sup>[back to list](#modules-required-inputs)</sup> - - - -#### vpn_client_configuration - -VPN client configurations (IPSec point-to-site connections). - -List of available attributes of each VPN client configurations: -- `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. - You can provide more than one address space, e.g. in CIDR notation. -- `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL -- `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. - See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values -- `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant -- `root_certificate` - (`object`, optional, defaults to `null`) one or more root_certificate blocks - which are defined below. These root certificates are used to sign the client - certificate used by the VPN clients to connect to the gateway. -- `revoked_certificate` - (`object`, optional, defaults to `null`) one or more revoked_certificate blocks - which are defined below. -- `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. -- `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. -- `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. - The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with - the use of aad_tenant, aad_audience and aad_issuer. -- `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for - the virtual network gateway. The supported values are AAD, Radius and Certificate. - +#### instance_settings + +A map containing the basic Virtual Network Gateway instance settings. + +You configure the size, capacity and capabilities with 3/4 parameters that heavily depend on each other. Please follow the +table below for details on available combinations: + +<table> + <tr> + <th>type</th> + <th>generation</th> + <th>sku</th> + </tr> + <tr> + <td rowspan="6">ExpressRoute</td> + <td rowspan="6">N/A</td> + <td>Standard</td> + </tr> + <tr><td>HighPerformance</td></tr> + <tr><td>UltraPerformance</td></tr> + <tr><td>ErGw1AZ</td></tr> + <tr><td>ErGw2AZ</td></tr> + <tr><td>ErGw3AZ</td></tr> + <tr> + <td rowspan="11">Vpn</td> + <td rowspan="3">Generation1</td> + <td>Basic</td> + <tr><td>VpnGw1</td></tr> + <tr><td>VpnGw1AZ</td></tr> + <tr> + <td rowspan="8">Generation1/Generation2</td> + <td>VpnGw2</td> + </tr> + <tr><td>VpnGw3</td></tr> + <tr><td>VpnGw4</td></tr> + <tr><td>VpnGw5</td></tr> + <tr><td>VpnGw2AZ</td></tr> + <tr><td>VpnGw3AZ</td></tr> + <tr><td>VpnGw4AZ</td></tr> + <tr><td>VpnGw5AZ</td></tr> +</table> + +Following properties are available: + +- `type` - (`string`, optional, defaults to `Vpn`) the type of the Virtual Network Gateway, possible values are: `Vpn` + or `ExpressRoute`. +- `vpn_type` - (`string`, optional, defaults to `RouteBased`) the routing type of the Virtual Network Gateway, possible + values are: `RouteBased` or `PolicyBased`. +- `generation` - (`string`, optional, defaults to `Generation1`) the Generation of the Virtual Network gateway, possible + values are: `None`, `Generation1` or `Generation2`. This property is ignored when type is set to + `ExpressRoute`. +- `sku` - (`string`, optional, defaults to `Basic`) sets the size and capacity of the virtual network gateway. +- `active_active` - (`bool`, optional, defaults to `false`) when set to true creates an active-active Virtual Network Gateway, + active-passive otherwise. Not supported for `Basic` and `Standard` SKUs. Type: ```hcl -list(object({ - address_space = string - aad_tenant = optional(string) - aad_audience = optional(string) - aad_issuer = optional(string) - root_certificate = optional(object({ - name = string - public_cert_data = string - })) - revoked_certificate = optional(object({ - name = string - thumbprint = string - })) - radius_server_address = optional(string) - radius_server_secret = optional(string) - vpn_client_protocols = optional(list(string)) - vpn_auth_types = optional(list(string)) - })) +object({ + type = optional(string, "Vpn") + vpn_type = optional(string, "RouteBased") + generation = optional(string, "Generation1") + sku = optional(string, "Basic") + active_active = optional(bool, false) + + }) ``` <sup>[back to list](#modules-required-inputs)</sup> +#### ip_configurations -#### local_bgp_settings - -BGP settings. - -Attributes: -- `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. -- `peering_addresses` - (`map`, required) a map of peering addresses, which contains 1 (for active-standby) - or 2 objects (for active-active), where key is the ip configuration name and with attributes: - - `apipa_addresses` - (`list`, required) is the list of keys for IP addresses defined in variable azure_bgp_peers_addresses - - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to - the BGP peer of the Virtual Network Gateway. -- `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes - which have been learned through BGP peering. +A map defining the Public IPs used by the Virtual Network Gateway. + +Following properties are available: +- `primary` - (`map`, required) a map defining the primary Public IP address, following properties are available: + - `name` - (`string`, required) name of the IP config. + - `create_public_ip` - (`bool`, optional, defaults to `true`) controls if a Public IP is created or sourced. + - `public_ip_name` - (`string`, required) name of a Public IP resource, depending on the value of + `create_public_ip` property this will be a name of a newly create or existing resource + (for values of `true` and `false` accordingly). + - `dynamic_private_ip_allocation` - (`bool`, optional, defaults to `true`) controls if the private IP address is assigned + dynamically or statically. +- `secondary` - (`map`, optional, defaults to `null`) a map defining the secondary Public IP address resource. Required only + for `type` set to `Vpn` and `active-active` set to `true`. Same properties available as for `primary` property. -Example: - -```hcl -local_bgp_settings = { - asn = "65001" - peering_addresses = { - "001" = { - apipa_addresses = ["primary_1", "primary_2"] - }, - "002" = { - apipa_addresses = ["secondary_1", "secondary_2"] - } - } -} -``` Type: ```hcl object({ - asn = string - peering_addresses = map(object({ - apipa_addresses = list(string) - default_addresses = optional(list(string)) + primary = object({ + name = string + create_public_ip = optional(bool, true) + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool, true) + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") })) - peer_weight = optional(number) }) ``` @@ -368,179 +528,10 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> -#### local_network_gateways - -Map of local network gateways. - -Every object in the map contains attributes: -- local_ng_name - (`string`, required) the name of the local network gateway. -- connection_name - (`string`, required) the name of the virtual network gateway connection. -- remote_bgp_settings - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: - - asn - (`string`, required) the BGP speaker's ASN. - - bgp_peering_address - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. - - peer_weight - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. -- gateway_address - (`string`, optional, defaults to `null`) the gateway IP address to connect with. -- address_space - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces - the gateway exposes. -- custom_bgp_addresses - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, - which can only be used on IPSec / active-active connections. Object contains 2 attributes: - - primary - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway - ip_configuration (first one) - - secondary - (`string`, optional, defaults to `null`) single IP address that is part of - the azurerm_virtual_network_gateway ip_configuration (second one) - -Example: - -```hcl -local_network_gateways = { - "lg1" = { - local_ng_name = "001" - connection_name = "001" - gateway_address = "PUBLIC_IP_1" - remote_bgp_settings = [{ - asn = "65002" - bgp_peering_address = "169.254.21.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - } - "lg2" = { - local_ng_name = "002" - connection_name = "002" - gateway_address = "PUBLIC_IP_2" - remote_bgp_settings = [{ - asn = "65003" - bgp_peering_address = "169.254.21.5" - }] - custom_bgp_addresses = [ - { - primary = "primary_2" - secondary = "secondary_2" - } - ] - } - "lg3" = { - local_ng_name = "003" - connection_name = "003" - gateway_address = "PUBLIC_IP_3" - remote_bgp_settings = [{ - asn = "65002" - bgp_peering_address = "169.254.22.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - } - "lg4" = { - local_ng_name = "004" - connection_name = "004" - gateway_address = "PUBLIC_IP_4" - remote_bgp_settings = [{ - asn = "65003" - bgp_peering_address = "169.254.22.5" - }] - custom_bgp_addresses = [ - { - primary = "primary_2" - secondary = "secondary_2" - } - ] - } -} -``` - - -Type: - -```hcl -map(object({ - local_ng_name = string - connection_name = string - remote_bgp_settings = optional(list(object({ - asn = string - bgp_peering_address = string - peer_weight = optional(number) - })), []) - gateway_address = optional(string) - address_space = optional(list(string), []) - custom_bgp_addresses = optional(list(object({ - primary = string - secondary = optional(string) - })), []) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - - - -#### ipsec_shared_key - -The shared IPSec key. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### ipsec_policies - -IPsec policies used for Virtual Network Connection. -Single policy contains attributes: -- `dh_group` - (`string`, required) The DH group used in IKE phase 1 for initial SA. -- `ike_encryption` - (`string`, required) The IKE encryption algorithm. -- `ike_integrity` - (`string`, required) The IKE integrity algorithm. -- `ipsec_encryption` - (`string`, required) The IPSec encryption algorithm. -- `ipsec_integrity` - (`string`, required) The IPSec integrity algorithm. -- `pfs_group` - (`string`, required) The DH group used in IKE phase 2 for new child SA. -- `sa_datasize` - (`string`, optional, defaults to `102400000`) The IPSec SA payload size in KB. - Must be at least 1024 KB. -- `sa_lifetime` - (`string`, optional, defaults to `27000`) The IPSec SA lifetime in seconds. - Must be at least 300 seconds. - -Example: -```hcl -ipsec_policy = [ - { - dh_group = "ECP384" - ike_encryption = "AES256" - ike_integrity = "SHA256" - ipsec_encryption = "AES256" - ipsec_integrity = "SHA256" - pfs_group = "ECP384" - sa_datasize = "102400000" - sa_lifetime = "27000" - } -] -``` -Type: - -```hcl -list(object({ - dh_group = string - ike_encryption = string - ike_integrity = string - ipsec_encryption = string - ipsec_integrity = string - pfs_group = string - sa_datasize = optional(string, "102400000") - sa_lifetime = optional(string, "27000") - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -560,80 +551,36 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### type -The type of the Virtual Network Gateway. - -Type: string - -Default value: `Vpn` +#### zones -<sup>[back to list](#modules-optional-inputs)</sup> +After provider version 3.x you need to specify in which availability zone(s) you want to place a Public IP address. -#### vpn_type +For zone-redundant with 3 availability zones in current region, value will be: +```["1","2","3"]``` -The routing type of the Virtual Network Gateway. -Type: string +Type: list(string) -Default value: `RouteBased` +Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> -#### sku - -Configuration of the size and capacity of the virtual network gateway. - -Valid option depends on the type, vpn_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. -Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway. +#### edge_zone +Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist. Type: string -Default value: `Basic` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### active_active - -Active-active Virtual Network Gateway. - -If true, an active-active Virtual Network Gateway will be created. -An active-active gateway requires a HighPerformance or an UltraPerformance SKU. -If false, an active-standby gateway will be created. Defaults to false. - - -Type: bool - -Default value: `false` - -<sup>[back to list](#modules-optional-inputs)</sup> - - - -#### enable_bgp - -Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway. - -Type: bool - -Default value: `false` +Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> -#### generation -The Generation of the Virtual Network gateway. - -Type: string - -Default value: `Generation1` - -<sup>[back to list](#modules-optional-inputs)</sup> #### private_ip_address_enabled -Controls whether the private IP is enabled on the gateway. +Controls whether the private IP is enabled on the Virtual Netowkr Gateway. Type: bool @@ -641,72 +588,21 @@ Default value: `false` <sup>[back to list](#modules-optional-inputs)</sup> -#### zones +#### default_local_network_gateway_id -After provider version 3.x you need to specify in which availability zone(s) you want to place IP. +The ID of the local network gateway. -For zone-redundant with 3 availability zones in current region value will be: -```["1","2","3"]``` +When set, the outbound Internet traffic from the virtual network, in which the gateway is created, will be routed through local +network gateway (forced tunnelling). -Type: list(string) +Type: string Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> -#### ip_configuration - -IP configurations. - -List of available attributes of each IP configuration. - -- `name` - (`string`, required) name of the IP configuration -- `create_public_ip` - (`bool`, required) - true if public IP needs to be created -- `public_ip_name` - (`string`, required) name of the public IP resource used, when there is no need - to create new one -- `private_ip_address_allocation` - (`string`, optional, defaults to `Dynamic`) defines how the private IP address of - the gateways virtual interface is assigned. -- `subnet_id` - (`string`, required) the ID of the gateway subnet of a virtual network in which - the virtual network gateway will be created. - -Example: - -```hcl -ip_configuration = [ - { - name = "001" - create_public_ip = true - subnet_id = "ID_for_subnet_GatewaySubnet" - }, - { - name = "002" - create_public_ip = true - subnet_id = "ID_for_subnet_GatewaySubnet" - } -] -``` - - -Type: - -```hcl -list(object({ - name = string - create_public_ip = bool - public_ip_name = string - private_ip_address_allocation = optional(string, "Dynamic") - subnet_id = string - })) -``` - - -Default value: `[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -#### azure_bgp_peers_addresses +#### azure_bgp_peer_addresses Map of IP addresses used on Azure side for BGP. @@ -732,51 +628,190 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### bgp -#### custom_route - -List of custom routes. +A map controlling the BGP configuration used by this Virtual Network Gateway. -Every object in the list contains attributes: -- `address_prefixes` - (`list`, optional, defaults to `null`) a list of address blocks reserved for this virtual network in CIDR notation as defined below. +Following properties are available: +- `enable` - (`bool`, optional, defaults to `false`) controls whether BGP (Border Gateway Protocol) will be enabled for + this Virtual Network Gateway +- `configuration` - (`map`, optional, defaults to `null`) contains BGP configuration, required when `enable` is set to `true`. + Contains the following properties: + - `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. + - `peer_weigth` - (`number`, optional`, defaults to `null`) weight added to routes which have been learned + through BGP peering. Values are between `0` and `100`. + - `primary_peering_addresses` - (`map`, required) a map defining peering addresses, following properties are available: + - `name` - (`string`, required) name of the configuration. + - `apipa_address_keys` - (`list`, required) list of keys identifying addresses defined in `azure_bgp_peer_addresses`. + - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to the BGP peer of + the Virtual Network Gateway. + - `secondary_peering_addresses` - (`map`, optional, defaults to `null`) a map defining secondary peering addresses, required + only for `active-active` deployments. Same properties are available. Type: ```hcl -list(object({ - address_prefixes = optional(list(string)) - })) +object({ + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) + })) + }) ``` -Default value: `[]` +Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> +#### local_network_gateways -#### connection_type +Map of local network gateways and their connections. + +Every object in the map contains following attributes: + +- `name` - (`string`, required) the name of the local network gateway. +- `remote_bgp_settings` - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: + - `asn` - (`string`, required) the BGP speaker's ASN. + - `bgp_peering_address` - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. + - `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. +- `gateway_address` - (`string`, optional, defaults to `null`) the gateway IP address to connect with. +- `address_space` - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces + the gateway exposes. +- `custom_bgp_addresses` - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, + which can only be used on IPSec / active-active connections. Object contains 2 attributes: + - `primary_key` - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway + ip_configuration (first one) + - `secondary_key` - (`string`, optional, defaults to `null`) single IP address that is part of the + azurerm_virtual_network_gateway ip_configuration (second one) +- `connection` - (`map`, required) a map defining configuration for a VPN connection between Azure VNG and on-premises + VPN device. Contains the following properties: + - `name` - (`string`, required) the name of the virtual network gateway connection. + - `ipsec_policies` - (`list`, required) list of IPsec policies used for Virtual Network Connection. A single policy consist + of the following properties: + - `dh_group` - (`string`, required) the DH group used in IKE phase 1 for initial SA. + - `ike_encryption` - (`string`, required) the IKE encryption algorithm. + - `ike_integrity` - (`string`, required) the IKE integrity algorithm. + - `ipsec_encryption` - (`string`, required) the IPSec encryption algorithm. + - `ipsec_integrity` - (`string`, required) the IPSec integrity algorithm. + - `pfs_group` - (`string`, required) the DH group used in IKE phase 2 for new child SA. + - `sa_datasize` - (`string`, optional, defaults to `102400000`) the IPSec SA payload size in KB. Must be at least + 1024 KB. + - `sa_lifetime` - (`string`, optional, defaults to `27000`) the IPSec SA lifetime in seconds. Must be at least 300 + seconds. + - `connection_type` - (`string`, optional, defaults to `IPsec`) a VPN connection type, can be one of: `IPsec`, `ExpressRoute` + or `Vnet2Vnet`. + - `connection_mode` - (`string`, optional, defaults to `Default`) connection mode to use, can be one of: `Default`, + `InitiatorOnly` or `ResponderOnly`. + - `shared_key` - (`string`, optional, defaults to `null`) a shared IPSec key used during connection creation. -The type of VNG connection. -Type: string +Type: + +```hcl +map(object({ + name = string + remote_bgp_settings = optional(object({ + asn = string + bgp_peering_address = string + peer_weight = optional(number) + })) + address_space = optional(list(string), []) + gateway_address = optional(string) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string, "102400000") + sa_lifetime = optional(string, "27000") + })) + type = optional(string, "IPsec") + mode = optional(string, "Default") + shared_key = optional(string) + }) + })) +``` -Default value: `IPsec` + +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### connection_mode +#### vpn_clients -The connection mode to use. +VPN client configurations (IPSec point-to-site connections). -Type: string +This is a map, where each value is a VPN client configuration. Keys are just names describing a particular configuration. They +are not being used in the actual deployment. + +Following properties are available: + +- `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. + You can provide more than one address space, e.g. in CIDR notation. +- `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL +- `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. + See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values +- `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant +- `root_certificates` - (`map`, optional, defaults to `{}`) a map defining root certificates used to sign client + certificates used by VPN clients. The key is a name of the certificate, value is the public + certificate in PEM format. +- `revoked_certificates` - (`map`, optional, defaults to `null`) a map defining revoked certificates. The key is a name of + the certificate, value is the thumbprint of the certificate. +- `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. +- `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. +- `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. + The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with + the use of aad_tenant, aad_audience and aad_issuer. +- `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for + the virtual network gateway. The supported values are AAD, Radius and Certificate. +- `custom_routes` - (`map`, optional, defaults to `{}`) a map defining custom routes. Each route is a list of address + blocks reserved for this Virtual Network (in CIDR notation). Keys in this map are only to identify + the CIDR blocks, values are lists of the actual address blocks. -Default value: `Default` -<sup>[back to list](#modules-optional-inputs)</sup> +Type: + +```hcl +map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + custom_routes = optional(map(list(string)), {}) + })) +``` +Default value: `map[]` +<sup>[back to list](#modules-optional-inputs)</sup> <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_network_gateway/main.tf b/modules/virtual_network_gateway/main.tf index ab7bfd67..cee0e3cb 100644 --- a/modules/virtual_network_gateway/main.tf +++ b/modules/virtual_network_gateway/main.tf @@ -1,83 +1,116 @@ # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { - for_each = toset([for ip_configuration in var.ip_configuration : ip_configuration.name if ip_configuration.create_public_ip]) + for_each = { for k, v in var.ip_configurations : k => v if try(v.create_public_ip, false) } resource_group_name = var.resource_group_name location = var.location - name = each.value + name = each.value.public_ip_name allocation_method = "Static" sku = "Standard" zones = var.zones tags = var.tags - - lifecycle { - precondition { - condition = var.active_active ? ( - var.zones != null ? length(var.zones) == 3 : false - ) : true - error_message = "For active-active you need to configure zones" - } - } } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "exists" { - for_each = toset([for ip_configuration in var.ip_configuration : ip_configuration.name if !ip_configuration.create_public_ip]) + for_each = { for k, v in var.ip_configurations : k => v if !try(v.create_public_ip, true) } - name = each.value + name = each.value.public_ip_name resource_group_name = var.resource_group_name } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway resource "azurerm_virtual_network_gateway" "this" { - location = var.location - resource_group_name = var.resource_group_name name = var.name + resource_group_name = var.resource_group_name + location = var.location - type = var.type - vpn_type = var.vpn_type - sku = var.sku - - active_active = var.active_active + type = var.instance_settings.type + vpn_type = var.instance_settings.vpn_type + sku = var.instance_settings.sku + generation = var.instance_settings.ype == "VPN" ? var.instance_settings.generation : null + active_active = var.instance_settings.active_active default_local_network_gateway_id = var.default_local_network_gateway_id edge_zone = var.edge_zone - enable_bgp = var.enable_bgp - generation = var.generation private_ip_address_enabled = var.private_ip_address_enabled dynamic "ip_configuration" { - for_each = var.ip_configuration + for_each = [for _, v in var.ip_configurations : v if v != null] + content { - name = ip_configuration.value.name - public_ip_address_id = ip_configuration.value.create_public_ip ? azurerm_public_ip.this[ip_configuration.value.name].id : data.azurerm_public_ip.exists[ip_configuration.value.name].id + name = ip_configuration.value.name + public_ip_address_id = ip_configuration.value.create_public_ip ? ( + azurerm_public_ip.this[ip_configuration.value.name].id + ) : data.azurerm_public_ip.exists[ip_configuration.value.name].id private_ip_address_allocation = ip_configuration.value.private_ip_address_allocation - subnet_id = ip_configuration.value.subnet_id + subnet_id = var.subnet_id + } + } + + enable_bgp = try(var.bgp.enable, false) + + dynamic "bgp_settings" { + for_each = try(var.bgp.enable, false) ? [1] : [] + content { + asn = var.bgp.configuration.asn + + peering_addresses { + ip_configuration_name = var.bgp.configuration.primary_peering_addresses.name + apipa_addresses = [ + for i in var.bgp.configuration.primary_peering_addresses.apipa_address_keys : var.azure_bgp_peer_addresses[i] + ] + default_addresses = var.bgp.configuration.primary_peering_addresses.default_addresses + } + + dynamic "peering_addresses" { + for_each = var.bgp.configuration.secondary_peering_addresses != null ? [1] : [] + content { + ip_configuration_name = var.bgp.configuration.secondary_peering_addresses.name + apipa_addresses = [ + for i in var.bgp.configuration.secondary_peering_addresses.apipa_address_keys : var.azure_bgp_peer_addresses[i] + ] + default_addresses = var.bgp.configuration.secondary_peering_addresses.default_addresses + } + } + + peer_weight = var.bgp.configuration.peer_weight + } + } + + dynamic "custom_route" { + for_each = var.vpn_clients.custom_routes + content { + address_prefixes = custom_route.value } } dynamic "vpn_client_configuration" { - for_each = var.vpn_client_configuration + for_each = var.vpn_clients + content { address_space = vpn_client_configuration.value.address_space aad_tenant = vpn_client_configuration.value.aad_tenant aad_audience = vpn_client_configuration.value.aad_audience aad_issuer = vpn_client_configuration.value.aad_issuer + dynamic "root_certificate" { - for_each = coalesce({ for t in vpn_client_configuration.value.root_certificate : t.name => t }, {}) + for_each = vpn_client_configuration.value.root_certificates content { - name = root_certificate.value.name - public_cert_data = root_certificate.value.public_cert_data + name = root_certificate.key + public_cert_data = root_certificate.value } } + dynamic "revoked_certificate" { - for_each = coalesce({ for t in vpn_client_configuration.value.revoked_certificate : t.name => t }, {}) + for_each = vpn_client_configuration.value.revoked_certificates content { - name = revoked_certificate.value.name - thumbprint = revoked_certificate.value.thumbprint + name = revoked_certificate.key + thumbprint = revoked_certificate.value } } + radius_server_address = vpn_client_configuration.value.radius_server_address radius_server_secret = vpn_client_configuration.value.radius_server_secret vpn_client_protocols = vpn_client_configuration.value.vpn_client_protocols @@ -85,44 +118,51 @@ resource "azurerm_virtual_network_gateway" "this" { } } - dynamic "bgp_settings" { - for_each = [var.local_bgp_settings] - content { - asn = bgp_settings.value.asn - dynamic "peering_addresses" { - for_each = bgp_settings.value.peering_addresses - content { - ip_configuration_name = peering_addresses.key - apipa_addresses = [for i in peering_addresses.value.apipa_addresses : var.azure_bgp_peers_addresses[i]] - default_addresses = peering_addresses.value.default_addresses - } - } - peer_weight = bgp_settings.value.peer_weight - } - } - - dynamic "custom_route" { - for_each = var.custom_route - content { - address_prefixes = custom_route.value.address_prefixes - } - } - tags = var.tags lifecycle { - precondition { - condition = (contains(["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.sku) && var.generation == "Generation2" - ) || (contains(["Basic", "Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "VpnGw1", "VpnGw1AZ"], var.sku) && var.generation == "Generation1") - error_message = "Generation2 is only value for a sku larger than VpnGw2 or VpnGw2AZ" + precondition { # bgp + condition = var.instance_settings.type == "ExpressRoute" ? var.bgp == null : true + error_message = <<-EOF + VNG Name: [${var.name}] + BGP configuration is supported only for Virtual Network Gateways of "VPN" type, `var.bgp` should have a value of `null`. + EOF } - precondition { - condition = var.active_active && length(keys(var.local_bgp_settings.peering_addresses)) == 2 || !var.active_active && length(keys(var.local_bgp_settings.peering_addresses)) == 1 - error_message = "Map of peering addresses has to contain 1 (for active-standby) or 2 objects (for active-active)." + precondition { # azure_bgp_peer_addresses + condition = var.instance_settings.type == "ExpressRoute" ? length(var.azure_bgp_peer_addresses) == 0 : true + error_message = <<-EOF + VNG Name: [${var.name}] + BGP configuration is supported only for Virtual Network Gateways of "VPN" type, `var.azure_bgp_peer_addresses` map should + be empty. + EOF } - precondition { - condition = var.active_active && length(keys(var.azure_bgp_peers_addresses)) >= 2 || !var.active_active && length(keys(var.azure_bgp_peers_addresses)) >= 1 - error_message = "For active-standby you need to configure at least 1 custom Azure APIPA BGP IP address, for active-active at least 2." + precondition { # ip_configurations.secondary + condition = var.instance_settings.active_active ? ( + var.ip_configurations.secondary != null + ) : var.ip_configurations.secondary == null + error_message = <<-EOF + VNG Name: [${var.name}] + The `ip_configurations.secondary` property is required ONLY when `instance_settings.active_active` property is set + to `true`. + EOF + } + precondition { # bgp.configuration.secondary_peering_addresses + condition = var.instance_settings.active_active ? ( + var.bgp.configuration.secondary_peering_addresses != null + ) : try(var.bgp.configuration.secondary_peering_addresses, null) == null + error_message = <<-EOF + VNG Name: [${var.name}] + The `bgp.configuration.secondary_peering_addresses` property is required ONLY when `instance_settings.active_active` + property is set to `true`. + EOF + } + precondition { # zones + condition = var.instance_settings.type == "Vpn" && can( + regex("^\\w{5,6}$", var.instance_settings.sku) + ) ? length(coalesce(var.zones, [])) == 0 : true + error_message = <<-EOF + For Virtual Network Gateways of `Vpn` type, sku of non `AZ` type, the `zones` variable has to be an empty list. + EOF } } } @@ -131,18 +171,19 @@ resource "azurerm_virtual_network_gateway" "this" { resource "azurerm_local_network_gateway" "this" { for_each = var.local_network_gateways - name = each.value.local_ng_name + name = each.value.name resource_group_name = var.resource_group_name location = var.location gateway_address = each.value.gateway_address address_space = each.value.address_space + dynamic "bgp_settings" { - for_each = each.value.remote_bgp_settings + for_each = each.value.remote_bgp_settings != null ? [1] : [] content { - asn = bgp_settings.value.asn - bgp_peering_address = bgp_settings.value.bgp_peering_address - peer_weight = bgp_settings.value.peer_weight + asn = each.value.remote_bgp_settings.asn + bgp_peering_address = each.value.remote_bgp_settings.bgp_peering_address + peer_weight = each.value.remote_bgp_settings.peer_weight } } @@ -153,29 +194,29 @@ resource "azurerm_local_network_gateway" "this" { resource "azurerm_virtual_network_gateway_connection" "this" { for_each = var.local_network_gateways - name = each.value.connection_name + name = each.value.connection.name location = var.location resource_group_name = var.resource_group_name - type = var.connection_type + type = each.value.connection.type virtual_network_gateway_id = azurerm_virtual_network_gateway.this.id local_network_gateway_id = azurerm_local_network_gateway.this[each.key].id - enable_bgp = var.enable_bgp + enable_bgp = var.bgp.enable local_azure_ip_address_enabled = var.private_ip_address_enabled - shared_key = var.ipsec_shared_key + shared_key = each.value.connection.shared_key dynamic "custom_bgp_addresses" { - for_each = each.value.custom_bgp_addresses + for_each = each.value.connection.custom_bgp_addresses != null ? [1] : [] content { - primary = var.azure_bgp_peers_addresses[custom_bgp_addresses.value.primary] - secondary = try(var.azure_bgp_peers_addresses[custom_bgp_addresses.value.secondary], null) + primary = var.azure_bgp_peer_addresses[each.value.connection.custom_bgp_addresses.primary_key] + secondary = try(var.azure_bgp_peer_addresses[each.value.connection.custom_bgp_addresses.secondary_key], null) } } - connection_mode = var.connection_mode + connection_mode = each.value.connection.mode dynamic "ipsec_policy" { - for_each = var.ipsec_policies + for_each = each.value.connection.ipsec_policies content { dh_group = ipsec_policy.value.dh_group ike_encryption = ipsec_policy.value.ike_encryption diff --git a/modules/virtual_network_gateway/variables.tf b/modules/virtual_network_gateway/variables.tf index 7b3a2a7d..fc43f271 100644 --- a/modules/virtual_network_gateway/variables.tf +++ b/modules/virtual_network_gateway/variables.tf @@ -1,10 +1,8 @@ -# Main resource variable "name" { description = "The name of the Virtual Network Gateway." type = string } -# Common settings variable "resource_group_name" { description = "The name of the Resource Group to use." type = string @@ -21,103 +19,20 @@ variable "tags" { type = map(string) } -# Virtual Network Gateway -variable "type" { - description = "The type of the Virtual Network Gateway." - default = "Vpn" - nullable = false - type = string - validation { - condition = contains(["Vpn", "ExpressRoute"], var.type) - error_message = "Valid options are Vpn or ExpressRoute" - } -} - -variable "vpn_type" { - description = "The routing type of the Virtual Network Gateway." - default = "RouteBased" - nullable = false - type = string - validation { - condition = contains(["RouteBased", "PolicyBased"], var.vpn_type) - error_message = "Valid options are RouteBased or PolicyBased" - } -} - -variable "sku" { - description = <<-EOF - Configuration of the size and capacity of the virtual network gateway. - - Valid option depends on the type, vpn_type and generation arguments. A PolicyBased gateway only supports the Basic SKU. - Further, the UltraPerformance SKU is only supported by an ExpressRoute gateway. - EOF - default = "Basic" - nullable = false - type = string - validation { - condition = contains(["Basic", "Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "VpnGw1", "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw1AZ", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.sku) - error_message = "Valid options are Basic, Standard, HighPerformance, UltraPerformance, ErGw1AZ, ErGw2AZ, ErGw3AZ, VpnGw1, VpnGw2, VpnGw3, VpnGw4,VpnGw5, VpnGw1AZ, VpnGw2AZ, VpnGw3AZ,VpnGw4AZ and VpnGw5AZ and depend on the type, vpn_type and generation arguments" - } -} - -variable "active_active" { - description = <<-EOF - Active-active Virtual Network Gateway. - - If true, an active-active Virtual Network Gateway will be created. - An active-active gateway requires a HighPerformance or an UltraPerformance SKU. - If false, an active-standby gateway will be created. Defaults to false. - EOF - default = false - nullable = false - type = bool -} - -variable "default_local_network_gateway_id" { +variable "subnet_id" { description = <<-EOF - The ID of the local network gateway. + An ID of a Subnet in which the Virtual Network Gateway will be created. - Outbound Internet traffic from the virtual network, in which the gateway is created, - will be routed through local network gateway(forced tunnelling)" + This has to be a dedicated Subnet named `GatewaySubnet`. EOF type = string } -variable "edge_zone" { - description = "Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist." - type = string -} - -variable "enable_bgp" { - description = "Controls whether BGP (Border Gateway Protocol) will be enabled for this Virtual Network Gateway." - default = false - nullable = false - type = bool -} - -variable "generation" { - description = "The Generation of the Virtual Network gateway." - type = string - default = "Generation1" - nullable = false - validation { - condition = contains(["Generation1", "Generation2", "None"], coalesce(var.generation, "Generation1")) - error_message = "Valid options are Generation1, Generation2 or None" - } -} - -variable "private_ip_address_enabled" { - description = "Controls whether the private IP is enabled on the gateway." - default = false - nullable = false - type = bool -} - variable "zones" { description = <<-EOF - After provider version 3.x you need to specify in which availability zone(s) you want to place IP. + After provider version 3.x you need to specify in which availability zone(s) you want to place a Public IP address. - For zone-redundant with 3 availability zones in current region value will be: + For zone-redundant with 3 availability zones in current region, value will be: ```["1","2","3"]``` EOF default = null @@ -126,110 +41,195 @@ variable "zones" { condition = var.zones == null || (var.zones != null ? ( length(var.zones) == 3 && length(setsubtract(var.zones, ["1", "2", "3"])) == 0 ) : true) - error_message = "No zones or all 3 zones are expected" + error_message = "No zones or all 3 zones are expected." } } -variable "ip_configuration" { - description = <<-EOF - IP configurations. +variable "edge_zone" { + description = "Specifies the Edge Zone within the Azure Region where this Virtual Network Gateway should exist." + default = null + type = string +} - List of available attributes of each IP configuration. +variable "instance_settings" { + description = <<-EOF + A map containing the basic Virtual Network Gateway instance settings. + + You configure the size, capacity and capabilities with 3/4 parameters that heavily depend on each other. Please follow the + table below for details on available combinations: + + <table> + <tr> + <th>type</th> + <th>generation</th> + <th>sku</th> + </tr> + <tr> + <td rowspan="6">ExpressRoute</td> + <td rowspan="6">N/A</td> + <td>Standard</td> + </tr> + <tr><td>HighPerformance</td></tr> + <tr><td>UltraPerformance</td></tr> + <tr><td>ErGw1AZ</td></tr> + <tr><td>ErGw2AZ</td></tr> + <tr><td>ErGw3AZ</td></tr> + <tr> + <td rowspan="11">Vpn</td> + <td rowspan="3">Generation1</td> + <td>Basic</td> + <tr><td>VpnGw1</td></tr> + <tr><td>VpnGw1AZ</td></tr> + <tr> + <td rowspan="8">Generation1/Generation2</td> + <td>VpnGw2</td> + </tr> + <tr><td>VpnGw3</td></tr> + <tr><td>VpnGw4</td></tr> + <tr><td>VpnGw5</td></tr> + <tr><td>VpnGw2AZ</td></tr> + <tr><td>VpnGw3AZ</td></tr> + <tr><td>VpnGw4AZ</td></tr> + <tr><td>VpnGw5AZ</td></tr> + </table> + + Following properties are available: + + - `type` - (`string`, optional, defaults to `Vpn`) the type of the Virtual Network Gateway, possible values are: `Vpn` + or `ExpressRoute`. + - `vpn_type` - (`string`, optional, defaults to `RouteBased`) the routing type of the Virtual Network Gateway, possible + values are: `RouteBased` or `PolicyBased`. + - `generation` - (`string`, optional, defaults to `Generation1`) the Generation of the Virtual Network gateway, possible + values are: `None`, `Generation1` or `Generation2`. This property is ignored when type is set to + `ExpressRoute`. + - `sku` - (`string`, optional, defaults to `Basic`) sets the size and capacity of the virtual network gateway. + - `active_active` - (`bool`, optional, defaults to `false`) when set to true creates an active-active Virtual Network Gateway, + active-passive otherwise. Not supported for `Basic` and `Standard` SKUs. + EOF + type = object({ + type = optional(string, "Vpn") + vpn_type = optional(string, "RouteBased") + generation = optional(string, "Generation1") + sku = optional(string, "Basic") + active_active = optional(bool, false) - - `name` - (`string`, required) name of the IP configuration - - `create_public_ip` - (`bool`, required) - true if public IP needs to be created - - `public_ip_name` - (`string`, required) name of the public IP resource used, when there is no need - to create new one - - `private_ip_address_allocation` - (`string`, optional, defaults to `Dynamic`) defines how the private IP address of - the gateways virtual interface is assigned. - - `subnet_id` - (`string`, required) the ID of the gateway subnet of a virtual network in which - the virtual network gateway will be created. + }) + validation { # type + condition = contains(["Vpn", "ExpressRoute"], var.virtual_network_gateway.type) + error_message = <<-EOF + The `virtual_network_gateway.type` property can take one of the following values: "Vpn" or "ExpressRoute". + EOF + } + validation { # vpn_type + condition = contains(["RouteBased", "PolicyBased"], var.virtual_network_gateway.vpn_type) + error_message = <<-EOF + The `virtual_network_gateway.vpn_type` property can take one of the following values: "RouteBased" or "PolicyBased". + EOF + } + validation { # generation + condition = contains(["Generation1", "Generation2", "None"], var.virtual_network_gateway.generation) + error_message = <<-EOF + The `virtual_network_gateway.generation` property can take one of the following values: "Generation1" or "Generation2" + or "None". + EOF + } + validation { # type, generation & sku + condition = var.virtual_network_gateway.generation == "Generation2" && var.virtual_network_gateway.type == "Vpn" ? contains( + ["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.virtual_network_gateway.sku + ) : true + error_message = <<-EOF + For `sku` of "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ" or "VpnGw5AZ" the `generation` + property has to be set to `Generation2` and `type` to `Vpn`. + EOF + } + validation { # type & sku + condition = (var.virtual_network_gateway.type == "Vpn" && contains( + ["Basic", "VpnGw1", "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw1AZ", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], + var.virtual_network_gateway.sku + )) || ( + var.virtual_network_gateway.type == "ExpressRoute" && contains( + ["Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ"], var.virtual_network_gateway.sku + ) + ) + error_message = <<-EOF + Invalid combination of `sku` and `type`. Please check documentation for `var.virtual_network_gateway`. + EOF + } + validation { # active_active + condition = var.virtual_network_gateway.type == "ExpressRoute" ? !var.virtual_network_gateway.active_active : true + error_message = <<-EOF + The `active_active` property has to be set to `false` (default) when type is `ExpressRoute`. + EOF + } +} - Example: +variable "ip_configurations" { + description = <<-EOF + A map defining the Public IPs used by the Virtual Network Gateway. + + Following properties are available: + - `primary` - (`map`, required) a map defining the primary Public IP address, following properties are available: + - `name` - (`string`, required) name of the IP config. + - `create_public_ip` - (`bool`, optional, defaults to `true`) controls if a Public IP is created or sourced. + - `public_ip_name` - (`string`, required) name of a Public IP resource, depending on the value of + `create_public_ip` property this will be a name of a newly create or existing resource + (for values of `true` and `false` accordingly). + - `dynamic_private_ip_allocation` - (`bool`, optional, defaults to `true`) controls if the private IP address is assigned + dynamically or statically. + - `secondary` - (`map`, optional, defaults to `null`) a map defining the secondary Public IP address resource. Required only + for `type` set to `Vpn` and `active-active` set to `true`. Same properties available as for `primary` property. - ```hcl - ip_configuration = [ - { - name = "001" - create_public_ip = true - subnet_id = "ID_for_subnet_GatewaySubnet" - }, - { - name = "002" - create_public_ip = true - subnet_id = "ID_for_subnet_GatewaySubnet" - } - ] - ``` EOF - default = [] - nullable = false - type = list(object({ - name = string - create_public_ip = bool - public_ip_name = string - private_ip_address_allocation = optional(string, "Dynamic") - subnet_id = string - })) - validation { - condition = length(flatten([for _, config in var.ip_configuration : config.name])) == length(distinct(flatten([for _, config in var.ip_configuration : config.name]))) - error_message = "The `name` property has to be unique among all IP configuration." + type = object({ + primary = object({ + name = string + create_public_ip = optional(bool, true) + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") + }) + secondary = optional(object({ + name = string + create_public_ip = optional(bool, true) + public_ip_name = string + private_ip_address_allocation = optional(string, "Dynamic") + })) + }) + validation { # primary/secondary.name + condition = var.ip_configurations.primary.name != var.ip_configurations.secondary.name + error_message = <<-EOF + The `name` property has to be unique among all IP configurations. + EOF } - validation { - condition = alltrue(flatten([ - for _, config in var.ip_configuration : [ - contains(["Static", "Dynamic"], config.private_ip_address_allocation) - ]])) - error_message = "Possible values for `private_ip_address_allocation` are Static or Dynamic" + validation { # primary/secondary.private_ip_address_allocation + condition = contains(["Dynamic", "Static"], var.ip_configurations.primary.private_ip_address_allocation) && ( + var.ip_configurations.secondary.name != null ? ( + contains(["Dynamic", "Static"], var.ip_configurations.secondary.private_ip_address_allocation) + ) : true + ) + error_message = <<EOF + Possible values for `private_ip_address_allocation` are "Dynamic" or "Static". + EOF } } -variable "vpn_client_configuration" { - description = <<-EOF - VPN client configurations (IPSec point-to-site connections). +variable "private_ip_address_enabled" { + description = "Controls whether the private IP is enabled on the Virtual Netowkr Gateway." + default = false + type = bool +} - List of available attributes of each VPN client configurations: - - `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. - You can provide more than one address space, e.g. in CIDR notation. - - `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL - - `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. - See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values - - `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant - - `root_certificate` - (`object`, optional, defaults to `null`) one or more root_certificate blocks - which are defined below. These root certificates are used to sign the client - certificate used by the VPN clients to connect to the gateway. - - `revoked_certificate` - (`object`, optional, defaults to `null`) one or more revoked_certificate blocks - which are defined below. - - `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. - - `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. - - `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. - The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with - the use of aad_tenant, aad_audience and aad_issuer. - - `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for - the virtual network gateway. The supported values are AAD, Radius and Certificate. +variable "default_local_network_gateway_id" { + description = <<-EOF + The ID of the local network gateway. + When set, the outbound Internet traffic from the virtual network, in which the gateway is created, will be routed through local + network gateway (forced tunnelling). EOF - type = list(object({ - address_space = string - aad_tenant = optional(string) - aad_audience = optional(string) - aad_issuer = optional(string) - root_certificate = optional(object({ - name = string - public_cert_data = string - })) - revoked_certificate = optional(object({ - name = string - thumbprint = string - })) - radius_server_address = optional(string) - radius_server_secret = optional(string) - vpn_client_protocols = optional(list(string)) - vpn_auth_types = optional(list(string)) - })) + default = null + type = string } -variable "azure_bgp_peers_addresses" { +variable "azure_bgp_peer_addresses" { description = <<-EOF Map of IP addresses used on Azure side for BGP. @@ -251,285 +251,290 @@ variable "azure_bgp_peers_addresses" { default = {} nullable = false type = map(string) + validation { + condition = alltrue([ + for _, v in var.azure_bgp_peer_addresses : + cidrhost("${v}/24", 0) == cidrhost("169.254.21.0/24", 0) || cidrhost("${v}/24", 0) == cidrhost("169.254.22.0/24", 0) + ]) + error_message = <<-EOF + The value of a peer BGP address should be contained within the following address spaces: 169.254.21.0/24 or 169.254.22.0/24. + EOF + } } -variable "local_bgp_settings" { +variable "bgp" { description = <<-EOF - BGP settings. - - Attributes: - - `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. - - `peering_addresses` - (`map`, required) a map of peering addresses, which contains 1 (for active-standby) - or 2 objects (for active-active), where key is the ip configuration name and with attributes: - - `apipa_addresses` - (`list`, required) is the list of keys for IP addresses defined in variable azure_bgp_peers_addresses - - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to - the BGP peer of the Virtual Network Gateway. - - `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes - which have been learned through BGP peering. - - Example: - - ```hcl - local_bgp_settings = { - asn = "65001" - peering_addresses = { - "001" = { - apipa_addresses = ["primary_1", "primary_2"] - }, - "002" = { - apipa_addresses = ["secondary_1", "secondary_2"] - } - } - } - ``` + A map controlling the BGP configuration used by this Virtual Network Gateway. + + Following properties are available: + + - `enable` - (`bool`, optional, defaults to `false`) controls whether BGP (Border Gateway Protocol) will be enabled for + this Virtual Network Gateway + - `configuration` - (`map`, optional, defaults to `null`) contains BGP configuration, required when `enable` is set to `true`. + Contains the following properties: + - `asn` - (`string`, required) the Autonomous System Number (ASN) to use as part of the BGP. + - `peer_weigth` - (`number`, optional`, defaults to `null`) weight added to routes which have been learned + through BGP peering. Values are between `0` and `100`. + - `primary_peering_addresses` - (`map`, required) a map defining peering addresses, following properties are available: + - `name` - (`string`, required) name of the configuration. + - `apipa_address_keys` - (`list`, required) list of keys identifying addresses defined in `azure_bgp_peer_addresses`. + - `default_addresses` - (`list`, optional, defaults to `null`) is the list of peering address assigned to the BGP peer of + the Virtual Network Gateway. + - `secondary_peering_addresses` - (`map`, optional, defaults to `null`) a map defining secondary peering addresses, required + only for `active-active` deployments. Same properties are available. EOF + default = null type = object({ - asn = string - peering_addresses = map(object({ - apipa_addresses = list(string) - default_addresses = optional(list(string)) + enable = optional(bool, false) + configuration = optional(object({ + asn = string + peer_weight = optional(number) + primary_peering_addresses = object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + }) + secondary_peering_addresses = optional(object({ + name = string + apipa_address_keys = list(string) + default_addresses = optional(list(string)) + })) })) - peer_weight = optional(number) }) - validation { - condition = var.local_bgp_settings.peer_weight != null ? var.local_bgp_settings.peer_weight >= 0 && var.local_bgp_settings.peer_weight <= 100 : true - error_message = "Possible values for `peer_weight` are between 0 and 100." + validation { # configuration + condition = var.bgp == null ? true : (var.bgp.enable && var.bgp.configuration != null) || ( + !var.bgp.enable && var.bgp.configuration == null + ) + error_message = <<-EOF + The `configuration` property is required only when `enabled` is set to `true`. + EOF + } + validation { # configuration.peer_weight + condition = var.bgp == null ? true : ( + var.bgp.configuration.peer_weight == null ? true : ( + var.bgp.configuration.peer_weight >= 0 && var.bgp.configuration.peer_weight <= 100 + ) + ) + error_message = <<-EOF + Possible values for `peer_weight` are between 0 and 100. + EOF } } -variable "custom_route" { - description = <<-EOF - List of custom routes. - - Every object in the list contains attributes: - - `address_prefixes` - (`list`, optional, defaults to `null`) a list of address blocks reserved for this virtual network in CIDR notation as defined below. - - EOF - default = [] - nullable = false - type = list(object({ - address_prefixes = optional(list(string)) - })) -} - -# Local network gateways variable "local_network_gateways" { description = <<-EOF - Map of local network gateways. - - Every object in the map contains attributes: - - local_ng_name - (`string`, required) the name of the local network gateway. - - connection_name - (`string`, required) the name of the virtual network gateway connection. - - remote_bgp_settings - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: - - asn - (`string`, required) the BGP speaker's ASN. - - bgp_peering_address - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. - - peer_weight - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. - - gateway_address - (`string`, optional, defaults to `null`) the gateway IP address to connect with. - - address_space - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces - the gateway exposes. - - custom_bgp_addresses - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, - which can only be used on IPSec / active-active connections. Object contains 2 attributes: - - primary - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway - ip_configuration (first one) - - secondary - (`string`, optional, defaults to `null`) single IP address that is part of - the azurerm_virtual_network_gateway ip_configuration (second one) - - Example: - - ```hcl - local_network_gateways = { - "lg1" = { - local_ng_name = "001" - connection_name = "001" - gateway_address = "PUBLIC_IP_1" - remote_bgp_settings = [{ - asn = "65002" - bgp_peering_address = "169.254.21.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - } - "lg2" = { - local_ng_name = "002" - connection_name = "002" - gateway_address = "PUBLIC_IP_2" - remote_bgp_settings = [{ - asn = "65003" - bgp_peering_address = "169.254.21.5" - }] - custom_bgp_addresses = [ - { - primary = "primary_2" - secondary = "secondary_2" - } - ] - } - "lg3" = { - local_ng_name = "003" - connection_name = "003" - gateway_address = "PUBLIC_IP_3" - remote_bgp_settings = [{ - asn = "65002" - bgp_peering_address = "169.254.22.1" - }] - custom_bgp_addresses = [ - { - primary = "primary_1" - secondary = "secondary_1" - } - ] - } - "lg4" = { - local_ng_name = "004" - connection_name = "004" - gateway_address = "PUBLIC_IP_4" - remote_bgp_settings = [{ - asn = "65003" - bgp_peering_address = "169.254.22.5" - }] - custom_bgp_addresses = [ - { - primary = "primary_2" - secondary = "secondary_2" - } - ] - } - } - ``` + Map of local network gateways and their connections. + + Every object in the map contains following attributes: + + - `name` - (`string`, required) the name of the local network gateway. + - `remote_bgp_settings` - (`list`, optional, defaults to `[]`) block containing Local Network Gateway's BGP speaker settings: + - `asn` - (`string`, required) the BGP speaker's ASN. + - `bgp_peering_address` - (`string`, required) the BGP peering address and BGP identifier of this BGP speaker. + - `peer_weight` - (`number`, optional, defaults to `null`) the weight added to routes learned from this BGP speaker. + - `gateway_address` - (`string`, optional, defaults to `null`) the gateway IP address to connect with. + - `address_space` - (`list`, optional, defaults to `[]`) the list of string CIDRs representing the address spaces + the gateway exposes. + - `custom_bgp_addresses` - (`list`, optional, defaults to `[]`) Border Gateway Protocol custom IP Addresses, + which can only be used on IPSec / active-active connections. Object contains 2 attributes: + - `primary_key` - (`string`, required) single IP address that is part of the azurerm_virtual_network_gateway + ip_configuration (first one) + - `secondary_key` - (`string`, optional, defaults to `null`) single IP address that is part of the + azurerm_virtual_network_gateway ip_configuration (second one) + - `connection` - (`map`, required) a map defining configuration for a VPN connection between Azure VNG and on-premises + VPN device. Contains the following properties: + - `name` - (`string`, required) the name of the virtual network gateway connection. + - `ipsec_policies` - (`list`, required) list of IPsec policies used for Virtual Network Connection. A single policy consist + of the following properties: + - `dh_group` - (`string`, required) the DH group used in IKE phase 1 for initial SA. + - `ike_encryption` - (`string`, required) the IKE encryption algorithm. + - `ike_integrity` - (`string`, required) the IKE integrity algorithm. + - `ipsec_encryption` - (`string`, required) the IPSec encryption algorithm. + - `ipsec_integrity` - (`string`, required) the IPSec integrity algorithm. + - `pfs_group` - (`string`, required) the DH group used in IKE phase 2 for new child SA. + - `sa_datasize` - (`string`, optional, defaults to `102400000`) the IPSec SA payload size in KB. Must be at least + 1024 KB. + - `sa_lifetime` - (`string`, optional, defaults to `27000`) the IPSec SA lifetime in seconds. Must be at least 300 + seconds. + - `connection_type` - (`string`, optional, defaults to `IPsec`) a VPN connection type, can be one of: `IPsec`, `ExpressRoute` + or `Vnet2Vnet`. + - `connection_mode` - (`string`, optional, defaults to `Default`) connection mode to use, can be one of: `Default`, + `InitiatorOnly` or `ResponderOnly`. + - `shared_key` - (`string`, optional, defaults to `null`) a shared IPSec key used during connection creation. EOF + default = {} + nullable = false type = map(object({ - local_ng_name = string - connection_name = string - remote_bgp_settings = optional(list(object({ + name = string + remote_bgp_settings = optional(object({ asn = string bgp_peering_address = string peer_weight = optional(number) - })), []) - gateway_address = optional(string) + })) address_space = optional(list(string), []) - custom_bgp_addresses = optional(list(object({ - primary = string - secondary = optional(string) - })), []) + gateway_address = optional(string) + connection = object({ + name = string + custom_bgp_addresses = optional(object({ + primary_key = string + secondary_key = optional(string) + })) + ipsec_policies = list(object({ + dh_group = string + ike_encryption = string + ike_integrity = string + ipsec_encryption = string + ipsec_integrity = string + pfs_group = string + sa_datasize = optional(string, "102400000") + sa_lifetime = optional(string, "27000") + })) + type = optional(string, "IPsec") + mode = optional(string, "Default") + shared_key = optional(string) + }) })) -} - -# Virtual Network Gateway connection -variable "connection_type" { - description = "The type of VNG connection." - default = "IPsec" - nullable = false - type = string - validation { - condition = contains(["IPsec", "ExpressRoute", "Vnet2Vnet"], var.connection_type) - error_message = "Valid options are IPsec (Site-to-Site), ExpressRoute (ExpressRoute), and Vnet2Vnet (VNet-to-VNet)" + validation { # remote_bgp_settings & address_space + condition = alltrue([ + for _, v in var.local_network_gateways : + length(coalesce(v.remote_bgp_settings, {})) > 0 || length(v.address_space) > 0 + ]) + error_message = <<-EOF + You have to define at least one: `remote_bpg_settings` or `address_space`. + EOF } -} - -variable "connection_mode" { - description = "The connection mode to use." - default = "Default" - nullable = false - type = string - validation { - condition = contains(["Default", "InitiatorOnly", "ResponderOnly"], var.connection_mode) - error_message = "Possible values are Default, InitiatorOnly and ResponderOnly" + validation { # connection.type + condition = alltrue([ + for _, v in var.local_network_gateways : contains(["IPsec", "ExpressRoute", "Vnet2Vnet"], v.connection.type) + ]) + error_message = <<-EOF + The `connection_type` can be one of either: "IPsec", "ExpressRoute" or "Vnet2Vnet". + EOF } -} - -variable "ipsec_shared_key" { - description = "The shared IPSec key." - type = string - sensitive = true -} - -variable "ipsec_policies" { - description = <<-EOF - IPsec policies used for Virtual Network Connection. - - Single policy contains attributes: - - `dh_group` - (`string`, required) The DH group used in IKE phase 1 for initial SA. - - `ike_encryption` - (`string`, required) The IKE encryption algorithm. - - `ike_integrity` - (`string`, required) The IKE integrity algorithm. - - `ipsec_encryption` - (`string`, required) The IPSec encryption algorithm. - - `ipsec_integrity` - (`string`, required) The IPSec integrity algorithm. - - `pfs_group` - (`string`, required) The DH group used in IKE phase 2 for new child SA. - - `sa_datasize` - (`string`, optional, defaults to `102400000`) The IPSec SA payload size in KB. - Must be at least 1024 KB. - - `sa_lifetime` - (`string`, optional, defaults to `27000`) The IPSec SA lifetime in seconds. - Must be at least 300 seconds. - - Example: - - ```hcl - ipsec_policy = [ - { - dh_group = "ECP384" - ike_encryption = "AES256" - ike_integrity = "SHA256" - ipsec_encryption = "AES256" - ipsec_integrity = "SHA256" - pfs_group = "ECP384" - sa_datasize = "102400000" - sa_lifetime = "27000" - } - ] - ``` - EOF - type = list(object({ - dh_group = string - ike_encryption = string - ike_integrity = string - ipsec_encryption = string - ipsec_integrity = string - pfs_group = string - sa_datasize = optional(string, "102400000") - sa_lifetime = optional(string, "27000") - })) - validation { + validation { # connection.mode + condition = alltrue([ + for _, v in var.local_network_gateways : contains(["Default", "InitiatorOnly", "ResponderOnly"], v.connection.mode) + ]) + error_message = <<-EOF + The `connection_mode` property can be one of either: "Default", "InitiatorOnly" or "ResponderOnly". + EOF + } + validation { # connection.ipsec_policies.dh_group condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ - contains(["DHGroup1", "DHGroup14", "DHGroup2", "DHGroup2048", "DHGroup24", "ECP256", "ECP384", "None"], ipsec_policy.dh_group) - ]])) - error_message = "Possible values for `dh_group` are DHGroup1, DHGroup14, DHGroup2, DHGroup2048, DHGroup24, ECP256, ECP384 or None" + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : + contains( + ["DHGroup1", "DHGroup14", "DHGroup2", "DHGroup2048", "DHGroup24", "ECP256", "ECP384", "None"], + ipsec_policy.dh_group + ) + ] + ])) + error_message = <<-EOF + Possible values for `dh_group` are "DHGroup1", "DHGroup14", "DHGroup2", "DHGroup2048", "DHGroup24", "ECP256", "ECP384" or + "None". + EOF } - validation { + validation { # connection.ipsec_policies.ike_encryption condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : contains(["AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES256"], ipsec_policy.ike_encryption) ]])) - error_message = "Possible values for `ike_encryption` are AES128, AES192, AES256, DES, DES3, GCMAES128, or GCMAES256" + error_message = <<-EOF + Possible values for `ike_encryption` are "AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128" or "GCMAES256". + EOF } - validation { + validation { # connection.ipsec_policies.ike_integrity condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : contains(["GCMAES128", "GCMAES256", "MD5", "SHA1", "SHA256", "SHA384"], ipsec_policy.ike_integrity) ]])) - error_message = "Possible values for `ike_integrity` are GCMAES128, GCMAES256, MD5, SHA1, SHA256, or SHA384" + error_message = <<-EOF + Possible values for `ike_integrity` are "GCMAES128", "GCMAES256", "MD5", "SHA1", "SHA256", or "SHA384". + EOF } - validation { + validation { # connection.ipsec_policies.ipsec_encryption condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ - contains(["AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES192", "GCMAES256", "None"], ipsec_policy.ipsec_encryption) - ]])) - error_message = "Possible values for `ipsec_encryption` are AES128, AES192, AES256, DES, DES3, GCMAES128, GCMAES192, GCMAES256, or None" + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : + contains( + ["AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES192", "GCMAES256", "None"], + ipsec_policy.ipsec_encryption + ) + ] + ])) + error_message = <<-EOF + Possible values for `ipsec_encryption` are "AES128", "AES192", "AES256", "DES", "DES3", "GCMAES128", "GCMAES192", "GCMAES256" + or "None". + EOF } - validation { + validation { # connection.ipsec_policies.ipsec_integrity condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : contains(["GCMAES128", "GCMAES192", "GCMAES256", "MD5", "SHA1", "SHA256"], ipsec_policy.ipsec_integrity) ]])) - error_message = "Possible values for `ipsec_integrity` are GCMAES128, GCMAES192, GCMAES256, MD5, SHA1, or SHA256" + error_message = <<-EOF + Possible values for `ipsec_integrity` are "GCMAES128", "GCMAES192", "GCMAES256", "MD5", "SHA1" or "SHA256". + EOF } - validation { + validation { # connection.ipsec_policies.pfs_group condition = alltrue(flatten([ - for _, ipsec_policy in var.ipsec_policies : [ + for _, v in var.local_network_gateways : [ + for _, ipsec_policy in v.connection.ipsec_policies : contains(["ECP256", "ECP384", "PFS1", "PFS14", "PFS2", "PFS2048", "PFS24", "PFSMM", "None"], ipsec_policy.pfs_group) ]])) - error_message = "Possible values for `pfs_group` are ECP256, ECP384, PFS1, PFS14, PFS2, PFS2048, PFS24, PFSMM, or None" + error_message = <<EOF + Possible values for `pfs_group` are "ECP256", "ECP384", "PFS1", "PFS14", "PFS2", "PFS2048", "PFS24", "PFSMM" or "None". + EOF } } + +variable "vpn_clients" { + description = <<-EOF + VPN client configurations (IPSec point-to-site connections). + + This is a map, where each value is a VPN client configuration. Keys are just names describing a particular configuration. They + are not being used in the actual deployment. + + Following properties are available: + + - `address_space` - (`string`, required) the address space out of which IP addresses for vpn clients will be taken. + You can provide more than one address space, e.g. in CIDR notation. + - `aad_tenant` - (`string`, optional, defaults to `null`) AzureAD Tenant URL + - `aad_audience` - (`string`, optional, defaults to `null`) the client id of the Azure VPN application. + See Create an Active Directory (AD) tenant for P2S OpenVPN protocol connections for values + - `aad_issuer` - (`string`, optional, defaults to `null`) the STS url for your tenant + - `root_certificates` - (`map`, optional, defaults to `{}`) a map defining root certificates used to sign client + certificates used by VPN clients. The key is a name of the certificate, value is the public + certificate in PEM format. + - `revoked_certificates` - (`map`, optional, defaults to `null`) a map defining revoked certificates. The key is a name of + the certificate, value is the thumbprint of the certificate. + - `radius_server_address` - (`string`, optional, defaults to `null`) the address of the Radius server. + - `radius_server_secret` - (`string`, optional, defaults to `null`) the secret used by the Radius server. + - `vpn_client_protocols` - (`list(string)`, optional, defaults to `null`) list of the protocols supported by the vpn client. + The supported values are SSTP, IkeV2 and OpenVPN. Values SSTP and IkeV2 are incompatible with + the use of aad_tenant, aad_audience and aad_issuer. + - `vpn_auth_types` - (`list(string)`, optional, defaults to `null`) list of the vpn authentication types for + the virtual network gateway. The supported values are AAD, Radius and Certificate. + - `custom_routes` - (`map`, optional, defaults to `{}`) a map defining custom routes. Each route is a list of address + blocks reserved for this Virtual Network (in CIDR notation). Keys in this map are only to identify + the CIDR blocks, values are lists of the actual address blocks. + EOF + default = {} + nullable = false + type = map(object({ + address_space = string + aad_tenant = optional(string) + aad_audience = optional(string) + aad_issuer = optional(string) + root_certificates = optional(map(string), {}) + revoked_certificates = optional(map(string), {}) + radius_server_address = optional(string) + radius_server_secret = optional(string) + vpn_client_protocols = optional(list(string)) + vpn_auth_types = optional(list(string)) + custom_routes = optional(map(list(string)), {}) + })) +} diff --git a/modules/virtual_network_gateway/versions.tf b/modules/virtual_network_gateway/versions.tf index 8dc5c1eb..9abec711 100644 --- a/modules/virtual_network_gateway/versions.tf +++ b/modules/virtual_network_gateway/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.3, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 31970013..5d21468b 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -135,7 +135,6 @@ The `password` property is required when `ssh_keys` is not specified. If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - Type: ```hcl @@ -156,24 +155,23 @@ Basic Azure VM image configuration. Following properties are available: -- `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with - `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` +- `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`. - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for a image - which should be deployed + which should be deployed. - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a - published image + published image. - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU; list available with - `az vm image list -o table --all --publisher paloaltonetworks` + `az vm image list -o table --all --publisher paloaltonetworks`. - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan - on Azure Market Place + on Azure Market Place. - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used - for creating new Virtual Machines + for creating new Virtual Machines. **Important!** \ `custom_id` and `version` properties are mutually exclusive. - Type: ```hcl @@ -200,12 +198,12 @@ Nevertheless they should be at least reviewed to meet deployment requirements. List of either required or important properties: - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported + Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) -- `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk + `vm_size` values). +- `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. - `bootstrap_options` - bootstrap options to pass to VM-Series instance. Proper syntax is a string of semicolon separated properties, for example: @@ -218,42 +216,43 @@ List of either required or important properties: List of other, optional properties: -- `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use +- `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use. - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the - management interface (always disabled) + management interface (always disabled). - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be - used to encrypt this VM's disk + used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted - by enabling Encryption at Host -- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files + by enabling Encryption at Host. +- `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. +- `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) Storage Account's Blob endpoint to hold + diagnostic files, when skipped a managed Storage Account will be used (preferred). - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be - assigned to this VM. Required only if `identity_type` is not "SystemAssigned" -- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM - + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. Type: ```hcl object({ - size = optional(string, "Standard_D3_v2") - bootstrap_options = optional(string) - zone = string - disk_type = optional(string, "StandardSSD_LRS") - disk_name = string - avset_id = optional(string) - accelerated_networking = optional(bool, true) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) }) ``` @@ -274,13 +273,13 @@ Interfaces will be attached to VM in the order you define here, therefore: Following configuration options are available: -- `name` - (`string`, required) the interface name -- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in +- `name` - (`string`, required) the interface name. +- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. - `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not to change as long as the VM is running. Any stop/deallocate/restart operation might cause the IP to change. -- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface +- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface. - `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the interface. When `create_public_ip` is set to `true` this will become a name of a newly created Public IP interface. Otherwise this is a name of an existing interfaces that will @@ -317,7 +316,6 @@ Example: ``` - Type: ```hcl @@ -358,5 +356,4 @@ Default value: `map[]` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/main.tf b/modules/vmseries/main.tf index ec13f2b2..f94640bf 100644 --- a/modules/vmseries/main.tf +++ b/modules/vmseries/main.tf @@ -105,8 +105,11 @@ resource "azurerm_linux_virtual_machine" "this" { custom_data = var.virtual_machine.bootstrap_options == null ? null : base64encode(var.virtual_machine.bootstrap_options) - boot_diagnostics { - storage_account_uri = var.virtual_machine.diagnostics_storage_uri + dynamic "boot_diagnostics" { + for_each = var.virtual_machine.enable_boot_diagnostics ? [1] : [] + content { + storage_account_uri = var.virtual_machine.boot_diagnostics_storage_uri + } } identity { diff --git a/modules/vmseries/variables.tf b/modules/vmseries/variables.tf index 3cd0cb5f..c18aace5 100644 --- a/modules/vmseries/variables.tf +++ b/modules/vmseries/variables.tf @@ -37,7 +37,6 @@ variable "authentication" { **Important!** \ `ssh_keys` property is a list of strings, so each item should be the actual public key value. If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - EOF type = object({ username = optional(string, "panadmin") @@ -53,22 +52,21 @@ variable "image" { Following properties are available: - - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with - `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` + - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`. - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for a image - which should be deployed + which should be deployed. - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a - published image + published image. - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU; list available with - `az vm image list -o table --all --publisher paloaltonetworks` + `az vm image list -o table --all --publisher paloaltonetworks`. - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan - on Azure Market Place + on Azure Market Place. - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used - for creating new Virtual Machines + for creating new Virtual Machines. **Important!** \ `custom_id` and `version` properties are mutually exclusive. - EOF type = object({ version = optional(string) @@ -78,12 +76,14 @@ variable "image" { enable_marketplace_plan = optional(bool, true) custom_id = optional(string) }) - validation { + validation { # version & custom_id condition = (var.image.custom_id != null && var.image.version == null ) || ( var.image.custom_id == null && var.image.version != null ) - error_message = "Either `custom_id` or `version` has to be defined." + error_message = <<-EOF + Either `custom_id` or `version` has to be defined. + EOF } } @@ -97,12 +97,12 @@ variable "virtual_machine" { List of either required or important properties: - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported + Deployment Guide* as only a few selected sizes are supported. - `zone` - (`string`, required) Availability Zone to place the VM in, `null` value means a non-zonal deployment. - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) - - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk + `vm_size` values). + - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. - `bootstrap_options` - bootstrap options to pass to VM-Series instance. Proper syntax is a string of semicolon separated properties, for example: @@ -115,50 +115,61 @@ variable "virtual_machine" { List of other, optional properties: - - `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use + - `avset_key` - (`string`, optional, default to `null`) identifier of the Availability Set to use. - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the - management interface (always disabled) + management interface (always disabled). - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be - used to encrypt this VM's disk + used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted - by enabling Encryption at Host - - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files + by enabling Encryption at Host. + - `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. + - `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) Storage Account's Blob endpoint to hold + diagnostic files, when skipped a managed Storage Account will be used (preferred). - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be - assigned to this VM. Required only if `identity_type` is not "SystemAssigned" - - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM - + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. EOF type = object({ - size = optional(string, "Standard_D3_v2") - bootstrap_options = optional(string) - zone = string - disk_type = optional(string, "StandardSSD_LRS") - disk_name = string - avset_id = optional(string) - accelerated_networking = optional(bool, true) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) }) - validation { + validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) - error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + error_message = <<-EOF + The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. + EOF } - validation { - condition = contains(["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type) - error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + validation { # identity_type + condition = contains( + ["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine.identity_type + ) + error_message = <<-EOF + The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\". + EOF } - validation { - condition = var.virtual_machine.identity_type == "SystemAssigned" ? length(var.virtual_machine.identity_ids) == 0 : length(var.virtual_machine.identity_ids) > 0 - error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + validation { # identity_type & identity_ids + condition = var.virtual_machine.identity_type == "SystemAssigned" ? ( + length(var.virtual_machine.identity_ids) == 0 + ) : length(var.virtual_machine.identity_ids) > 0 + error_message = <<-EOF + The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\". + EOF } } @@ -176,13 +187,13 @@ variable "interfaces" { Following configuration options are available: - - `name` - (`string`, required) the interface name - - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in + - `name` - (`string`, required) the interface name. + - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. - `private_ip_address` - (`string`, optional, defaults to `null`) static private IP to assign to the interface. When skipped Azure will assign one dynamically. Keep in mind that a dynamic IP is guarantied not to change as long as the VM is running. Any stop/deallocate/restart operation might cause the IP to change. - - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface + - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, creates a public IP for the interface. - `public_ip_name` - (`string`, optional, defaults to `null`) name of the public IP to associate with the interface. When `create_public_ip` is set to `true` this will become a name of a newly created Public IP interface. Otherwise this is a name of an existing interfaces that will @@ -217,7 +228,6 @@ variable "interfaces" { }, ] ``` - EOF type = list(object({ name = string @@ -229,16 +239,20 @@ variable "interfaces" { lb_backend_pool_id = optional(string) attach_to_lb_backend_pool = optional(bool, false) })) - validation { + validation { # create_public_ip & public_ip_name condition = alltrue([ for v in var.interfaces : v.public_ip_name != null if v.create_public_ip ]) - error_message = "The `public_ip_name` property is required when `create_public_ip` is set to `true`." + error_message = <<-EOF + The `public_ip_name` property is required when `create_public_ip` is set to `true`. + EOF } - validation { + validation { # lb_backend_pool_id & attach_to_lb_backend_pool condition = alltrue([ for v in var.interfaces : v.lb_backend_pool_id != null if v.attach_to_lb_backend_pool ]) - error_message = "The `lb_backend_pool_id` cannot be `null` when `attach_to_lb_backend_pool` is set to `true`." + error_message = <<-EOF + The `lb_backend_pool_id` cannot be `null` when `attach_to_lb_backend_pool` is set to `true`. + EOF } } diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 24103870..1a5ed992 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -191,10 +191,11 @@ A map defining authentication settings (including username and password). Following properties are available: -- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series username -- `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password -- `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication -- `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys +- `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series + username. +- `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password. +- `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. +- `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. **Important!** \ The `password` property is required when `ssh_keys` is not specified. You can have both, password and key authentication. @@ -204,7 +205,6 @@ The `password` property is required when `ssh_keys` is not specified. You can ha If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - Type: ```hcl @@ -226,17 +226,17 @@ Basic Azure VM configuration. Following properties are available: - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with - `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`. - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image - which should be deployed + which should be deployed. - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a - published image + published image. - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU, list available with - `az vm image list -o table --all --publisher paloaltonetworks` + `az vm image list -o table --all --publisher paloaltonetworks`. - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan - on Azure Market Place + on Azure Market Place. - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used - for creating new Virtual Machines + for creating new Virtual Machines. **Important!** \ `custom_id` and `version` properties are mutually exclusive. @@ -273,15 +273,15 @@ Interfaces will be attached to VM in the order you define here, therefore: Following configuration options are available: -- `name` - (`string`, required) the interface name -- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in -- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface -- `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend - pools to associate the interface with -- `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend - pools to associate the interface with -- `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name - Label for each Virtual Machine Instance. +- `name` - (`string`, required) the interface name. +- `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. +- `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface. +- `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend pools + to associate the interface with. +- `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend pools + to associate the interface with. +- `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name + Label for each Virtual Machine Instance. Example: @@ -352,14 +352,14 @@ Nevertheless they should be at least reviewed to meet deployment requirements. List of either required or important properties: -- `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. -- `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from - this Scale Set will be created -- `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `size` values) -- `bootstrap_options` - bootstrap options to pass to VM-Series instance. +- `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. +- `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from this Scale Set + will be created. +- `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` + values). +- `bootstrap_options` - (`string`, optional) bootstrap options to pass to VM-Series instance. Proper syntax is a string of semicolon separated properties, for example: @@ -371,27 +371,28 @@ List of either required or important properties: List of other, optional properties: -- `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated - networking (SR-IOV) for all dataplane network interfaces, this does not affect the - management interface (always disabled) -- `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be - used to encrypt this VM's disk -- `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling - Encryption at Host -- `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) -- `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that - are used by this Virtual Machine Scale Set -- `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be - limited to a Single Placement Group, which means the number of instances will be capped - at 100 Virtual Machines -- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files -- `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that - should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or - "SystemAssigned, UserAssigned". -- `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be - assigned to this VM. Required only if `identity_type` is not "SystemAssigned" -- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM +- `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled). +- `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk. +- `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling + Encryption at Host. +- `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). +- `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that + are used by this Virtual Machine Scale Set. +- `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be + limited to a Single Placement Group, which means the number of instances will be capped + at 100 Virtual Machines. +- `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. +- `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) Storage Account's Blob endpoint to hold + diagnostic files, when skipped a managed Storage Account will be used (preferred). +- `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". +- `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. @@ -399,20 +400,21 @@ Type: ```hcl object({ - size = optional(string, "Standard_D3_v2") - bootstrap_options = optional(string) - zones = optional(list(string)) - disk_type = optional(string, "StandardSSD_LRS") - accelerated_networking = optional(bool, true) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool, true) - platform_fault_domain_count = optional(number) - single_placement_group = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string, "StandardSSD_LRS") + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool, true) + platform_fault_domain_count = optional(number) + single_placement_group = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) }) ``` @@ -430,18 +432,18 @@ Following properties are available: - `application_insights_id` - (`string`, optional, defaults to `null`) an ID of Application Insights instance that should be used to provide metrics for autoscaling; to **avoid false positives** this should be an - instance **dedicated to this Scale Set** + instance **dedicated to this Scale Set**. - `default_count` - (`number`, optional, defaults to `2`) minimum number of instances that should be present in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable - to compare the metrics to the thresholds + to compare the metrics to the thresholds. - `scale_in_policy` - (`string`, optional, defaults to Azure default) controls which VMs are chosen for removal during a scale-in, can be one of: `Default`, `NewestVM`, `OldestVM`. - `scale_in_force_deletion` - (`bool`, optional, defaults to `false`) when `true` will **force delete** machines during a - scale-in operation + scale-in operation. - `notification_emails` - (`list`, optional, defaults to `[]`) list of email addresses to notify about autoscaling - events + events. - `webhooks_uris` - (`map`, optional, defaults to `{}`) the URIs that receive autoscaling events; a map where keys - are just arbitrary identifiers and the values are the webhook URIs + are just arbitrary identifiers and the values are the webhook URIs. Type: @@ -471,25 +473,25 @@ The order does matter. The 1<sup>st</sup> profile becomes the default one. There are some considerations when creating autoscaling configuration: -1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule -2. all other profiles should contain schedules +1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule. +2. all other profiles should contain schedules. 3. the scaling rules are optional, if you skip them you will create a profile with a set number of VM instances (in such case the `minimum_count` and `maximum_count` properties are skipped). Following properties are available: -- `name` - (`string`, required) the name of the profile -- `default_count` - (`number`, required) the default number of VMs -- `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in -- `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out -- `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply - - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can - be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string) - - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, - possible values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". - - `start_time` - (`string`, required) profile start time in RFC3339 format - - `end_time` - (`string`, required) profile end time in RFC3339 format -- `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. +- `name` - (`string`, required) the name of the profile. +- `default_count` - (`number`, required) the default number of VMs. +- `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in. +- `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out. +- `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply: + - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can + be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string). + - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, possible + values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". + - `start_time` - (`string`, required) profile start time in RFC3339 format. + - `end_time` - (`string`, required) profile end time in RFC3339 format. +- `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. **Note!** \ By default all VMSS built-in metrics are available. These do not differentiate between management and data planes. @@ -497,31 +499,31 @@ Following properties are available: Each metric definition is a map with 3 properties: - - `name` - (`string`, required) name of the rule - - `scale_out_config` - (`map`, required) definition of the rule used to scale-out - - `scale_in_config` - (`map`, required) definition of the rule used to scale-in + - `name` - (`string`, required) name of the rule. + - `scale_out_config` - (`map`, required) definition of the rule used to scale-out. + - `scale_in_config` - (`map`, required) definition of the rule used to scale-in. - Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for scale-out - but optional for scale-in, when skipped in the latter configuration, default to scale-out values: + Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for + scale-out but optional for scale-in, when skipped in the latter configuration, default to scale-out values: - - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action - - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) - the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, - `==` or `!=`. - - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that - the rule monitors, between 1 minute and 12 hours (specified in minutes) - - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from - `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum` - - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze - metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than - `grain_window_minutes` - - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from - `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` - or `Total` - - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute - and 1 week (specified in minutes) - - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count - of instances in a Scale Set will be changed during a scale action + - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action. + - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) + the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, + `==` or `!=`. + - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that + the rule monitors, between 1 minute and 12 hours (specified in minutes). + - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from + `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum`. + - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze + metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than + `grain_window_minutes`. + - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from + `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` + or `Total`. + - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute + and 1 week (specified in minutes). + - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count + of instances in a Scale Set will be changed during a scale action. Example: @@ -646,5 +648,4 @@ Default value: `[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/main.tf b/modules/vmss/main.tf index c3a054eb..90bbc696 100644 --- a/modules/vmss/main.tf +++ b/modules/vmss/main.tf @@ -96,7 +96,12 @@ resource "azurerm_linux_virtual_machine_scale_set" "this" { } } - boot_diagnostics { storage_account_uri = var.virtual_machine_scale_set.diagnostics_storage_uri } + dynamic "boot_diagnostics" { + for_each = var.virtual_machine_scale_set.enable_boot_diagnostics ? [1] : [] + content { + storage_account_uri = var.virtual_machine_scale_set.boot_diagnostics_storage_uri + } + } identity { type = var.virtual_machine_scale_set.identity_type diff --git a/modules/vmss/variables.tf b/modules/vmss/variables.tf index 8d830822..817578ac 100644 --- a/modules/vmss/variables.tf +++ b/modules/vmss/variables.tf @@ -25,10 +25,11 @@ variable "authentication" { Following properties are available: - - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series username - - `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password - - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication - - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys + - `username` - (`string`, optional, defaults to `panadmin`) the initial administrative VM-Series + username. + - `password` - (`string`, optional, defaults to `null`) the initial administrative VM-Series password. + - `disable_password_authentication` - (`bool`, optional, defaults to `true`) disables password-based authentication. + - `ssh_keys` - (`list`, optional, defaults to `[]`) a list of initial administrative SSH public keys. **Important!** \ The `password` property is required when `ssh_keys` is not specified. You can have both, password and key authentication. @@ -36,7 +37,6 @@ variable "authentication" { **Important!** \ `ssh_keys` property is a list of strings, so each item should be the actual public key value. If you would like to load them from files use the `file` function, for example: `[ file("/path/to/public/keys/key_1.pub") ]`. - EOF type = object({ username = optional(string, "panadmin") @@ -44,17 +44,22 @@ variable "authentication" { disable_password_authentication = optional(bool, true) ssh_keys = optional(list(string), []) }) - validation { + validation { # password, disable_password_authentication & ssh_keys condition = !( - var.authentication.password == null && !var.authentication.disable_password_authentication && length(var.authentication.ssh_keys) == 0 + var.authentication.password == null && + !var.authentication.disable_password_authentication && length(var.authentication.ssh_keys) == 0 ) - error_message = "At least `password` or `ssh_keys` property has to be set when `disable_password_authentication` is `false`." + error_message = <<-EOF + At least `password` or `ssh_keys` property has to be set when `disable_password_authentication` is `false`. + EOF } - validation { + validation { # disable_password_authentication & ssh_keys condition = !( var.authentication.disable_password_authentication && length(var.authentication.ssh_keys) == 0 ) - error_message = "The `ssh_keys` property has to be set when `disable_password_authentication` is `true`." + error_message = <<-EOF + The `ssh_keys` property has to be set when `disable_password_authentication` is `true`. + EOF } } @@ -65,17 +70,17 @@ variable "image" { Following properties are available: - `version` - (`string`, optional, defaults to `null`) VM-Series PAN-OS version; list available with - `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all` + `az vm image list -o table --publisher paloaltonetworks --offer vmseries-flex --all`. - `publisher` - (`string`, optional, defaults to `paloaltonetworks`) the Azure Publisher identifier for an image - which should be deployed + which should be deployed. - `offer` - (`string`, optional, defaults to `vmseries-flex`) the Azure Offer identifier corresponding to a - published image + published image. - `sku` - (`string`, optional, defaults to `byol`) VM-Series SKU, list available with - `az vm image list -o table --all --publisher paloaltonetworks` + `az vm image list -o table --all --publisher paloaltonetworks`. - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan - on Azure Market Place + on Azure Market Place. - `custom_id` - (`string`, optional, defaults to `null`) absolute ID of your own custom PAN-OS image to be used - for creating new Virtual Machines + for creating new Virtual Machines. **Important!** \ `custom_id` and `version` properties are mutually exclusive. @@ -88,12 +93,14 @@ variable "image" { enable_marketplace_plan = optional(bool, true) custom_id = optional(string) }) - validation { + validation { # version & custom_id condition = (var.image.custom_id != null && var.image.version == null ) || ( var.image.custom_id == null && var.image.version != null ) - error_message = "Either `custom_id` or `version` has to be defined." + error_message = <<-EOF + Either `custom_id` or `version` has to be defined. + EOF } } @@ -106,14 +113,14 @@ variable "virtual_machine_scale_set" { List of either required or important properties: - - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. - - `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from - this Scale Set will be created - - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `size` values) - - `bootstrap_options` - bootstrap options to pass to VM-Series instance. + - `size` - (`string`, optional, defaults to `Standard_D3_v2`) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only few selected sizes are supported. The default one is a VM-300 equivalent. + - `zones` - (`list`, optional, defaults to `null`) a list of Availability Zones in which VMs from this Scale Set + will be created. + - `disk_type` - (`string`, optional, defaults to `StandardSSD_LRS`) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` + values). + - `bootstrap_options` - (`string`, optional) bootstrap options to pass to VM-Series instance. Proper syntax is a string of semicolon separated properties, for example: @@ -125,65 +132,77 @@ variable "virtual_machine_scale_set" { List of other, optional properties: - - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated - networking (SR-IOV) for all dataplane network interfaces, this does not affect the - management interface (always disabled) - - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be - used to encrypt this VM's disk - - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling - Encryption at Host - - `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set) - - `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that - are used by this Virtual Machine Scale Set - - `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be - limited to a Single Placement Group, which means the number of instances will be capped - at 100 Virtual Machines - - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files - - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that - should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or - "SystemAssigned, UserAssigned". - - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be - assigned to this VM. Required only if `identity_type` is not "SystemAssigned" - - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM + - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated + networking (SR-IOV) for all dataplane network interfaces, this does not affect the + management interface (always disabled). + - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be + used to encrypt this VM's disk. + - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling + Encryption at Host. + - `overprovision` - (`bool`, optional, defaults to `true`) See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine_scale_set). + - `platform_fault_domain_count` - (`number`, optional, defaults to Azure defaults) specifies the number of fault domains that + are used by this Virtual Machine Scale Set. + - `single_placement_group` - (`bool`, defaults to Azure defaults) when `true` this Virtual Machine Scale Set will be + limited to a Single Placement Group, which means the number of instances will be capped + at 100 Virtual Machines. + - `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. + - `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) Storage Account's Blob endpoint to hold + diagnostic files, when skipped a managed Storage Account will be used (preferred). + - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that + should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or + "SystemAssigned, UserAssigned". + - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be + assigned to this VM. Required only if `identity_type` is not "SystemAssigned". + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. EOF default = {} nullable = false type = object({ - size = optional(string, "Standard_D3_v2") - bootstrap_options = optional(string) - zones = optional(list(string)) - disk_type = optional(string, "StandardSSD_LRS") - accelerated_networking = optional(bool, true) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool, true) - platform_fault_domain_count = optional(number) - single_placement_group = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) + size = optional(string, "Standard_D3_v2") + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string, "StandardSSD_LRS") + accelerated_networking = optional(bool, true) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool, true) + platform_fault_domain_count = optional(number) + single_placement_group = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool, false) }) - validation { + validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine_scale_set.disk_type) - error_message = "The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`." + error_message = <<-EOF + The `disk_type` property can be one of: `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. + EOF } - # validation { - # condition = length(var.virtual_machine_scale_set.zones) == 3 || var.virtual_machine_scale_set.zones == null - # error_message = "The `var.virtual_machine_scale_set.zones` can either be a list of all Availability Zones or explicit `null`." - # } - validation { + /* validation { # zones + condition = length(var.virtual_machine_scale_set.zones) == 3 || var.virtual_machine_scale_set.zones == null + error_message = <<-EOF + The `var.virtual_machine_scale_set.zones` can either be a list of all Availability Zones or explicit `null`. + EOF + } */ + validation { # identity_type condition = contains( ["SystemAssigned", "UserAssigned", "SystemAssigned, UserAssigned"], var.virtual_machine_scale_set.identity_type ) - error_message = "The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\"." + error_message = <<-EOF + The `identity_type` property can be one of \"SystemAssigned\", \"UserAssigned\" or \"SystemAssigned, UserAssigned\". + EOF } - validation { - condition = var.virtual_machine_scale_set.identity_type == "SystemAssigned" ? length(var.virtual_machine_scale_set.identity_ids) == 0 : length(var.virtual_machine_scale_set.identity_ids) >= 0 - error_message = "The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\"." + validation { # identity_type & identity_ids + condition = var.virtual_machine_scale_set.identity_type == "SystemAssigned" ? ( + length(var.virtual_machine_scale_set.identity_ids) == 0 + ) : length(var.virtual_machine_scale_set.identity_ids) >= 0 + error_message = <<-EOF + The `identity_ids` property is required when `identity_type` is not \"SystemAssigned\". + EOF } } @@ -201,15 +220,15 @@ variable "interfaces" { Following configuration options are available: - - `name` - (`string`, required) the interface name - - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in - - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface - - `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend - pools to associate the interface with - - `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend - pools to associate the interface with - - `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name - Label for each Virtual Machine Instance. + - `name` - (`string`, required) the interface name. + - `subnet_id` - (`string`, required) ID of an existing subnet to create the interface in. + - `create_public_ip` - (`bool`, optional, defaults to `false`) if `true`, create a public IP for the interface. + - `lb_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifiers of existing Load Balancer backend pools + to associate the interface with. + - `appgw_backend_pool_ids` - (`list`, optional, defaults to `[]`) a list of identifier of Application Gateway's backend pools + to associate the interface with. + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) the IP Prefix which should be used for the Domain Name + Label for each Virtual Machine Instance. Example: @@ -240,9 +259,11 @@ variable "interfaces" { appgw_backend_pool_ids = optional(list(string), []) pip_domain_name_label = optional(string) })) - validation { + validation { # lb_backend_pool_ids & appgw_backend_pool_ids condition = length(var.interfaces[0].lb_backend_pool_ids) == 0 && length(var.interfaces[0].appgw_backend_pool_ids) == 0 - error_message = "The `lb_backend_pool_ids` and `appgw_backend_pool_ids` properties are not acceptable for the 1st (management) interface." + error_message = <<-EOF + The `lb_backend_pool_ids` and `appgw_backend_pool_ids` properties are not acceptable for the 1st (management) interface. + EOF } } @@ -254,18 +275,18 @@ variable "autoscaling_configuration" { - `application_insights_id` - (`string`, optional, defaults to `null`) an ID of Application Insights instance that should be used to provide metrics for autoscaling; to **avoid false positives** this should be an - instance **dedicated to this Scale Set** + instance **dedicated to this Scale Set**. - `default_count` - (`number`, optional, defaults to `2`) minimum number of instances that should be present in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable - to compare the metrics to the thresholds + to compare the metrics to the thresholds. - `scale_in_policy` - (`string`, optional, defaults to Azure default) controls which VMs are chosen for removal during a scale-in, can be one of: `Default`, `NewestVM`, `OldestVM`. - `scale_in_force_deletion` - (`bool`, optional, defaults to `false`) when `true` will **force delete** machines during a - scale-in operation + scale-in operation. - `notification_emails` - (`list`, optional, defaults to `[]`) list of email addresses to notify about autoscaling - events + events. - `webhooks_uris` - (`map`, optional, defaults to `{}`) the URIs that receive autoscaling events; a map where keys - are just arbitrary identifiers and the values are the webhook URIs + are just arbitrary identifiers and the values are the webhook URIs. EOF default = {} nullable = false @@ -277,9 +298,13 @@ variable "autoscaling_configuration" { notification_emails = optional(list(string), []) webhooks_uris = optional(map(string), {}) }) - validation { - condition = var.autoscaling_configuration.scale_in_policy != null ? contains(["Default", "NewestVM", "OldestVM"], var.autoscaling_configuration.scale_in_policy) : true - error_message = "The `scale_in_policy` property can be one of: `Default`, `NewestVM`, `OldestVM`." + validation { # scale_in_policy + condition = var.autoscaling_configuration.scale_in_policy != null ? contains( + ["Default", "NewestVM", "OldestVM"], var.autoscaling_configuration.scale_in_policy + ) : true + error_message = <<-EOF + The `scale_in_policy` property can be one of: `Default`, `NewestVM`, `OldestVM`. + EOF } } @@ -292,25 +317,25 @@ variable "autoscaling_profiles" { There are some considerations when creating autoscaling configuration: - 1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule - 2. all other profiles should contain schedules + 1. the 1<sup>st</sup> profile created will become the default one, it cannot contain any schedule. + 2. all other profiles should contain schedules. 3. the scaling rules are optional, if you skip them you will create a profile with a set number of VM instances (in such case the `minimum_count` and `maximum_count` properties are skipped). Following properties are available: - - `name` - (`string`, required) the name of the profile - - `default_count` - (`number`, required) the default number of VMs - - `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in - - `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out - - `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply - - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can - be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string) - - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, - possible values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". - - `start_time` - (`string`, required) profile start time in RFC3339 format - - `end_time` - (`string`, required) profile end time in RFC3339 format - - `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. + - `name` - (`string`, required) the name of the profile. + - `default_count` - (`number`, required) the default number of VMs. + - `minimum_count` - (`number`, optional, defaults to `default_count`) minimum number of VMs when scaling in. + - `maximum_count` - (`number`, optional, defaults to `default_count`) maximum number of VMs when you scale out. + - `recurrence` - (`map`, required for rules beside the 1st one) a map defining time schedule for the profile to apply: + - `timezone` - (`string`, optional, defaults to Azure default (UTC)) timezone for the time schedule, supported list can + be found [here](https://learn.microsoft.com/en-us/rest/api/monitor/autoscale-settings/create-or-update?view=rest-monitor-2022-10-01&tabs=HTTP#:~:text=takes%20effect%20at.-,timeZone,-string). + - `days` - (`list`, required) list of days of the week during which the profile is applicable, case sensitive, possible + values are "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" and "Sunday". + - `start_time` - (`string`, required) profile start time in RFC3339 format. + - `end_time` - (`string`, required) profile end time in RFC3339 format. + - `scale_rules` - (`list`, optional, defaults to `[]`) a list of maps defining metrics and rules for autoscaling. **Note!** \ By default all VMSS built-in metrics are available. These do not differentiate between management and data planes. @@ -318,31 +343,31 @@ variable "autoscaling_profiles" { Each metric definition is a map with 3 properties: - - `name` - (`string`, required) name of the rule - - `scale_out_config` - (`map`, required) definition of the rule used to scale-out - - `scale_in_config` - (`map`, required) definition of the rule used to scale-in + - `name` - (`string`, required) name of the rule. + - `scale_out_config` - (`map`, required) definition of the rule used to scale-out. + - `scale_in_config` - (`map`, required) definition of the rule used to scale-in. - Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for scale-out - but optional for scale-in, when skipped in the latter configuration, default to scale-out values: + Both `scale_out_config` and `scale_in_config` maps contain the same properties. The ones that are required for + scale-out but optional for scale-in, when skipped in the latter configuration, default to scale-out values: - - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action - - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) - the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, - `==` or `!=`. - - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that - the rule monitors, between 1 minute and 12 hours (specified in minutes) - - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from - `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum` - - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze - metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than - `grain_window_minutes` - - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from - `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` - or `Total` - - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute - and 1 week (specified in minutes) - - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count - of instances in a Scale Set will be changed during a scale action + - `threshold` - (`number`, required) the threshold of a metric that triggers the scale action. + - `operator` - (`string`, optional, defaults to `>=` or `<=` for scale-out and scale-in respectively) + the metric vs. threshold comparison operator, can be one of: `>`, `>=`, `<`, `<=`, + `==` or `!=`. + - `grain_window_minutes` - (`number`, required for scale-out, optional for scale-in) granularity of metrics that + the rule monitors, between 1 minute and 12 hours (specified in minutes). + - `grain_aggregation_type` - (`string`, optional, defaults to "Average") method used to combine data from + `grain_window`, can be one of `Average`, `Max`, `Min` or `Sum`. + - `aggregation_window_minutes` - (`number`, required for scale-out, optional for scale-in) time window used to analyze + metrics, between 5 minutes and 12 hours (specified in minutes), must be greater than + `grain_window_minutes`. + - `aggregation_window_type` - (`string`, optional, defaults to "Average") method used to combine data from + `aggregation_window`, can be one of `Average`, `Maximum`, `Minimum`, `Count`, `Last` + or `Total`. + - `cooldown_window_minutes` - (`number`, required) the amount of time to wait after a scale action, between 1 minute + and 1 week (specified in minutes). + - `change_count_by` - (`number`, optional, default to `1`) a number of VM instances by which the total count + of instances in a Scale Set will be changed during a scale action. Example: @@ -458,19 +483,25 @@ variable "autoscaling_profiles" { }) })), []) })) - validation { # profiles count + validation { # autoscaling_profiles condition = length(var.autoscaling_profiles) <= 20 - error_message = "Azure supports up to 20 autoscaling profiles." + error_message = <<-EOF + Azure supports up to 20 autoscaling profiles. + EOF } - validation { + validation { # recurrence condition = length(var.autoscaling_profiles) > 0 ? var.autoscaling_profiles[0].recurrence == null : true - error_message = "The `autoscaling_profiles->recurrence` property is not allowed in the 1st profile definition." + error_message = <<-EOF + The `autoscaling_profiles->recurrence` property is not allowed in the 1st profile definition. + EOF } validation { # recurrence condition = length(var.autoscaling_profiles) > 0 ? alltrue([ for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : v.recurrence != null ]) : true - error_message = "The `autoscaling_profiles->recurrence` property is required in all profiles except the 1st one." + error_message = <<-EOF + The `autoscaling_profiles->recurrence` property is required in all profiles except the 1st one. + EOF } validation { # recurrence.days condition = length(var.autoscaling_profiles) > 0 ? alltrue(flatten( @@ -483,27 +514,36 @@ variable "autoscaling_profiles" { ] ] )) : true - error_message = "The `autoscaling_profiles->recurrence.days` property can be one of: `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, `Saturday` or `Sunday`." + error_message = <<-EOF + The `autoscaling_profiles->recurrence.days` property can be one of: `Monday`, `Tuesday`, `Wednesday`, `Thursday`, `Friday`, + `Saturday` or `Sunday`. + EOF } validation { # recurrence.start_time condition = length(var.autoscaling_profiles) > 0 ? alltrue([ for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : can(regex("^(([0,1][0-9])|(2[0-3])):([0-5][0-9])$", v.recurrence.start_time)) ]) : true - error_message = "The `autoscaling_profiles->recurrence.start_time` property has to be a time in RFC3339 format." + error_message = <<-EOF + The `autoscaling_profiles->recurrence.start_time` property has to be a time in RFC3339 format. + EOF } validation { # recurrence.end_time condition = length(var.autoscaling_profiles) > 0 ? alltrue([ for v in slice(var.autoscaling_profiles, 1, length(var.autoscaling_profiles)) : can(regex("^(([0,1][0-9])|(2[0-3])):([0-5][0-9])$", v.recurrence.end_time)) ]) : true - error_message = "The `autoscaling_profiles->recurrence.end_time` property has to be a time in RFC3339 format." + error_message = <<-EOF + The `autoscaling_profiles->recurrence.end_time` property has to be a time in RFC3339 format. + EOF } - validation { # scale_rules count + validation { # scale_rules condition = alltrue([for profile in var.autoscaling_profiles : length(profile.scale_rules) <= 10]) - error_message = "Azure supports up to 10 scale rules per autoscaling profile." + error_message = <<-EOF + Azure supports up to 10 scale rules per autoscaling profile. + EOF } - validation { # scale_rule->operator + validation { # scale_rules.scale_in/out_config.operator condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ @@ -512,9 +552,11 @@ variable "autoscaling_profiles" { ] ] ])) - error_message = "The `operator` property can be one of: `>`, `>=`, `<`, `<=`, `==` or `!=`." + error_message = <<-EOF + The `operator` property can be one of: `>`, `>=`, `<`, `<=`, `==` or `!=`. + EOF } - validation { # scale_rule->grain_window_minutes + validation { # scale_rules.scale_in/out_config.grain_window_minutes condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ @@ -524,9 +566,11 @@ variable "autoscaling_profiles" { ] ] ])) - error_message = "The `grain_window_minutes` property has to be between 1 minute and 12 hours." + error_message = <<-EOF + The `grain_window_minutes` property has to be between 1 minute and 12 hours. + EOF } - validation { # scale_rule->grain_aggregation_type + validation { # scale_rules.scale_in/out_config.grain_aggregation_type condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ @@ -535,21 +579,28 @@ variable "autoscaling_profiles" { ] ] ])) - error_message = "The `grain_aggregation_type` property can be one of: `Average`, `Max`, `Min` or `Sum`." + error_message = <<-EOF + The `grain_aggregation_type` property can be one of: `Average`, `Max`, `Min` or `Sum`. + EOF } - validation { # scale_rule->aggregation_window_minutes + validation { # scale_rules.scale_in/out_config.aggregation_window_minutes condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ for config in ["scale_out_config", "scale_in_config"] : - rule[config].aggregation_window_minutes >= 5 && rule[config].aggregation_window_minutes <= 720 && rule[config].aggregation_window_minutes > rule[config].grain_window_minutes + rule[config].aggregation_window_minutes >= 5 && + rule[config].aggregation_window_minutes <= 720 && + rule[config].aggregation_window_minutes > rule[config].grain_window_minutes if rule[config].aggregation_window_minutes != null ] ] ])) - error_message = "The `aggregation_window_minutes` property has to be between 5 minute and 12 hours and should be longer than `grain_window_minutes`." + error_message = <<-EOF + The `aggregation_window_minutes` property has to be between 5 minute and 12 hours and should be longer than + `grain_window_minutes`. + EOF } - validation { # scale_rule->aggregation_window_type + validation { # scale_rules.scale_in/out_config.aggregation_window_type condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ @@ -558,9 +609,11 @@ variable "autoscaling_profiles" { ] ] ])) - error_message = "The `aggregation_window_type` property can be one of: `Average`, `Maximum`, `Minimum`, `Count`, `Last` or `Total`." + error_message = <<-EOF + The `aggregation_window_type` property can be one of: `Average`, `Maximum`, `Minimum`, `Count`, `Last` or `Total`. + EOF } - validation { # scale_rule->cooldown_window_minutes + validation { # scale_rules.scale_in/out_config.cooldown_window_minutes condition = alltrue(flatten([ for profile in var.autoscaling_profiles : [ for rule in profile.scale_rules : [ @@ -569,6 +622,8 @@ variable "autoscaling_profiles" { ] ] ])) - error_message = "The `cooldown_window_minutes` property has to be between 1 minute and 1 week." + error_message = <<-EOF + The `cooldown_window_minutes` property has to be between 1 minute and 1 week. + EOF } } diff --git a/modules/vnet/README.md b/modules/vnet/README.md index f36ac1cf..7f481692 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -271,45 +271,45 @@ Map of objects describing Network Security Groups. List of available properties: -- `name` - (`string`, required) name of the Network Security Group. -- `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. +- `name` - (`string`, required) name of the Network Security Group. +- `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. - > [!NOTE] - > All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, - > example: `21-23`. + **Note!** \ + All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, + example: `21-23`. Following attributes are available: - - `name` - (`string`, required) name of the rule - - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 - and must be unique for each rule in the collection. The lower the priority number, - the higher the priority of the rule. - - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming - or outgoing traffic. Possible values are `Inbound` and `Outbound`. - - `access` - (`string`, required) specifies whether network traffic is allowed or denied. - Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include - `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the - [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or - a range of ports. This can also be an `*` to match all. - - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports - or ranges of ports. - - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP - range or `*` to match any IP. This can also be a tag. To see all available tags for a - region use the following command (example for US West Central): - `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source - address prefixes. Tags are not allowed. - - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port - or a range of ports. This can also be an `*` to match all. - - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of - destination ports or a ranges of ports. - - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination - CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` - for details. - - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of - destination address prefixes. Tags are not allowed. + - `name` - (`string`, required) name of the rule. + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 + and must be unique for each rule in the collection. The lower the priority number, + the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming + or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. + Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include + `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the + [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol). + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or + a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports + or ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP + range or `*` to match any IP. This can also be a tag. To see all available tags for a + region use the following command (example for US West Central): + `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source + address prefixes. Tags are not allowed. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port + or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of + destination ports or a ranges of ports. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination + CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` + for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of + destination address prefixes. Tags are not allowed. Example: ```hcl @@ -394,14 +394,14 @@ Map of objects describing a Route Tables. List of available properties: - `name` - (`string`, required) name of a Route Table. -- `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP +- `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP. - `routes` - (`map`, required) a map of Route Table entries (UDRs): - - `name` - (`string`, required) a name of a UDR. - - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. - Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. - - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. - Used only when `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. + - `name` - (`string`, required) a name of a UDR. + - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. + - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. Possible values are: + `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. + - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. Used only when + `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. Example: ```hcl @@ -462,9 +462,11 @@ Controls subnet creation. Possible variants: -- `true` - create subnets described in `var.subnets` -- `false` - source subnets described in `var.subnets` -- `false` and `var.subnets` is empty - skip subnets management. +- `true` - create subnets described in `var.subnets`. +- `false` - source subnets described in `var.subnets`. + +**Note!** \ +When this variable is `false` and `var.subnets` variable is empty, subnets management is skipped. Type: bool @@ -477,8 +479,8 @@ Default value: `true` Map of objects describing subnets to manage. -By the default the described subnets will be created. -If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. +By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping +between the existing subnets and UDRs and NSGs that should be assigned to them. List of available attributes of each subnet entry: @@ -532,5 +534,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index 33f77764..b28643aa 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -43,7 +43,9 @@ variable "address_space" { for v in coalesce(var.address_space, []) : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", v)) ]) - error_message = "All items in var.address_space should be in CIDR notation, with the maximum subnet of /29." + error_message = <<-EOF + All items in var.address_space should be in CIDR notation, with the maximum subnet of /29. + EOF } } @@ -53,45 +55,45 @@ variable "network_security_groups" { List of available properties: - - `name` - (`string`, required) name of the Network Security Group. - - `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. + - `name` - (`string`, required) name of the Network Security Group. + - `rules` - (`map`, optional, defaults to `{}`) A list of objects representing Network Security Rules. - > [!NOTE] - > All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, - > example: `21-23`. + **Note!** \ + All port values are integers between `0` and `65535`. Port ranges can be specified as `minimum-maximum` port value, + example: `21-23`. Following attributes are available: - - `name` - (`string`, required) name of the rule - - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 - and must be unique for each rule in the collection. The lower the priority number, - the higher the priority of the rule. - - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming - or outgoing traffic. Possible values are `Inbound` and `Outbound`. - - `access` - (`string`, required) specifies whether network traffic is allowed or denied. - Possible values are `Allow` and `Deny`. - - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include - `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the - [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol) - - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or - a range of ports. This can also be an `*` to match all. - - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports - or ranges of ports. - - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP - range or `*` to match any IP. This can also be a tag. To see all available tags for a - region use the following command (example for US West Central): - `az network list-service-tags --location westcentralus`. - - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source - address prefixes. Tags are not allowed. - - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port - or a range of ports. This can also be an `*` to match all. - - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of - destination ports or a ranges of ports. - - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination - CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` - for details. - - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of - destination address prefixes. Tags are not allowed. + - `name` - (`string`, required) name of the rule. + - `priority` - (`number`, required) numeric priority of the rule. The value can be between 100 and 4096 + and must be unique for each rule in the collection. The lower the priority number, + the higher the priority of the rule. + - `direction` - (`string`, required) the direction specifies if rule will be evaluated on incoming + or outgoing traffic. Possible values are `Inbound` and `Outbound`. + - `access` - (`string`, required) specifies whether network traffic is allowed or denied. + Possible values are `Allow` and `Deny`. + - `protocol` - (`string`, required) a network protocol this rule applies to. Possible values include + `Tcp`, `Udp`, `Icmp`, or `*` (which matches all). For supported values refer to the + [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_securityrule#protocol). + - `source_port_range` - (`string`, required, mutually exclusive with `source_port_ranges`) a source port or + a range of ports. This can also be an `*` to match all. + - `source_port_ranges` - (`list`, required, mutually exclusive with `source_port_range`) a list of source ports + or ranges of ports. + - `source_address_prefix` - (`string`, required, mutually exclusive with `source_address_prefixes`) source CIDR or IP + range or `*` to match any IP. This can also be a tag. To see all available tags for a + region use the following command (example for US West Central): + `az network list-service-tags --location westcentralus`. + - `source_address_prefixes` - (`list`, required, mutually exclusive with `source_address_prefix`) a list of source + address prefixes. Tags are not allowed. + - `destination_port_range` - (`string`, required, mutually exclusive with `destination_port_ranges`) destination port + or a range of ports. This can also be an `*` to match all. + - `destination_port_ranges` - (`list`, required, mutually exclusive with `destination_port_range`) a list of + destination ports or a ranges of ports. + - `destination_address_prefix` - (`string`, required, mutually exclusive with `destination_address_prefixes`) destination + CIDR or IP range or `*` to match any IP. Tags are allowed, see `source_address_prefix` + for details. + - `destination_address_prefixes` - (`list`, required, mutually exclusive with `destination_address_prefixes`) a list of + destination address prefixes. Tags are not allowed. Example: ```hcl @@ -161,62 +163,79 @@ variable "network_security_groups" { })), {}) })) validation { # name - condition = length([for _, v in var.network_security_groups : v.name]) == length(distinct([for _, v in var.network_security_groups : v.name])) - error_message = "The `name` property has to be unique." + condition = length( + [for _, v in var.network_security_groups : v.name]) == length(distinct([for _, v in var.network_security_groups : v.name]) + ) + error_message = <<-EOF + The `name` property has to be unique. + EOF } - validation { # rule.name + validation { # rules.name condition = alltrue([ for _, nsg in var.network_security_groups : length([for _, rule in nsg.rules : rule.name]) == length(distinct([for _, rule in nsg.rules : rule.name])) ]) - error_message = "The `rule.name` property has to be unique in a particular NSG." + error_message = <<-EOF + The `rule.name` property has to be unique in a particular NSG. + EOF } - validation { # rule.priority + validation { # rules.priority condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : rule.priority >= 100 && rule.priority <= 4096 ] ])) - error_message = "The `priority` should be a value between 100 and 4096." + error_message = <<-EOF + The `priority` should be a value between 100 and 4096. + EOF } - validation { # rule.direction + validation { # rules.direction condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : contains(["Inbound", "Outbound"], rule.direction) ] ])) - error_message = "The `direction` property should be one of Inbound or Outbound." + error_message = <<-EOF + The `direction` property should be one of Inbound or Outbound. + EOF } - validation { # rule.access + validation { # rules.access condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : contains(["Allow", "Deny"], rule.access) ] ])) - error_message = "The `access` property should be one of Allow or Deny." + error_message = <<-EOF + The `access` property should be one of Allow or Deny. + EOF } - validation { # rule.protocol + validation { # rules.protocol condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : contains(["Tcp", "Udp", "Icmp", "*"], rule.protocol) ] ])) - error_message = "The `protocol` property should be one of Tcp, Udp, Icmp or *." + error_message = <<-EOF + The `protocol` property should be one of Tcp, Udp, Icmp or *. + EOF } - validation { # rule.source_port_range(s) + validation { # rules.source_port_range(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - (rule.source_port_range == null || rule.source_port_ranges == null) && !(rule.source_port_range == null && rule.source_port_ranges == null) + (rule.source_port_range == null || rule.source_port_ranges == null) && + !(rule.source_port_range == null && rule.source_port_ranges == null) ] ])) - error_message = "The `source_port_range` and `source_port_ranges` properties are required but mutually exclusive." + error_message = <<-EOF + The `source_port_range` and `source_port_ranges` properties are required but mutually exclusive. + EOF } - validation { # rule.source_port_range + validation { # rules.source_port_range condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -224,9 +243,12 @@ variable "network_security_groups" { if rule.source_port_range != null ] ])) - error_message = "The `source_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." + error_message = <<-EOF + The `source_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports + (delimited with a '-'). + EOF } - validation { # rule.source_port_ranges + validation { # rules.source_port_ranges condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ @@ -235,18 +257,23 @@ variable "network_security_groups" { ] ] ])) - error_message = "The `source_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." + error_message = <<-EOF + The `source_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-'). + EOF } - validation { # rule.destination_port_range(s) + validation { # rules.destination_port_range(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - (rule.destination_port_range == null || rule.destination_port_ranges == null) && !(rule.destination_port_range == null && rule.destination_port_ranges == null) + (rule.destination_port_range == null || rule.destination_port_ranges == null) && + !(rule.destination_port_range == null && rule.destination_port_ranges == null) ] ])) - error_message = "The `destination_port_range` and `destination_port_ranges` properties are required but mutually exclusive." + error_message = <<-EOF + The `destination_port_range` and `destination_port_ranges` properties are required but mutually exclusive. + EOF } - validation { # rule.destination_port_range + validation { # rules.destination_port_range condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -254,9 +281,12 @@ variable "network_security_groups" { if rule.destination_port_range != null ] ])) - error_message = "The `destination_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports (delimited with a '-')." + error_message = <<-EOF + The `destination_port_range` can be either an '*' or a port number (between 0 and 65535) or a range of ports + (delimited with a '-'). + EOF } - validation { # rule.destination_port_ranges + validation { # rules.destination_port_ranges condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ @@ -265,18 +295,23 @@ variable "network_security_groups" { ] ] ])) - error_message = "The `destination_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-')." + error_message = <<-EOF + The `destination_port_ranges` is a list of port numbers (between 0 and 65535) or a ranges of ports (delimited with a '-'). + EOF } - validation { # rule.source_address_prefix(s) + validation { # rules.source_address_prefix(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - (rule.source_address_prefix == null || rule.source_address_prefixes == null) && !(rule.source_address_prefix == null && rule.source_address_prefixes == null) + (rule.source_address_prefix == null || rule.source_address_prefixes == null) && + !(rule.source_address_prefix == null && rule.source_address_prefixes == null) ] ])) - error_message = "The `source_address_prefixes` and `source_address_prefixes` properties are required but mutually exclusive." + error_message = <<-EOF + The `source_address_prefix` and `source_address_prefixes` properties are required but mutually exclusive. + EOF } - validation { # rule.source_address_prefix + validation { # rules.source_address_prefix condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -284,9 +319,11 @@ variable "network_security_groups" { if rule.source_address_prefix != null ] ])) - error_message = "The `source_address_prefix` can be either '*', a CIDR or an Azure Service Tag." + error_message = <<-EOF + The `source_address_prefix` can be either '*', a CIDR or an Azure Service Tag. + EOF } - validation { # rule.source_address_prefixes + validation { # rules.source_address_prefixes condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ @@ -295,18 +332,23 @@ variable "network_security_groups" { ] ] ])) - error_message = "The `source_address_prefixes` can be a list of CIDRs." + error_message = <<-EOF + The `source_address_prefixes` can be a list of CIDRs. + EOF } - validation { # rule.destination_address_prefix(s) + validation { # rules.destination_address_prefix(s) condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : - (rule.destination_address_prefix == null || rule.destination_address_prefixes == null) && !(rule.destination_address_prefix == null && rule.destination_address_prefixes == null) + (rule.destination_address_prefix == null || rule.destination_address_prefixes == null) && + !(rule.destination_address_prefix == null && rule.destination_address_prefixes == null) ] ])) - error_message = "The `destination_address_prefix` and `destination_address_prefixes` properties are required but mutually exclusive." + error_message = <<-EOF + The `destination_address_prefix` and `destination_address_prefixes` properties are required but mutually exclusive. + EOF } - validation { # rule.destination_address_prefix + validation { # rules.destination_address_prefix condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : @@ -314,9 +356,11 @@ variable "network_security_groups" { if rule.destination_address_prefix != null ] ])) - error_message = "The `destination_address_prefix` can be either '*', a CIDR or an Azure Service Tag." + error_message = <<-EOF + The `destination_address_prefix` can be either '*', a CIDR or an Azure Service Tag. + EOF } - validation { # rule.destination_address_prefixes + validation { # rules.destination_address_prefixes condition = alltrue(flatten([ for _, nsg in var.network_security_groups : [ for _, rule in nsg.rules : [ @@ -325,7 +369,9 @@ variable "network_security_groups" { ] ] ])) - error_message = "The `destination_address_prefixes` can be a list of CIDRs." + error_message = <<-EOF + The `destination_address_prefixes` can be a list of CIDRs. + EOF } } @@ -336,14 +382,14 @@ variable "route_tables" { List of available properties: - `name` - (`string`, required) name of a Route Table. - - `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP + - `disable_bgp_route_propagation` - (`bool`, optional, defaults to `false`) controls propagation of routes learned by BGP. - `routes` - (`map`, required) a map of Route Table entries (UDRs): - - `name` - (`string`, required) a name of a UDR. - - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. - - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. - Possible values are: `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. - - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. - Used only when `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. + - `name` - (`string`, required) a name of a UDR. + - `address_prefix` - (`string`, required) the destination CIDR to which the route applies, such as `10.1.0.0/16`. + - `next_hop_type` - (`string`, required) the type of Azure hop the packet should be sent to. Possible values are: + `VirtualNetworkGateway`, `VnetLocal`, `Internet`, `VirtualAppliance` and `None`. + - `next_hop_ip_address` - (`string`, required) contains the IP address packets should be forwarded to. Used only when + `next_hop_type` is set to `VirtualAppliance`, ignored otherwise. Example: ```hcl @@ -391,16 +437,20 @@ variable "route_tables" { })) validation { # name condition = length([for _, v in var.route_tables : v.name]) == length(distinct([for _, v in var.route_tables : v.name])) - error_message = "The `name` property has to be unique." + error_message = <<-EOF + The `name` property has to be unique. + EOF } - validation { # route.name + validation { # routes.name condition = alltrue([ for _, rt in var.route_tables : length([for _, udr in rt.routes : udr.name]) == length(distinct([for _, udr in rt.routes : udr.name])) ]) - error_message = "The `rule.name` property has to be unique in a particular NSG." + error_message = <<-EOF + The `rule.name` property has to be unique in a particular NSG. + EOF } - validation { # route.address_prefix + validation { # routes.address_prefix condition = alltrue(flatten([ for _, rt in var.route_tables : [ for _, udr in rt.routes : [ @@ -408,17 +458,24 @@ variable "route_tables" { ] ] ])) - error_message = "The `address_prefix` should be in CIDR notation." + error_message = <<-EOF + The `address_prefix` should be in CIDR notation. + EOF } - validation { # route.next_hop_type + validation { # routes.next_hop_type condition = alltrue(flatten([ for _, rt in var.route_tables : [ - for _, udr in rt.routes : can(udr.next_hop_type) ? contains(["VirtualNetworkGateway", "VnetLocal", "Internet", "VirtualAppliance", "None"], udr.next_hop_type) : true + for _, udr in rt.routes : can(udr.next_hop_type) ? contains( + ["VirtualNetworkGateway", "VnetLocal", "Internet", "VirtualAppliance", "None"], udr.next_hop_type + ) : true ] ])) - error_message = "The `next_hop_type` route property should have value of either: \"VirtualNetworkGateway\", \"VnetLocal\", \"Internet\", \"VirtualAppliance\" or \"None\"." + error_message = <<-EOF + The `next_hop_type` route property should have value of either: \"VirtualNetworkGateway\", \"VnetLocal\", \"Internet\", + \"VirtualAppliance\" or \"None\". + EOF } - validation { # route.next_hop_ip_address + validation { # routes.next_hop_ip_address condition = alltrue(flatten([ for _, rt in var.route_tables : [ for _, udr in rt.routes : @@ -426,7 +483,9 @@ variable "route_tables" { if udr.next_hop_ip_address != null ] ])) - error_message = "The `next_hop_ip_address` should be a valid IPv4 address." + error_message = <<-EOF + The `next_hop_ip_address` should be a valid IPv4 address. + EOF } } @@ -436,9 +495,11 @@ variable "create_subnets" { Possible variants: - - `true` - create subnets described in `var.subnets` - - `false` - source subnets described in `var.subnets` - - `false` and `var.subnets` is empty - skip subnets management. + - `true` - create subnets described in `var.subnets`. + - `false` - source subnets described in `var.subnets`. + + **Note!** \ + When this variable is `false` and `var.subnets` variable is empty, subnets management is skipped. EOF default = true nullable = false @@ -449,8 +510,8 @@ variable "subnets" { description = <<-EOF Map of objects describing subnets to manage. - By the default the described subnets will be created. - If however `create_subnets` is set to `false` this is just a mapping between the existing subnets and UDRs and NSGs that should be assigned to them. + By the default the described subnets will be created. If however `create_subnets` is set to `false` this is just a mapping + between the existing subnets and UDRs and NSGs that should be assigned to them. List of available attributes of each subnet entry: @@ -497,15 +558,19 @@ variable "subnets" { })) validation { # name condition = length([for _, v in var.subnets : v.name]) == length(distinct([for _, v in var.subnets : v.name])) - error_message = "The `name` property has to be unique." + error_message = <<-EOF + The `name` property has to be unique. + EOF } - validation { # subnet.address_prefixes + validation { # address_prefixes condition = alltrue(flatten([ for _, snet in var.subnets : [ for _, cidr in snet.address_prefixes : can(regex("^(\\d{1,3}\\.){3}\\d{1,3}\\/[12]?[0-9]$", cidr)) ] ])) - error_message = "The `address_prefixes` should be list of CIDR blocks, with the maximum subnet of /29." + error_message = <<-EOF + The `address_prefixes` should be list of CIDR blocks, with the maximum subnet of /29. + EOF } } From 7beb04bdd865f53efc7be420f69f419b0aea9bfb Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 4 Mar 2024 11:40:16 +0100 Subject: [PATCH 21/49] chore: Add comments with links to TF Registry documentation to all resources (#20) --- examples/appgw/README.md | 1 + examples/appgw/main.tf | 3 +++ examples/common_vmseries/README.md | 1 + examples/common_vmseries/main.tf | 4 ++++ examples/common_vmseries_and_autoscale/README.md | 1 + examples/common_vmseries_and_autoscale/main.tf | 3 +++ examples/dedicated_vmseries/README.md | 1 + examples/dedicated_vmseries/main.tf | 4 ++++ examples/dedicated_vmseries_and_autoscale/README.md | 1 + examples/dedicated_vmseries_and_autoscale/main.tf | 3 +++ examples/gwlb_with_vmseries/README.md | 1 + examples/gwlb_with_vmseries/main.tf | 4 ++++ examples/standalone_panorama/main.tf | 3 +++ examples/standalone_vmseries/README.md | 1 + examples/standalone_vmseries/main.tf | 3 +++ examples/test_infrastructure/main.tf | 3 +++ examples/virtual_network_gateway/main.tf | 2 ++ modules/loadbalancer/main.tf | 9 ++++++++- modules/name_templater/main.tf | 1 + modules/virtual_machine/main.tf | 4 ++++ modules/vnet/main.tf | 10 ++++++++++ 21 files changed, 62 insertions(+), 1 deletion(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index c1a29658..992114d3 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -375,4 +375,5 @@ Default value: `true` + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf index 55610608..1bde6a85 100644 --- a/examples/appgw/main.tf +++ b/examples/appgw/main.tf @@ -1,4 +1,5 @@ # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -7,6 +8,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name @@ -17,6 +19,7 @@ locals { } # Create public IP in order to reuse it in 1 of the application gateways +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { name = "pip-existing" resource_group_name = local.resource_group.name diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 64350283..cf8f1d35 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -1078,4 +1078,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index b2a5393d..ecee2c8d 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.vmseries : v.authentication.password == null @@ -26,6 +27,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -34,6 +36,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name @@ -251,6 +254,7 @@ module "bootstrap" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set resource "azurerm_availability_set" "this" { for_each = var.availability_sets diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 1cb1b8b5..93cf8f78 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -972,4 +972,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 4f7c5f84..9f208f66 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.scale_sets : v.authentication.password == null @@ -27,6 +28,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -35,6 +37,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 42e914c2..7a466066 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -1082,4 +1082,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index b2a5393d..ecee2c8d 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.vmseries : v.authentication.password == null @@ -26,6 +27,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -34,6 +36,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name @@ -251,6 +254,7 @@ module "bootstrap" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set resource "azurerm_availability_set" "this" { for_each = var.availability_sets diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index dcbfe2fd..eb0acea1 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -968,4 +968,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 4f7c5f84..9f208f66 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.scale_sets : v.authentication.password == null @@ -27,6 +28,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -35,6 +37,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index edc98d0c..3b146381 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -809,4 +809,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 820b29d2..8761e2c4 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.vmseries : v.authentication.password == null @@ -26,6 +27,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -34,6 +36,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name @@ -239,6 +242,7 @@ module "bootstrap" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set resource "azurerm_availability_set" "this" { for_each = var.availability_sets diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index d12684b4..5a26246f 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.panoramas : v.authentication.password == null @@ -26,6 +27,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -34,6 +36,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index f6296ff3..c44da705 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -1022,4 +1022,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 7335a3ba..f69aa3bc 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ for _, v in var.vmseries : v.authentication.password == null @@ -26,6 +27,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -34,6 +36,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/examples/test_infrastructure/main.tf b/examples/test_infrastructure/main.tf index 8a902444..41b63865 100644 --- a/examples/test_infrastructure/main.tf +++ b/examples/test_infrastructure/main.tf @@ -1,4 +1,5 @@ # Generate a random password. +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = var.password == null ? 1 : 0 @@ -15,6 +16,7 @@ locals { } # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -23,6 +25,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf index fdb9a1ee..959871c6 100644 --- a/examples/virtual_network_gateway/main.tf +++ b/examples/virtual_network_gateway/main.tf @@ -1,4 +1,5 @@ # Create or source the Resource Group. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" @@ -7,6 +8,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 name = var.resource_group_name diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index 044617ee..0f23661b 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -30,6 +30,7 @@ locals { out_rules = { for v in local.out_flat_rules : "${v.fipkey}-${v.rulekey}" => v } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { for_each = { for k, v in var.frontend_ips : k => v if v.create_public_ip } @@ -42,6 +43,7 @@ resource "azurerm_public_ip" "this" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip data "azurerm_public_ip" "this" { for_each = { for k, v in var.frontend_ips : k => v if !v.create_public_ip && v.public_ip_name != null } @@ -49,6 +51,7 @@ data "azurerm_public_ip" "this" { resource_group_name = coalesce(each.value.public_ip_resource_group, var.resource_group_name) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb resource "azurerm_lb" "this" { name = var.name resource_group_name = var.resource_group_name @@ -85,6 +88,7 @@ resource "azurerm_lb" "this" { } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_backend_address_pool resource "azurerm_lb_backend_address_pool" "this" { name = var.backend_name loadbalancer_id = azurerm_lb.this.id @@ -113,6 +117,7 @@ locals { } : {} } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_probe resource "azurerm_lb_probe" "this" { for_each = merge(coalesce(var.health_probes, {}), local.default_probe) @@ -130,6 +135,7 @@ resource "azurerm_lb_probe" "this" { number_of_probes = 1 } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_rule resource "azurerm_lb_rule" "this" { for_each = local.in_rules @@ -147,6 +153,7 @@ resource "azurerm_lb_rule" "this" { load_distribution = each.value.rule.session_persistence } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/lb_outbound_rule resource "azurerm_lb_outbound_rule" "this" { for_each = local.out_rules @@ -179,7 +186,7 @@ locals { } } -# Optional NSG rules. Each corresponds to one azurerm_lb_rule. +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_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 } diff --git a/modules/name_templater/main.tf b/modules/name_templater/main.tf index 19e0db90..f3d0d6cf 100644 --- a/modules/name_templater/main.tf +++ b/modules/name_templater/main.tf @@ -1,3 +1,4 @@ +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet resource "random_pet" "this" { count = contains( flatten([for e in var.name_template.parts : [for _, v in e : v]]), diff --git a/modules/virtual_machine/main.tf b/modules/virtual_machine/main.tf index 5c0ced8a..6ac29247 100644 --- a/modules/virtual_machine/main.tf +++ b/modules/virtual_machine/main.tf @@ -5,6 +5,7 @@ locals { disable_password_authentication = var.password == null ? true : false } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { for_each = { for k, v in var.interfaces : k => v if try(v.create_public_ip, false) } @@ -17,6 +18,7 @@ resource "azurerm_public_ip" "this" { tags = try(each.value.tags, var.tags) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface resource "azurerm_network_interface" "this" { count = length(var.interfaces) @@ -36,6 +38,7 @@ resource "azurerm_network_interface" "this" { } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_backend_address_pool_association resource "azurerm_network_interface_backend_address_pool_association" "this" { for_each = { for k, v in var.interfaces : k => v if try(v.enable_backend_pool, false) } @@ -49,6 +52,7 @@ resource "azurerm_network_interface_backend_address_pool_association" "this" { ] } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine resource "azurerm_virtual_machine" "this" { name = var.name location = var.location diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index f5f8f5b7..0a8915cd 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -1,3 +1,4 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network resource "azurerm_virtual_network" "this" { count = var.create_virtual_network ? 1 : 0 @@ -15,6 +16,7 @@ resource "azurerm_virtual_network" "this" { } } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network data "azurerm_virtual_network" "this" { count = var.create_virtual_network == false ? 1 : 0 @@ -26,6 +28,7 @@ locals { virtual_network = var.create_virtual_network ? azurerm_virtual_network.this[0] : data.azurerm_virtual_network.this[0] } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet resource "azurerm_subnet" "this" { for_each = { for k, v in var.subnets : k => v if var.create_subnets } @@ -36,6 +39,7 @@ resource "azurerm_subnet" "this" { service_endpoints = each.value.enable_storage_service_endpoint ? ["Microsoft.Storage"] : null } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/subnet data "azurerm_subnet" "this" { for_each = { for k, v in var.subnets : k => v if var.create_subnets == false } @@ -48,6 +52,7 @@ locals { subnets = var.create_subnets ? azurerm_subnet.this : data.azurerm_subnet.this } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group resource "azurerm_network_security_group" "this" { for_each = var.network_security_groups @@ -70,6 +75,7 @@ locals { ]) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule resource "azurerm_network_security_rule" "this" { for_each = { for nsg in local.nsg_rules : "${nsg.nsg_key}-${nsg.rule_name}" => nsg @@ -94,6 +100,7 @@ resource "azurerm_network_security_rule" "this" { depends_on = [azurerm_network_security_group.this] } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route_table resource "azurerm_route_table" "this" { for_each = var.route_tables @@ -117,6 +124,7 @@ locals { ]) } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/route resource "azurerm_route" "this" { for_each = { for route in local.route : "${route.route_table_key}-${route.route_name}" => route @@ -130,6 +138,7 @@ resource "azurerm_route" "this" { next_hop_in_ip_address = each.value.route.next_hop_type == "VirtualAppliance" ? each.value.route.next_hop_ip_address : null } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association resource "azurerm_subnet_network_security_group_association" "this" { for_each = { for k, v in var.subnets : k => v if v.network_security_group_key != null } @@ -137,6 +146,7 @@ resource "azurerm_subnet_network_security_group_association" "this" { network_security_group_id = azurerm_network_security_group.this[each.value.network_security_group_key].id } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_route_table_association resource "azurerm_subnet_route_table_association" "this" { for_each = { for k, v in var.subnets : k => v if v.route_table_key != null } From d8df2b8e167d60d783eca139aaae1a42ee386c6f Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 4 Mar 2024 19:30:52 +0100 Subject: [PATCH 22/49] Fix GO files for appgw & vng examples --- examples/appgw/main_test.go | 2 +- examples/virtual_network_gateway/main_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/appgw/main_test.go b/examples/appgw/main_test.go index 0669ff80..fd492e7e 100644 --- a/examples/appgw/main_test.go +++ b/examples/appgw/main_test.go @@ -6,7 +6,7 @@ import ( "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/terraform" - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton/pkg/testskeleton" ) func CreateTerraformOptions(t *testing.T) *terraform.Options { diff --git a/examples/virtual_network_gateway/main_test.go b/examples/virtual_network_gateway/main_test.go index 97d2facd..c8f1e73b 100644 --- a/examples/virtual_network_gateway/main_test.go +++ b/examples/virtual_network_gateway/main_test.go @@ -6,7 +6,7 @@ import ( "github.com/gruntwork-io/terratest/modules/logger" "github.com/gruntwork-io/terratest/modules/terraform" - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton/pkg/testskeleton" ) func CreateTerraformOptions(t *testing.T) *terraform.Options { From 18545ec8ecd8498dbd4e8a55ef7823e5811de3aa Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Wed, 6 Mar 2024 15:42:26 +0100 Subject: [PATCH 23/49] chore: Upgrade Terraform version for examples, fix links for diagrams and sources for modules (#23) --- examples/appgw/README.md | 2 +- examples/appgw/versions.tf | 2 +- examples/common_vmseries/.header.md | 4 +- examples/common_vmseries/README.md | 4 +- examples/common_vmseries/versions.tf | 2 +- .../common_vmseries_and_autoscale/.header.md | 4 +- .../common_vmseries_and_autoscale/README.md | 4 +- .../common_vmseries_and_autoscale/versions.tf | 2 +- examples/dedicated_vmseries/.header.md | 4 +- examples/dedicated_vmseries/README.md | 3 +- examples/dedicated_vmseries/versions.tf | 2 +- .../.header.md | 4 +- .../README.md | 4 +- .../versions.tf | 2 +- examples/standalone_panorama/.header.md | 2 +- examples/standalone_panorama/README.md | 3 +- examples/standalone_panorama/versions.tf | 2 +- examples/standalone_vmseries/README.md | 2 +- examples/standalone_vmseries/versions.tf | 2 +- examples/test_infrastructure/.header.md | 16 + examples/test_infrastructure/README.md | 388 +++++++++++++++--- examples/test_infrastructure/versions.tf | 2 +- examples/virtual_network_gateway/README.md | 4 + examples/virtual_network_gateway/versions.tf | 2 +- modules/appgw/.header.md | 2 +- modules/appgw/README.md | 4 +- modules/bootstrap/.header.md | 6 +- modules/bootstrap/README.md | 9 +- modules/bootstrap/versions.tf | 2 +- modules/gwlb/README.md | 1 + modules/loadbalancer/.header.md | 4 +- modules/loadbalancer/README.md | 11 +- modules/loadbalancer/versions.tf | 4 +- modules/name_templater/.header.md | 2 +- modules/name_templater/README.md | 3 +- modules/natgw/.header.md | 2 +- modules/natgw/README.md | 5 +- modules/natgw/versions.tf | 2 +- modules/ngfw_metrics/.header.md | 2 +- modules/ngfw_metrics/README.md | 3 +- modules/panorama/README.md | 1 + modules/virtual_machine/versions.tf | 2 +- modules/virtual_network_gateway/.header.md | 2 +- modules/virtual_network_gateway/README.md | 5 +- modules/vmseries/README.md | 5 +- modules/vmseries/versions.tf | 2 +- modules/vmss/.header.md | 2 +- modules/vmss/README.md | 1 + modules/vnet/README.md | 7 +- modules/vnet/versions.tf | 4 +- modules/vnet_peering/README.md | 6 +- modules/vnet_peering/versions.tf | 4 +- 52 files changed, 436 insertions(+), 133 deletions(-) create mode 100644 examples/test_infrastructure/.header.md diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 992114d3..08f5d4db 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -31,7 +31,7 @@ Name | Type | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/appgw/versions.tf b/examples/appgw/versions.tf index 95b07f02..85620563 100644 --- a/examples/appgw/versions.tf +++ b/examples/appgw/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/common_vmseries/.header.md b/examples/common_vmseries/.header.md index 4caf7cbe..4de89653 100644 --- a/examples/common_vmseries/.header.md +++ b/examples/common_vmseries/.header.md @@ -16,7 +16,7 @@ common VM-Series for all traffic; for a discussion of other options, please see ## Reference Architecture Design -![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/aa2ae33a-fb46-4a1c-9811-98ea3b132297) This code implements: @@ -39,7 +39,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments because the number of firewalls low. However, the technical integration complexity is high. -![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920647-c7dc77c1-d86c-42ac-ba5a-59a95439ef23.png) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/8e8da6e0-afba-4bb5-b2c7-a95c7250dab3) This reference architecture consists of: diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 1c7115a5..2b5fc0e5 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -19,7 +19,6 @@ common VM-Series for all traffic; for a discussion of other options, please see ![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/aa2ae33a-fb46-4a1c-9811-98ea3b132297) - This code implements: - a *centralized design*, a hub-and-spoke topology with a Transit VNet containing VM-Series to inspect all inbound, outbound, @@ -43,7 +42,6 @@ because the number of firewalls low. However, the technical integration complexi ![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/8e8da6e0-afba-4bb5-b2c7-a95c7250dab3) - This reference architecture consists of: - a VNET containing: @@ -212,7 +210,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/common_vmseries/versions.tf b/examples/common_vmseries/versions.tf index 95b07f02..85620563 100644 --- a/examples/common_vmseries/versions.tf +++ b/examples/common_vmseries/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/common_vmseries_and_autoscale/.header.md b/examples/common_vmseries_and_autoscale/.header.md index ec76e4d7..c001fc82 100644 --- a/examples/common_vmseries_and_autoscale/.header.md +++ b/examples/common_vmseries_and_autoscale/.header.md @@ -22,7 +22,7 @@ but a [dedicated one exists](../standalone\_panorama/README.md). ## Reference Architecture Design -![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/aa2ae33a-fb46-4a1c-9811-98ea3b132297) This code implements: @@ -47,7 +47,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. -![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6500664/b10403f9-795a-4501-a189-3c21d44fc9e7) +![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/7d363d6a-b394-4851-99b9-03ce8abf379a) This reference architecture consists of: diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index b27f95a7..3009e858 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -50,7 +50,6 @@ outbound traffic flows occur on the same set of firewalls. However, the technica ![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/7d363d6a-b394-4851-99b9-03ce8abf379a) - This reference architecture consists of: - a VNET containing: @@ -235,6 +234,9 @@ Name | Description ## Module's Nameplate +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/common_vmseries_and_autoscale/versions.tf b/examples/common_vmseries_and_autoscale/versions.tf index c49189a0..bd4a41d5 100644 --- a/examples/common_vmseries_and_autoscale/versions.tf +++ b/examples/common_vmseries_and_autoscale/versions.tf @@ -1,5 +1,5 @@ terraform { - # required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/dedicated_vmseries/.header.md b/examples/dedicated_vmseries/.header.md index 0b6f6397..0c812dab 100644 --- a/examples/dedicated_vmseries/.header.md +++ b/examples/dedicated_vmseries/.header.md @@ -16,7 +16,7 @@ dedicated-inbound VM-Series; for a discussion of other options, please see the d ## Reference Architecture Design -![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/aa2ae33a-fb46-4a1c-9811-98ea3b132297) This code implements: @@ -40,7 +40,7 @@ The second set of VM-Series firewalls services all outbound, east-west, and ente choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Detailed Topology Diagram](https://user-images.githubusercontent.com/2110772/234920818-44e4082d-b445-4ffc-b0cb-174ef1e3c2ae.png) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/3644469f-5f0f-44f9-8990-010c8bcf1cec) This reference architecture consists of: diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index ebf362f8..229cc229 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -43,7 +43,6 @@ flows affecting other traffic flows within the deployment. ![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/3644469f-5f0f-44f9-8990-010c8bcf1cec) - This reference architecture consists of: - a VNET containing: @@ -215,7 +214,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/dedicated_vmseries/versions.tf b/examples/dedicated_vmseries/versions.tf index 95b07f02..85620563 100644 --- a/examples/dedicated_vmseries/versions.tf +++ b/examples/dedicated_vmseries/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/dedicated_vmseries_and_autoscale/.header.md b/examples/dedicated_vmseries_and_autoscale/.header.md index 57d88b65..cb288864 100644 --- a/examples/dedicated_vmseries_and_autoscale/.header.md +++ b/examples/dedicated_vmseries_and_autoscale/.header.md @@ -22,7 +22,7 @@ Panorama instance is not covered in this example, but a [dedicated one exists](. ## Reference Architecture Design -![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/6574404/a7c2452d-f926-49da-bf21-9d840282a0a2) +![simple](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/aa2ae33a-fb46-4a1c-9811-98ea3b132297) This code implements: @@ -48,7 +48,7 @@ set of VM-Series firewalls services all outbound, east-west, and enterprise netw increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/be84d4cb-c4c0-4e62-8bd7-8f5050215876) +![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/757005dc-3e24-4b39-8a69-7b3fbf9819cb) This reference architecture consists of: diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index a102b1aa..6e455114 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -51,7 +51,6 @@ other traffic flows within the deployment. ![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/757005dc-3e24-4b39-8a69-7b3fbf9819cb) - This reference architecture consists of: - a VNET containing: @@ -231,6 +230,9 @@ Name | Description ## Module's Nameplate +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/dedicated_vmseries_and_autoscale/versions.tf b/examples/dedicated_vmseries_and_autoscale/versions.tf index c49189a0..bd4a41d5 100644 --- a/examples/dedicated_vmseries_and_autoscale/versions.tf +++ b/examples/dedicated_vmseries_and_autoscale/versions.tf @@ -1,5 +1,5 @@ terraform { - # required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/standalone_panorama/.header.md b/examples/standalone_panorama/.header.md index 39b84dd9..534dd550 100644 --- a/examples/standalone_panorama/.header.md +++ b/examples/standalone_panorama/.header.md @@ -23,7 +23,7 @@ This is a non zonal deployment. The deployed infrastructure consists of: - a Network Security Group to give access to Panorama's public interface - a Panorama appliance with a public IP assigned to the management interface -![standalone-panorama](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules/assets/2110772/a2394f73-c0a8-4878-8693-825356abbd23) +![standalone-panorama](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/b2dadd69-f5b5-4ac4-b356-467ef79cbb0b) ## Prerequisites diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 63422e70..4febf400 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -26,7 +26,6 @@ This is a non zonal deployment. The deployed infrastructure consists of: ![standalone-panorama](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/b2dadd69-f5b5-4ac4-b356-467ef79cbb0b) - ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: @@ -156,7 +155,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/standalone_panorama/versions.tf b/examples/standalone_panorama/versions.tf index 036260f7..79f61517 100644 --- a/examples/standalone_panorama/versions.tf +++ b/examples/standalone_panorama/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index c44da705..b6fc80ee 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -154,7 +154,7 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.2, < 2.0 +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: diff --git a/examples/standalone_vmseries/versions.tf b/examples/standalone_vmseries/versions.tf index 95b07f02..85620563 100644 --- a/examples/standalone_vmseries/versions.tf +++ b/examples/standalone_vmseries/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/test_infrastructure/.header.md b/examples/test_infrastructure/.header.md new file mode 100644 index 00000000..0e66695b --- /dev/null +++ b/examples/test_infrastructure/.header.md @@ -0,0 +1,16 @@ +# Test Infrastructure code + +Terraform code to deploy a test infrastructure consisting of: + +* two VNETs that can be peered with the transit VNET deployed in any of the examples, each contains: + * a Linux-based VM running NGINX server to mock a web application + * an Azure Bastion (enables SSH access to the VM) + * UDRs forcing the traffic to flow through the NVA deployed by any of NGFW examples. + +## Usage + +To use this code, please deploy one of the examples first. Then copy the [`examples.tfvars`](./example.tfvars) to `terraform.tfvars` and edit it to your needs. + +Please correct the values marked with `TODO` markers at minimum. + +## Reference diff --git a/examples/test_infrastructure/README.md b/examples/test_infrastructure/README.md index 2e7007e6..a2d5cdd4 100644 --- a/examples/test_infrastructure/README.md +++ b/examples/test_infrastructure/README.md @@ -1,3 +1,4 @@ +<!-- BEGIN_TF_DOCS --> # Test Infrastructure code Terraform code to deploy a test infrastructure consisting of: @@ -14,62 +15,331 @@ To use this code, please deploy one of the examples first. Then copy the [`examp Please correct the values marked with `TODO` markers at minimum. ## Reference -<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> -### Requirements - -| Name | Version | -|------|---------| -| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| <a name="provider_random"></a> [random](#provider\_random) | n/a | -| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| <a name="module_vnet"></a> [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| <a name="module_vnet_peering"></a> [vnet\_peering](#module\_vnet\_peering) | ../../modules/vnet_peering | n/a | - -### Resources - -| Name | Type | -|------|------| -| [azurerm_bastion_host.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/bastion_host) | resource | -| [azurerm_linux_virtual_machine.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine) | resource | -| [azurerm_network_interface.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource | -| [azurerm_public_ip.bastion](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource | -| [random_password.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | - -### Inputs - -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| <a name="input_location"></a> [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| <a name="input_name_prefix"></a> [name\_prefix](#input\_name\_prefix) | A prefix that will be added to all created resources.<br>There is no default delimiter applied between the prefix and the resource name. Please include the delimiter in the actual prefix.<br><br>Example:<pre>name_prefix = "test-"</pre>NOTICE. This prefix is not applied to existing resources.<br>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. | `string` | `""` | no | -| <a name="input_create_resource_group"></a> [create\_resource\_group](#input\_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`.<br>When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| <a name="input_vnets"></a> [vnets](#input\_vnets) | A map defining VNETs.<br><br>For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md)<br><br>- `name` : A name of a VNET.<br>- `create_virtual_network` : (default: `true`) when set to `true` will create a VNET, `false` will source an existing VNET, in both cases the name of the VNET is specified with `name`<br>- `address_space` : a list of CIDRs for VNET<br>- `resource_group_name` : (default: current RG) a name of a Resource Group in which the VNET will reside<br>- `hub_vnet_name` : (default: `null`) name of an existing transit VNET. Setting this value triggers peering between the spoke and the transit VNET<br>- `hub_resource_group_name`: (default: current RG) name of a Resource Group hosting a transit VNET, when skipped, the local Resource Group will be used<br><br>- `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets<br>- `subnets` : map of Subnets to create<br><br>- `network_security_groups` : map of Network Security Groups to create<br>- `route_tables` : map of Route Tables to create. | `any` | n/a | yes | -| <a name="input_hub_resource_group_name"></a> [hub\_resource\_group\_name](#input\_hub\_resource\_group\_name) | Name of the Resource Group hosting the hub/transit infrastructure. This value is required to create peering between the spoke and the hub VNET. | `string` | `null` | no | -| <a name="input_hub_vnet_name"></a> [hub\_vnet\_name](#input\_hub\_vnet\_name) | Name of the hub/transit VNET. This value is required to create peering between the spoke and the hub VNET. | `string` | `null` | no | -| <a name="input_vm_size"></a> [vm\_size](#input\_vm\_size) | Azure test VM size. | `string` | `"Standard_D1_v2"` | no | -| <a name="input_username"></a> [username](#input\_username) | Name of the VM admin account. | `string` | `"panadmin"` | no | -| <a name="input_password"></a> [password](#input\_password) | A password for the admin account. | `string` | `null` | no | -| <a name="input_test_vms"></a> [test\_vms](#input\_test\_vms) | A map defining test VMs.<br><br>Values contain the following elements:<br><br>- `name`: a name of the VM<br>- `vnet_key`: a key describing a VNET defined in `var.vnets`<br>- `subnet_key`: a key describing a subnet found in a VNET definition | <pre>map(object({<br> name = string<br> vnet_key = string<br> subnet_key = string<br> }))</pre> | `{}` | no | -| <a name="input_bastions"></a> [bastions](#input\_bastions) | A map containing Azure Bastion definitions.<br><br>This map follows resource definition convention, following values are available:<br>- `name`: Bastion name<br>- `vnet_key`: a key describing a VNET defined in `var.vnets`. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft).<br>- `subnet_key`: a key pointing to a subnet dedicated to a Bastion deployment (the name should be `AzureBastionSubnet`.) | <pre>map(object({<br> name = string<br> vnet_key = string<br> subnet_key = string<br> }))</pre> | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| <a name="output_username"></a> [username](#output\_username) | Test VMs admin account. | -| <a name="output_password"></a> [password](#output\_password) | Password for the admin user. | -| <a name="output_vm_private_ips"></a> [vm\_private\_ips](#output\_vm\_private\_ips) | A map of private IPs assigned to test VMs. | -<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> + +## 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. +[`hub_resource_group_name`](#hub_resource_group_name) | `string` | Name of the Resource Group hosting the hub/transit infrastructure. +[`hub_vnet_name`](#hub_vnet_name) | `string` | Name of the hub/transit VNET. +[`vm_size`](#vm_size) | `string` | Azure test VM size. +[`username`](#username) | `string` | Name of the VM admin account. +[`password`](#password) | `string` | A password for the admin account. +[`test_vms`](#test_vms) | `map` | A map defining test VMs. +[`bastions`](#bastions) | `map` | A map containing Azure Bastion definitions. + + + +## Module's Outputs + +Name | Description +--- | --- +`username` | Test VMs admin account. +`password` | Password for the admin user. +`vm_private_ips` | A map of private IPs assigned to test VMs. + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet_peering` | - | ../../modules/vnet_peering | + + +Resources used in this module: + +- `bastion_host` (managed) +- `linux_virtual_machine` (managed) +- `network_interface` (managed) +- `public_ip` (managed) +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### 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 `true`) 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`, optional, 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 + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = optional(string) + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### 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: +``` +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: `` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### hub_resource_group_name + +Name of the Resource Group hosting the hub/transit infrastructure. This value is required to create peering between the spoke and the hub VNET. + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### hub_vnet_name + +Name of the hub/transit VNET. This value is required to create peering between the spoke and the hub VNET. + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### vm_size + +Azure test VM size. + +Type: string + +Default value: `Standard_D1_v2` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### username + +Name of the VM admin account. + +Type: string + +Default value: `panadmin` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### password + +A password for the admin account. + +Type: string + +Default value: `&{}` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_vms + +A map defining test VMs. + +Values contain the following elements: + +- `name`: a name of the VM +- `vnet_key`: a key describing a VNET defined in `var.vnets` +- `subnet_key`: a key describing a subnet found in a VNET definition + + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### bastions + +A map containing Azure Bastion definitions. + +This map follows resource definition convention, following values are available: +- `name`: Bastion name +- `vnet_key`: a key describing a VNET defined in `var.vnets`. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). +- `subnet_key`: a key pointing to a subnet dedicated to a Bastion deployment (the name should be `AzureBastionSubnet`.) + + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/test_infrastructure/versions.tf b/examples/test_infrastructure/versions.tf index 5a64cf60..14f2e802 100644 --- a/examples/test_infrastructure/versions.tf +++ b/examples/test_infrastructure/versions.tf @@ -1,5 +1,5 @@ terraform { - required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index df4acc39..1aa148b5 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -35,6 +35,9 @@ Name | Description ## Module's Nameplate +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 Providers used in this module: @@ -307,4 +310,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/versions.tf b/examples/virtual_network_gateway/versions.tf index 49009426..85620563 100644 --- a/examples/virtual_network_gateway/versions.tf +++ b/examples/virtual_network_gateway/versions.tf @@ -1,5 +1,5 @@ terraform { - # required_version = ">= 1.2, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" diff --git a/modules/appgw/.header.md b/modules/appgw/.header.md index ce2014ee..cef1efb9 100644 --- a/modules/appgw/.header.md +++ b/modules/appgw/.header.md @@ -10,7 +10,7 @@ Then you can use below code as an example of calling module to create Applicatio ```hcl # Create Application Gateay module "appgw" { - source = "../../modules/appgw" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/appgw" for_each = var.appgws diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 515035c3..c7d58039 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -9,7 +9,8 @@ In order to use module `appgw`, you need to deploy `azurerm_resource_group` and Then you can use below code as an example of calling module to create Application Gateway: ```hcl -module "Application Gateway" { +# Create Application Gateay +module "appgw" { source = "PaloAltoNetworks/swfw-modules/azurerm//modules/appgw" for_each = var.appgws @@ -1486,4 +1487,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/.header.md b/modules/bootstrap/.header.md index f2891779..db19a638 100644 --- a/modules/bootstrap/.header.md +++ b/modules/bootstrap/.header.md @@ -27,7 +27,7 @@ The module is used only to create a Storage Account with module defaults where p ```hcl module "empty_storage" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" name = "someemptystorage" resource_group_name = "rg-name" @@ -48,7 +48,7 @@ This code will create a storage account for 3 NGFWs. Please **note** that: ```hcl module "bootstrap" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" name = "samplebootstrapstorage" resource_group_name = "rg-name" @@ -96,7 +96,7 @@ structure already present. ```hcl module "existing_storage" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" storage_account = { create = false diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index eb645749..e3be44f5 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -28,7 +28,7 @@ The module is used only to create a Storage Account with module defaults where p ```hcl module "empty_storage" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" name = "someemptystorage" resource_group_name = "rg-name" @@ -97,7 +97,7 @@ structure already present. ```hcl module "existing_storage" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" storage_account = { create = false @@ -163,12 +163,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -403,4 +403,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/versions.tf b/modules/bootstrap/versions.tf index 2c796798..9abec711 100644 --- a/modules/bootstrap/versions.tf +++ b/modules/bootstrap/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index 2e2c0df4..d4c572e0 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -324,4 +324,5 @@ Default value: `map[name:lb_rule]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md index eac7e1a6..39153098 100644 --- a/modules/loadbalancer/.header.md +++ b/modules/loadbalancer/.header.md @@ -27,7 +27,7 @@ Example of a private Load Balancer with HA ports rule: ```hcl module "lbi" { - source = "../../modules/loadbalancer" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "private-lb" location = "West Europe" @@ -59,7 +59,7 @@ Example of a private Load Balancer with a single rule for port `80`: ```hcl module "lbe" { - source = "../../modules/loadbalancer" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "public-lb" location = "West Europe" diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 3e53bfb2..d560e521 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -28,7 +28,7 @@ Example of a private Load Balancer with HA ports rule: ```hcl module "lbi" { - source = "../../modules/loadbalancer" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "private-lb" location = "West Europe" @@ -60,7 +60,7 @@ Example of a private Load Balancer with a single rule for port `80`: ```hcl module "lbe" { - source = "../../modules/loadbalancer" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "public-lb" location = "West Europe" @@ -118,13 +118,13 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 -- `azurerm`, version: ~> 3.25 +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -461,4 +461,5 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/versions.tf b/modules/loadbalancer/versions.tf index 8dc5c1eb..9abec711 100644 --- a/modules/loadbalancer/versions.tf +++ b/modules/loadbalancer/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.3, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/name_templater/.header.md b/modules/name_templater/.header.md index 348dcd82..0646aeb7 100644 --- a/modules/name_templater/.header.md +++ b/modules/name_templater/.header.md @@ -14,7 +14,7 @@ A simple module invocation might look like the following: ```hcl module "name_templates" { - source = "../../modules/name_templater" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/name_templater" resource_type = "vnet" name_template = { diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index 2f032626..b5ed85bd 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -14,7 +14,7 @@ A simple module invocation might look like the following: ```hcl module "name_templates" { - source = "../../modules/name_templater" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/name_templater" resource_type = "vnet" name_template = { @@ -196,4 +196,5 @@ Default value: `map[application_gw:agw application_insights:appi availability_se <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/.header.md b/modules/natgw/.header.md index a48e0336..b811f989 100644 --- a/modules/natgw/.header.md +++ b/modules/natgw/.header.md @@ -26,7 +26,7 @@ and Subnets): ```hcl module "natgw" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/natgw" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/natgw" name = "NATGW_name" resource_group_name = "resource_group_name" diff --git a/modules/natgw/README.md b/modules/natgw/README.md index 7ae8b59c..07465c40 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -75,12 +75,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -314,4 +314,5 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/versions.tf b/modules/natgw/versions.tf index 2c796798..9abec711 100644 --- a/modules/natgw/versions.tf +++ b/modules/natgw/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/ngfw_metrics/.header.md b/modules/ngfw_metrics/.header.md index 53ab9b60..67e6aa36 100644 --- a/modules/ngfw_metrics/.header.md +++ b/modules/ngfw_metrics/.header.md @@ -41,7 +41,7 @@ The following snippet deploys Log Analytics Workspace and two Application Insigh ```hcl module "ngfw_metrics" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/ngfw_metrics" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/ngfw_metrics" name = "ngfw-law" resource_group_name = "ngfw-rg" diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index 269c8b1a..5cec3721 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -42,7 +42,7 @@ The following snippet deploys Log Analytics Workspace and two Application Insigh ```hcl module "ngfw_metrics" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/ngfw_metrics" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/ngfw_metrics" name = "ngfw-law" resource_group_name = "ngfw-rg" @@ -224,4 +224,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/README.md b/modules/panorama/README.md index b3f781c4..681ffb28 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -367,4 +367,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_machine/versions.tf b/modules/virtual_machine/versions.tf index a733704d..c7587464 100644 --- a/modules/virtual_machine/versions.tf +++ b/modules/virtual_machine/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/virtual_network_gateway/.header.md b/modules/virtual_network_gateway/.header.md index f9dcf37a..1e087b58 100644 --- a/modules/virtual_network_gateway/.header.md +++ b/modules/virtual_network_gateway/.header.md @@ -10,7 +10,7 @@ Then you can use below code as an example of calling module to create VNG: ```hcl module "vng" { - source = "../../modules/virtual_network_gateway" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/virtual_network_gateway" for_each = var.virtual_network_gateways diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index eba170a6..92ae788c 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -11,7 +11,7 @@ Then you can use below code as an example of calling module to create VNG: ```hcl module "vng" { - source = "../../modules/virtual_network_gateway" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/virtual_network_gateway" for_each = var.virtual_network_gateways @@ -406,7 +406,7 @@ Type: string An ID of a Subnet in which the Virtual Network Gateway will be created. -This has to be a dedicated Subnet names `GatewaySubnet`. +This has to be a dedicated Subnet named `GatewaySubnet`. Type: string @@ -814,4 +814,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 5d21468b..3b2b93d1 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -67,12 +67,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -356,4 +356,5 @@ Default value: `map[]` + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/versions.tf b/modules/vmseries/versions.tf index 2c796798..9abec711 100644 --- a/modules/vmseries/versions.tf +++ b/modules/vmseries/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/vmss/.header.md b/modules/vmss/.header.md index c5da546c..d52efd8f 100644 --- a/modules/vmss/.header.md +++ b/modules/vmss/.header.md @@ -60,7 +60,7 @@ Below you can find a simple example deploying a Scale Set w/o autoscaling, using ```hcl module "vmss" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmss" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/vmss" name = "ngfw-vmss" resource_group_name = "hub-rg" diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 29e0736f..8e004880 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -648,4 +648,5 @@ Default value: `[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 7f481692..8bb92e2a 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -161,13 +161,13 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 -- `azurerm`, version: ~> 3.25 +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 @@ -534,4 +534,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/versions.tf b/modules/vnet/versions.tf index 8dc5c1eb..9abec711 100644 --- a/modules/vnet/versions.tf +++ b/modules/vnet/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.3, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 23a16161..26dcb618 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -45,13 +45,13 @@ Name | Description Requirements needed by this module: -- `terraform`, version: >= 1.3, < 2.0 -- `azurerm`, version: ~> 3.25 +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 Providers used in this module: -- `azurerm`, version: ~> 3.25 +- `azurerm`, version: ~> 3.80 diff --git a/modules/vnet_peering/versions.tf b/modules/vnet_peering/versions.tf index 48646533..6be9475c 100644 --- a/modules/vnet_peering/versions.tf +++ b/modules/vnet_peering/versions.tf @@ -1,9 +1,9 @@ terraform { - required_version = ">= 1.3, < 2.0" + required_version = ">= 1.5, < 2.0" required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.25" + version = "~> 3.80" } } } \ No newline at end of file From 4d388b0e3897dafdebb5a721050fc813fe7ea55f Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:00:10 +0100 Subject: [PATCH 24/49] refactor: code style unification (#24) --- examples/appgw/README.md | 8 +- examples/appgw/example.tfvars | 7 +- examples/appgw/main.tf | 20 +- examples/appgw/variables.tf | 4 +- examples/common_vmseries/README.md | 459 +++++------ examples/common_vmseries/example.tfvars | 111 +-- examples/common_vmseries/main.tf | 169 ++-- examples/common_vmseries/variables.tf | 769 +++++++++--------- .../common_vmseries_and_autoscale/README.md | 269 +++--- .../example.tfvars | 18 +- .../common_vmseries_and_autoscale/main.tf | 83 +- .../variables.tf | 551 +++++++------ examples/dedicated_vmseries/README.md | 459 +++++------ examples/dedicated_vmseries/example.tfvars | 27 +- examples/dedicated_vmseries/main.tf | 169 ++-- examples/dedicated_vmseries/variables.tf | 769 +++++++++--------- .../README.md | 269 +++--- .../example.tfvars | 20 +- .../dedicated_vmseries_and_autoscale/main.tf | 83 +- .../variables.tf | 549 +++++++------ examples/gwlb_with_vmseries/README.md | 463 ++++++----- examples/gwlb_with_vmseries/example.tfvars | 34 +- examples/gwlb_with_vmseries/main.tf | 56 +- examples/gwlb_with_vmseries/variables.tf | 467 ++++++----- examples/standalone_panorama/README.md | 169 ++-- examples/standalone_panorama/example.tfvars | 12 +- examples/standalone_panorama/main.tf | 27 +- examples/standalone_panorama/outputs.tf | 2 +- examples/standalone_panorama/variables.tf | 159 ++-- examples/standalone_vmseries/README.md | 459 +++++------ examples/standalone_vmseries/example.tfvars | 12 +- examples/standalone_vmseries/main.tf | 172 ++-- examples/standalone_vmseries/variables.tf | 769 +++++++++--------- examples/virtual_network_gateway/README.md | 80 +- .../virtual_network_gateway/example.tfvars | 8 +- examples/virtual_network_gateway/main.tf | 18 +- examples/virtual_network_gateway/outputs.tf | 2 +- examples/virtual_network_gateway/variables.tf | 67 +- modules/appgw/main.tf | 24 +- modules/bootstrap/README.md | 2 +- modules/bootstrap/main.tf | 14 +- modules/gwlb/outputs.tf | 2 +- modules/loadbalancer/README.md | 13 +- modules/loadbalancer/main.tf | 41 +- modules/loadbalancer/outputs.tf | 5 +- modules/loadbalancer/variables.tf | 9 +- modules/name_templater/main.tf | 2 +- modules/name_templater/outputs.tf | 2 +- modules/ngfw_metrics/main.tf | 10 +- modules/panorama/README.md | 1 + modules/panorama/outputs.tf | 4 +- modules/virtual_machine/main.tf | 6 +- modules/virtual_machine/outputs.tf | 13 +- modules/virtual_machine/variables.tf | 92 ++- modules/virtual_network_gateway/outputs.tf | 2 +- modules/vmseries/README.md | 12 +- modules/vmseries/main.tf | 2 +- modules/vmseries/outputs.tf | 8 +- modules/vmseries/variables.tf | 10 +- modules/vmss/README.md | 2 +- modules/vmss/main.tf | 125 ++- modules/vmss/outputs.tf | 2 +- modules/vnet_peering/README.md | 1 - modules/vnet_peering/main.tf | 2 +- modules/vnet_peering/outputs.tf | 2 +- modules/vnet_peering/variables.tf | 2 +- modules/vnet_peering/versions.tf | 2 +- 67 files changed, 4261 insertions(+), 3940 deletions(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 08f5d4db..bb4eb7f1 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -42,8 +42,8 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. -`appgw` | - | ../../modules/appgw | Create Application Gateway +`vnet` | - | ../../modules/vnet | +`appgw` | - | ../../modules/appgw | Resources used in this module: @@ -349,7 +349,8 @@ Example: 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, +**Note!** \ +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. @@ -375,5 +376,4 @@ Default value: `true` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars index ffd4a7fb..efbe70e7 100644 --- a/examples/appgw/example.tfvars +++ b/examples/appgw/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "appgw-example" name_prefix = "fosix-" @@ -7,8 +8,8 @@ tags = { "CreatedWith" = "Terraform" } +### NETWORK ### -# --- VNET PART --- # vnets = { transit = { name = "transit" @@ -36,7 +37,7 @@ vnets = { } } -# --- APPGW PART --- # +### LOAD BALANCING ### appgws = { "public-empty" = { diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf index 1bde6a85..52e4fd39 100644 --- a/examples/appgw/main.tf +++ b/examples/appgw/main.tf @@ -1,4 +1,5 @@ -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -18,7 +19,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Create public IP in order to reuse it in 1 of the application gateways +### Create a public IP in order to reuse it in one of the Application Gateways ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { name = "pip-existing" @@ -31,7 +33,8 @@ resource "azurerm_public_ip" "this" { tags = var.tags } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -47,15 +50,18 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } -# Create Application Gateway +### Create Application Gateways ### + module "appgw" { source = "../../modules/appgw" @@ -90,4 +96,4 @@ module "appgw" { tags = var.tags depends_on = [module.vnet, azurerm_public_ip.this] -} \ No newline at end of file +} diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf index 796b38b0..3db178a9 100644 --- a/examples/appgw/variables.tf +++ b/examples/appgw/variables.tf @@ -21,7 +21,8 @@ variable "name_prefix" { 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, + **Note!** \ + 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 = "" @@ -68,7 +69,6 @@ variable "vnets" { - `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) diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 2b5fc0e5..0c1649b1 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -171,8 +171,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -181,11 +181,11 @@ Name | Type | Description 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. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -223,13 +223,13 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `natgw` | - | ../../modules/natgw | -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`load_balancer` | - | ../../modules/loadbalancer | +`appgw` | - | ../../modules/appgw | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | -`appgw` | - | ../../modules/appgw | Resources used in this module: @@ -246,46 +246,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -340,10 +339,6 @@ map(object({ - - - - #### appgws A map defining all Application Gateways in the current deployment. @@ -362,25 +357,25 @@ Below you can find a brief list of most important properties: described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. @@ -517,18 +512,11 @@ map(object({ -### Optional Inputs - - -#### tags -Map of tags to assign to the created resources. -Type: map(string) -Default value: `map[]` -<sup>[back to list](#modules-optional-inputs)</sup> +### Optional Inputs #### name_prefix @@ -569,6 +557,17 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### natgws A map defining NAT Gateways. @@ -578,21 +577,21 @@ explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: -- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. -- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. -- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). -- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. -- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. -- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. -- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. -- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. -- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -614,13 +613,13 @@ Type: ```hcl map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -642,7 +641,7 @@ Default value: `map[]` #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -652,26 +651,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -705,10 +707,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -737,17 +739,20 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### availability_sets A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: -- `name` - name of the Application Insights. -- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). -- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). -Please verify how many update and fault domain are supported in a region before deploying this resource. +- `name` - (`string`, required) name of the Application Insights. +- `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. +- `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + +**Note!** \ +Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability +Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. Type: @@ -755,8 +760,8 @@ Type: ```hcl map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) ``` @@ -772,22 +777,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -816,43 +821,52 @@ You can create or re-use an existing Storage Account and/or File Share. For deta - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. -- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. -- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. -- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - Type: ```hcl @@ -895,129 +909,121 @@ Default value: `map[]` #### vmseries -A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - -- `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - -- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). -- `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. +- `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - The most important ones are listed below: + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. +- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -1058,7 +1064,6 @@ map(object({ identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -1077,6 +1082,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 3dcf6c81..5bdd7084 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "transit-vnet-common" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -123,8 +125,8 @@ vnets = { } } +### LOAD BALANCING ### -# --- LOAD BALANCING PART --- # load_balancers = { "public" = { name = "public-lb" @@ -168,10 +170,59 @@ load_balancers = { } } -# --- VMSERIES PART --- # +appgws = { + public = { + name = "appgw" + vnet_key = "transit" + subnet_key = "appgw" + public_ip = { + name = "appgw-pip" + } + listeners = { + "http" = { + name = "http" + port = 80 + } + } + backend_settings = { + http = { + name = "http" + port = 80 + protocol = "Http" + } + } + rewrites = { + xff = { + name = "XFF-set" + rules = { + "xff-strip-port" = { + name = "xff-strip-port" + sequence = 100 + request_headers = { + "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + } + } + } + } + } + rules = { + "http" = { + name = "http" + listener_key = "http" + backend_key = "http" + rewrite_key = "xff" + priority = 1 + } + } + } +} + +### VM-SERIES ### + vmseries = { "fw-1" = { - name = "firewall01" + name = "firewall01" + vnet_key = "transit" image = { version = "10.2.3" } @@ -180,7 +231,6 @@ vmseries = { zone = 1 bootstrap_options = "type=dhcp-client" } - vnet_key = "transit" interfaces = [ { name = "vm01-mgmt" @@ -233,52 +283,3 @@ vmseries = { ] } } - - -# --- APPLICATION GATEWAYs --- # -appgws = { - public = { - name = "appgw" - vnet_key = "transit" - subnet_key = "appgw" - public_ip = { - name = "appgw-pip" - } - listeners = { - "http" = { - name = "http" - port = 80 - } - } - backend_settings = { - http = { - name = "http" - port = 80 - protocol = "Http" - } - } - rewrites = { - xff = { - name = "XFF-set" - rules = { - "xff-strip-port" = { - name = "xff-strip-port" - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } - } - rules = { - "http" = { - name = "http" - listener_key = "http" - backend_key = "http" - rewrite_key = "xff" - priority = 1 - } - } - } -} diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index ecee2c8d..29fd01d5 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -26,7 +27,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -46,7 +48,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -62,15 +65,16 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } - module "natgw" { source = "../../modules/natgw" @@ -84,15 +88,19 @@ module "natgw" { idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + public_ip = try(merge(each.value.public_ip, { + name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" + }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { + name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" + }), null) tags = var.tags depends_on = [module.vnet] } +### Create Load Balancers, both internal and external ### -# create load balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -109,7 +117,8 @@ module "load_balancer" { 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}", + "${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( @@ -137,9 +146,65 @@ module "load_balancer" { depends_on = [module.vnet] } +### Create Application Gateways ### + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules + + tags = var.tags + depends_on = [module.vnet, module.vmseries] +} +### Create VM-Series VMs and closely associated resources ### -# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -147,9 +212,11 @@ module "ngfw_metrics" { create_workspace = var.ngfw_metrics.create_workspace - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -161,6 +228,7 @@ module "ngfw_metrics" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : @@ -287,8 +355,10 @@ module "vmseries" { coalesce( each.value.virtual_machine.bootstrap_options, join(",", [ - "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", "file-share=${each.key}", "share-directory=None" ]), @@ -299,10 +369,12 @@ module "vmseries" { ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_public_ip = v.create_public_ip - public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${ + var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip") + }" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address attach_to_lb_backend_pool = v.load_balancer_key != null @@ -318,60 +390,3 @@ module "vmseries" { module.bootstrap, ] } - -# Create Application Gateway - -locals { - nics_with_appgw_key = flatten([ - for k, v in var.vmseries : [ - for nic in v.interfaces : { - vm_key = k - nic_name = nic.name - appgw_key = nic.application_gateway_key - } if nic.application_gateway_key != null - ]]) - - ips_4_nics_with_appgw_key = { - for v in local.nics_with_appgw_key : - v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... - } -} - -module "appgw" { - source = "../../modules/appgw" - - for_each = var.appgws - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name - location = var.location - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - - zones = each.value.zones - public_ip = merge( - each.value.public_ip, - { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } - ) - domain_name_label = each.value.domain_name_label - capacity = each.value.capacity - enable_http2 = each.value.enable_http2 - waf = each.value.waf - managed_identities = each.value.managed_identities - global_ssl_policy = each.value.global_ssl_policy - ssl_profiles = each.value.ssl_profiles - frontend_ip_configuration_name = each.value.frontend_ip_configuration_name - listeners = each.value.listeners - backend_pool = merge( - each.value.backend_pool, - length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } - ) - backend_settings = each.value.backend_settings - probes = each.value.probes - rewrites = each.value.rewrites - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - rules = each.value.rules - - tags = var.tags - depends_on = [module.vnet, module.vmseries] -} diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 9057e006..8a26966e 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -45,33 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -125,21 +123,21 @@ variable "natgws" { For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -158,13 +156,13 @@ variable "natgws" { EOF default = {} type = map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -179,12 +177,11 @@ variable "natgws" { })) } +### LOAD BALANCING ### - -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -194,26 +191,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -245,10 +245,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -272,29 +272,195 @@ variable "load_balancers" { })) } +variable "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. + + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + EOF + type = map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +} + +### VM-SERIES ### variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: - - `name` - name of the Application Insights. - - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). - Please verify how many update and fault domain are supported in a region before deploying this resource. + - `name` - (`string`, required) name of the Application Insights. + - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. + - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + + **Note!** \ + Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability + Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} nullable = false type = map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) } - variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -302,22 +468,22 @@ variable "ngfw_metrics" { When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: - - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. EOF default = null type = object({ @@ -338,41 +504,50 @@ variable "bootstrap_storages" { - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. - - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. - - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - EOF default = {} nullable = false @@ -410,127 +585,119 @@ variable "bootstrap_storages" { variable "vmseries" { description = <<-EOF - A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + - `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - The most important ones are listed below: + - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). EOF default = {} nullable = false type = map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -571,7 +738,6 @@ variable "vmseries" { identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -583,185 +749,26 @@ variable "vmseries" { application_gateway_key = optional(string) })) })) - validation { + validation { # virtual_machine.bootstrap_options & virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null ]) - error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + error_message = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF } - validation { + validation { # virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : - v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true - if v.virtual_machine.bootstrap_package != null + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? ( + v.virtual_machine.bootstrap_package.private_snet_key != null && + v.virtual_machine.bootstrap_package.public_snet_key != null + ) : true if v.virtual_machine.bootstrap_package != null ]) - error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF } } - -### Application Gateway -variable "appgws" { - description = <<-EOF - A map defining all Application Gateways in the current deployment. - - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, - refer to [module documentation](../../modules/appgw/README.md). - - **Note!** \ - The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). - It represents the Rules section of an Application Gateway in Azure Portal. - - Below you can find a brief list of most important properties: - - - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. - - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. - - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. - - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal - deployment. - - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. - - `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. - - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend - will be created. - - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - EOF - type = map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -} diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 3009e858..f4e4f45c 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -202,8 +202,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -212,11 +212,11 @@ Name | Type | Description 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. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. @@ -248,11 +248,11 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `natgw` | - | ../../modules/natgw | -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `vmss` | - | ../../modules/vmss | @@ -268,46 +268,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -362,8 +361,6 @@ map(object({ - - #### appgws A map defining all Application Gateways in the current deployment. @@ -382,25 +379,25 @@ Below you can find a brief list of most important properties: described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. @@ -537,18 +534,9 @@ map(object({ -### Optional Inputs - -#### tags -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> +### Optional Inputs #### name_prefix @@ -589,6 +577,17 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### natgws A map defining NAT Gateways. @@ -598,21 +597,21 @@ explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: -- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. -- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. -- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). -- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. -- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. -- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. -- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. -- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. -- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -634,13 +633,13 @@ Type: ```hcl map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -662,7 +661,7 @@ Default value: `map[]` #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -672,26 +671,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -725,10 +727,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -757,6 +759,7 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### ngfw_metrics A map controlling metrics-relates resources. @@ -764,22 +767,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -807,7 +810,10 @@ For details and defaults for available options please refer to the [`vmss`](../. The basic Scale Set configuration properties are as follows: -- `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` +- `name` - (`string`, required) name of the scale set, will be prefixed with the value of + `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts + subnets used to deploy network interfaces for VMs in this Scale Set. - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and @@ -817,74 +823,70 @@ The basic Scale Set configuration properties are as follows: The `disable_password_authentication` property is by default true. When using this value you have to specify at least one SSH key. You can however set this property to `true`. Then you have 2 options, either: - - do not specify anything else - a random password will be generated for you + - do not specify anything else, a random password will be generated for you. - specify at least one of `password` or `ssh_keys` properties. - For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). -- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. +- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. The + `image` property is required but there are only 2 properties (mutually exclusive) that have to + be set up, either: - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#image). - For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). +- `virtual_machine_scale_set` - (`map`, optional, defaults to module default) a map that groups most common Scale Set + configuration options: -- `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set - configuration options. + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zones` - (`list`, optional, defaults to module default) a list of Availability Zones in which VMs from + this Scale Set will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values). + - `bootstrap_options` - (`string`, optional, defaults to module default) bootstrap options to pass to VM-Series instance. - Below we present only the most important ones, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported - - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from - this Scale Set will be created - - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series - instance + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set). - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not - the scaling profiles (metrics thresholds, etc) + the scaling profiles (metrics, thresholds, etc.). Most common properties are: + + - `default_count` - (`number`, optional, defaults to module default) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to + compare the metrics to the thresholds. - Below we present only the most important properties, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in - the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare - the metrics to the thresholds +- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the + 1<sup>st</sup> interface should be the management one. Following properties are available: -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set -- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. Following properties are available: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets` - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module default) create Public IP for an interface. - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the - `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancer's backend pool + `var.loadbalancers` variable, network interface that has this property defined will be added to + the Load Balancer's backend pool. - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the `var.appgws`, network interface that has this property defined will be added to the Application - Gateways's backend pool + Gateways's backend pool. - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label - for each VM instance - -- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available - configuration please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) + for each VM instance. +- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + properties please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = object({ username = optional(string) password = optional(string) @@ -922,7 +924,6 @@ map(object({ notification_emails = optional(list(string), []) webhooks_uris = optional(map(string), {}) }), {}) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -974,6 +975,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 1a9cc94b..7edeba58 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "autoscale-common" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -123,8 +125,8 @@ vnets = { } } +### LOAD BALANCING ### -# --- LOAD BALANCING PART --- # load_balancers = { "public" = { name = "public-lb" @@ -168,9 +170,6 @@ load_balancers = { } } - - -# --- APPLICATION GATEWAYs --- # appgws = { public = { name = "appgw" @@ -218,14 +217,16 @@ appgws = { } } -# --- VMSERIES PART --- # +### VM-SERIES ### + ngfw_metrics = { name = "ngwf-log-analytics-wrksp" } scale_sets = { common = { - name = "common-vmss" + name = "common-vmss" + vnet_key = "transit" image = { version = "10.2.4" } @@ -239,7 +240,6 @@ scale_sets = { autoscaling_configuration = { default_count = 1 } - vnet_key = "transit" interfaces = [ { name = "management" diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 9f208f66..151af28c 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -27,7 +28,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -47,7 +49,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -63,9 +66,11 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags @@ -84,14 +89,19 @@ module "natgw" { idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + public_ip = try(merge(each.value.public_ip, { + name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" + }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { + name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" + }), null) tags = var.tags depends_on = [module.vnet] } -# create load balancers, both internal and external +### Create Load Balancers, both internal and external ### + module "load_balancer" { source = "../../modules/loadbalancer" @@ -108,7 +118,8 @@ module "load_balancer" { 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}", + "${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( @@ -136,30 +147,7 @@ module "load_balancer" { depends_on = [module.vnet] } -module "ngfw_metrics" { - source = "../../modules/ngfw_metrics" - - count = var.ngfw_metrics != null ? 1 : 0 - - create_workspace = var.ngfw_metrics.create_workspace - - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location - - log_analytics_workspace = { - sku = var.ngfw_metrics.sku - metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days - } - - application_insights = { - for k, v in var.scale_sets : - k => { name = "${var.name_prefix}${v.name}-ai" } - if length(v.autoscaling_profiles) > 0 - } - - tags = var.tags -} +### Create Application Gateways ### module "appgw" { source = "../../modules/appgw" @@ -197,6 +185,35 @@ module "appgw" { depends_on = [module.vnet] } +### Create VM-Series VM Scale Sets and closely associated resources ### + +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" + + count = var.ngfw_metrics != null ? 1 : 0 + + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { + for k, v in var.scale_sets : + k => { name = "${var.name_prefix}${v.name}-ai" } + if length(v.autoscaling_profiles) > 0 + } + + tags = var.tags +} + module "vmss" { source = "../../modules/vmss" diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index c085b3f2..a18c7c0f 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -45,33 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -125,21 +123,21 @@ variable "natgws" { For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -158,13 +156,13 @@ variable "natgws" { EOF default = {} type = map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -179,12 +177,11 @@ variable "natgws" { })) } +### LOAD BALANCING ### - -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -194,26 +191,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -245,10 +245,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -272,213 +272,6 @@ variable "load_balancers" { })) } -variable "ngfw_metrics" { - description = <<-EOF - A map controlling metrics-relates resources. - - When set to explicit `null` (default) it will disable any metrics resources in this deployment. - - When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - - All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - - Following properties are available: - - - `name` - (`string`, required) name of the (common) Log Analytics Workspace - - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace - - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. - EOF - default = null - type = object({ - name = string - create_workspace = optional(bool, true) - resource_group_name = optional(string) - sku = optional(string) - metrics_retention_in_days = optional(number) - }) -} - -### VMSERIES - -variable "scale_sets" { - description = <<-EOF - A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. - - For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. - - The basic Scale Set configuration properties are as follows: - - - `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` - - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. - - This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and - available in the Terraform outputs. - - **Note!** \ - The `disable_password_authentication` property is by default true. When using this value you have to specify at least one - SSH key. You can however set this property to `true`. Then you have 2 options, either: - - - do not specify anything else - a random password will be generated for you - - specify at least one of `password` or `ssh_keys` properties. - - For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image - - For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). - - - `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set - configuration options. - - Below we present only the most important ones, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported - - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from - this Scale Set will be created - - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series - instance - - - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not - the scaling profiles (metrics thresholds, etc) - - Below we present only the most important properties, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in - the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare - the metrics to the thresholds - - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set - - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. Following properties are available: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets` - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the - `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancer's backend pool - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the - `var.appgws`, network interface that has this property defined will be added to the Application - Gateways's backend pool - - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label - for each VM instance - - - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available - configuration please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) - - EOF - default = {} - nullable = false - type = map(object({ - name = string - authentication = object({ - username = optional(string) - password = optional(string) - disable_password_authentication = optional(bool, true) - ssh_keys = optional(list(string), []) - }) - image = object({ - version = optional(string) - publisher = optional(string) - offer = optional(string) - sku = optional(string) - enable_marketplace_plan = optional(bool) - custom_id = optional(string) - }) - virtual_machine_scale_set = optional(object({ - size = optional(string) - bootstrap_options = optional(string) - zones = optional(list(string)) - disk_type = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool) - platform_fault_domain_count = optional(number) - disk_encryption_set_id = optional(string) - enable_boot_diagnostics = optional(bool, true) - boot_diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) - })) - autoscaling_configuration = optional(object({ - default_count = optional(number) - scale_in_policy = optional(string) - scale_in_force_deletion = optional(bool) - notification_emails = optional(list(string), []) - webhooks_uris = optional(map(string), {}) - }), {}) - vnet_key = string - interfaces = list(object({ - name = string - subnet_key = string - create_public_ip = optional(bool) - load_balancer_key = optional(string) - application_gateway_key = optional(string) - pip_domain_name_label = optional(string) - })) - autoscaling_profiles = optional(list(object({ - name = string - minimum_count = optional(number) - default_count = number - maximum_count = optional(number) - recurrence = optional(object({ - timezone = optional(string) - days = list(string) - start_time = string - end_time = string - })) - scale_rules = optional(list(object({ - name = string - scale_out_config = object({ - threshold = number - operator = optional(string) - grain_window_minutes = number - grain_aggregation_type = optional(string) - aggregation_window_minutes = number - aggregation_window_type = optional(string) - cooldown_window_minutes = number - change_count_by = optional(number) - }) - scale_in_config = object({ - threshold = number - operator = optional(string) - grain_window_minutes = optional(number) - grain_aggregation_type = optional(string) - aggregation_window_minutes = optional(number) - aggregation_window_type = optional(string) - cooldown_window_minutes = number - change_count_by = optional(number) - }) - })), []) - })), []) - })) -} - - - -### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. @@ -497,25 +290,25 @@ variable "appgws" { described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. - - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + - `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. - - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + - `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF @@ -641,4 +434,206 @@ variable "appgws" { redirect_key = optional(string) })) })) -} \ No newline at end of file +} + +### VM-SERIES ### + +variable "ngfw_metrics" { + description = <<-EOF + A map controlling metrics-relates resources. + + When set to explicit `null` (default) it will disable any metrics resources in this deployment. + + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. + + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + + Following properties are available: + + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace. + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. + EOF + default = null + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +} + +variable "scale_sets" { + description = <<-EOF + A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + + For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. + + The basic Scale Set configuration properties are as follows: + + - `name` - (`string`, required) name of the scale set, will be prefixed with the value of + `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts + subnets used to deploy network interfaces for VMs in this Scale Set. + - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. + + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. + + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: + + - do not specify anything else, a random password will be generated for you. + - specify at least one of `password` or `ssh_keys` properties. + + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. The + `image` property is required but there are only 2 properties (mutually exclusive) that have to + be set up, either: + + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#image). + + - `virtual_machine_scale_set` - (`map`, optional, defaults to module default) a map that groups most common Scale Set + configuration options: + + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zones` - (`list`, optional, defaults to module default) a list of Availability Zones in which VMs from + this Scale Set will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values). + - `bootstrap_options` - (`string`, optional, defaults to module default) bootstrap options to pass to VM-Series instance. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set). + + - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics, thresholds, etc.). Most common properties are: + + - `default_count` - (`number`, optional, defaults to module default) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to + compare the metrics to the thresholds. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the + 1<sup>st</sup> interface should be the management one. Following properties are available: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module default) create Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be added to + the Load Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool. + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance. + + - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + properties please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles). + EOF + default = {} + nullable = false + type = map(object({ + name = string + vnet_key = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) +} diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 229cc229..2f6c85b9 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -175,8 +175,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -185,11 +185,11 @@ Name | Type | Description 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. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -227,13 +227,13 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `natgw` | - | ../../modules/natgw | -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`load_balancer` | - | ../../modules/loadbalancer | +`appgw` | - | ../../modules/appgw | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | -`appgw` | - | ../../modules/appgw | Resources used in this module: @@ -250,46 +250,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -344,10 +343,6 @@ map(object({ - - - - #### appgws A map defining all Application Gateways in the current deployment. @@ -366,25 +361,25 @@ Below you can find a brief list of most important properties: described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. @@ -521,18 +516,11 @@ map(object({ -### Optional Inputs - - -#### tags -Map of tags to assign to the created resources. -Type: map(string) -Default value: `map[]` -<sup>[back to list](#modules-optional-inputs)</sup> +### Optional Inputs #### name_prefix @@ -573,6 +561,17 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### natgws A map defining NAT Gateways. @@ -582,21 +581,21 @@ explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: -- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. -- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. -- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). -- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. -- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. -- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. -- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. -- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. -- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -618,13 +617,13 @@ Type: ```hcl map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -646,7 +645,7 @@ Default value: `map[]` #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -656,26 +655,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -709,10 +711,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -741,17 +743,20 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### availability_sets A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: -- `name` - name of the Application Insights. -- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). -- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). -Please verify how many update and fault domain are supported in a region before deploying this resource. +- `name` - (`string`, required) name of the Application Insights. +- `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. +- `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + +**Note!** \ +Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability +Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. Type: @@ -759,8 +764,8 @@ Type: ```hcl map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) ``` @@ -776,22 +781,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -820,43 +825,52 @@ You can create or re-use an existing Storage Account and/or File Share. For deta - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. -- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. -- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. -- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - Type: ```hcl @@ -899,129 +913,121 @@ Default value: `map[]` #### vmseries -A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - -- `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - -- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). -- `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. +- `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - The most important ones are listed below: + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. +- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -1062,7 +1068,6 @@ map(object({ identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -1081,6 +1086,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index e522960b..932692a9 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "transit-vnet-dedicated" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -109,8 +111,8 @@ vnets = { } } +### LOAD BALANCING ### -# --- LOAD BALANCING PART --- # load_balancers = { "public" = { name = "public-lb" @@ -154,7 +156,8 @@ load_balancers = { } } -# --- VMSERIES PART --- # +### VM-SERIES ### + ngfw_metrics = { name = "metrics" } @@ -172,7 +175,8 @@ bootstrap_storages = { vmseries = { "fw-in-1" = { - name = "inbound-firewall01" + name = "inbound-firewall01" + vnet_key = "transit" image = { version = "10.2.3" } @@ -187,7 +191,6 @@ vmseries = { public_snet_key = "public" } } - vnet_key = "transit" interfaces = [ { name = "vm-in-01-mgmt" @@ -207,7 +210,8 @@ vmseries = { ] } "fw-in-2" = { - name = "inbound-firewall02" + name = "inbound-firewall02" + vnet_key = "transit" image = { version = "10.2.3" } @@ -222,7 +226,6 @@ vmseries = { public_snet_key = "public" } } - vnet_key = "transit" interfaces = [ { name = "vm-in-02-mgmt" @@ -241,7 +244,8 @@ vmseries = { ] } "fw-obew-1" = { - name = "obew-firewall01" + name = "obew-firewall01" + vnet_key = "transit" image = { version = "10.2.3" } @@ -256,7 +260,6 @@ vmseries = { public_snet_key = "public" } } - vnet_key = "transit" interfaces = [ { name = "vm-obew-01-mgmt" @@ -276,7 +279,8 @@ vmseries = { ] } "fw-obew-2" = { - name = "obew-firewall02" + name = "obew-firewall02" + vnet_key = "transit" image = { version = "10.2.3" } @@ -291,7 +295,6 @@ vmseries = { public_snet_key = "public" } } - vnet_key = "transit" interfaces = [ { name = "vm-obew-02-mgmt" diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index ecee2c8d..29fd01d5 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -26,7 +27,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -46,7 +48,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -62,15 +65,16 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } - module "natgw" { source = "../../modules/natgw" @@ -84,15 +88,19 @@ module "natgw" { idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + public_ip = try(merge(each.value.public_ip, { + name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" + }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { + name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" + }), null) tags = var.tags depends_on = [module.vnet] } +### Create Load Balancers, both internal and external ### -# create load balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -109,7 +117,8 @@ module "load_balancer" { 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}", + "${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( @@ -137,9 +146,65 @@ module "load_balancer" { depends_on = [module.vnet] } +### Create Application Gateways ### + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules + + tags = var.tags + depends_on = [module.vnet, module.vmseries] +} +### Create VM-Series VMs and closely associated resources ### -# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -147,9 +212,11 @@ module "ngfw_metrics" { create_workspace = var.ngfw_metrics.create_workspace - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -161,6 +228,7 @@ module "ngfw_metrics" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : @@ -287,8 +355,10 @@ module "vmseries" { coalesce( each.value.virtual_machine.bootstrap_options, join(",", [ - "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", "file-share=${each.key}", "share-directory=None" ]), @@ -299,10 +369,12 @@ module "vmseries" { ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_public_ip = v.create_public_ip - public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${ + var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip") + }" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address attach_to_lb_backend_pool = v.load_balancer_key != null @@ -318,60 +390,3 @@ module "vmseries" { module.bootstrap, ] } - -# Create Application Gateway - -locals { - nics_with_appgw_key = flatten([ - for k, v in var.vmseries : [ - for nic in v.interfaces : { - vm_key = k - nic_name = nic.name - appgw_key = nic.application_gateway_key - } if nic.application_gateway_key != null - ]]) - - ips_4_nics_with_appgw_key = { - for v in local.nics_with_appgw_key : - v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... - } -} - -module "appgw" { - source = "../../modules/appgw" - - for_each = var.appgws - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name - location = var.location - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - - zones = each.value.zones - public_ip = merge( - each.value.public_ip, - { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } - ) - domain_name_label = each.value.domain_name_label - capacity = each.value.capacity - enable_http2 = each.value.enable_http2 - waf = each.value.waf - managed_identities = each.value.managed_identities - global_ssl_policy = each.value.global_ssl_policy - ssl_profiles = each.value.ssl_profiles - frontend_ip_configuration_name = each.value.frontend_ip_configuration_name - listeners = each.value.listeners - backend_pool = merge( - each.value.backend_pool, - length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } - ) - backend_settings = each.value.backend_settings - probes = each.value.probes - rewrites = each.value.rewrites - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - rules = each.value.rules - - tags = var.tags - depends_on = [module.vnet, module.vmseries] -} diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index d401eaab..8a26966e 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -45,33 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -125,21 +123,21 @@ variable "natgws" { For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -158,13 +156,13 @@ variable "natgws" { EOF default = {} type = map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -179,12 +177,11 @@ variable "natgws" { })) } +### LOAD BALANCING ### - -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -194,26 +191,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -245,10 +245,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -272,29 +272,195 @@ variable "load_balancers" { })) } +variable "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. + + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + EOF + type = map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +} + +### VM-SERIES ### variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: - - `name` - name of the Application Insights. - - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). - Please verify how many update and fault domain are supported in a region before deploying this resource. + - `name` - (`string`, required) name of the Application Insights. + - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. + - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + + **Note!** \ + Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability + Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} nullable = false type = map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) } - variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -302,22 +468,22 @@ variable "ngfw_metrics" { When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: - - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. EOF default = null type = object({ @@ -338,41 +504,50 @@ variable "bootstrap_storages" { - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. - - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. - - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - EOF default = {} nullable = false @@ -410,127 +585,119 @@ variable "bootstrap_storages" { variable "vmseries" { description = <<-EOF - A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + - `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - The most important ones are listed below: + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. + - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). EOF default = {} nullable = false type = map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -571,7 +738,6 @@ variable "vmseries" { identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -583,185 +749,26 @@ variable "vmseries" { application_gateway_key = optional(string) })) })) - validation { + validation { # virtual_machine.bootstrap_options & virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null ]) - error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + error_message = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF } - validation { + validation { # virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : - v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true - if v.virtual_machine.bootstrap_package != null + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? ( + v.virtual_machine.bootstrap_package.private_snet_key != null && + v.virtual_machine.bootstrap_package.public_snet_key != null + ) : true if v.virtual_machine.bootstrap_package != null ]) - error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF } } - -### Application Gateway -variable "appgws" { - description = <<-EOF - A map defining all Application Gateways in the current deployment. - - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, - refer to [module documentation](../../modules/appgw/README.md). - - **Note!** \ - The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). - It represents the Rules section of an Application Gateway in Azure Portal. - - Below you can find a brief list of most important properties: - - - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. - - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. - - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. - - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal - deployment. - - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. - - `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. - - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend - will be created. - - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - EOF - type = map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -} \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 6e455114..f3a74a4e 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -198,8 +198,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -208,11 +208,11 @@ Name | Type | Description 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. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. @@ -244,11 +244,11 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `natgw` | - | ../../modules/natgw | -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `vmss` | - | ../../modules/vmss | @@ -264,46 +264,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -358,8 +357,6 @@ map(object({ - - #### appgws A map defining all Application Gateways in the current deployment. @@ -378,25 +375,25 @@ Below you can find a brief list of most important properties: described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. @@ -533,18 +530,9 @@ map(object({ -### Optional Inputs - -#### tags -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> +### Optional Inputs #### name_prefix @@ -585,6 +573,17 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### natgws A map defining NAT Gateways. @@ -594,21 +593,21 @@ explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: -- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. -- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. -- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). -- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. -- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. -- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. -- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. -- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. -- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -630,13 +629,13 @@ Type: ```hcl map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -658,7 +657,7 @@ Default value: `map[]` #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -668,26 +667,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -721,10 +723,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -753,6 +755,7 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### ngfw_metrics A map controlling metrics-relates resources. @@ -760,22 +763,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -803,7 +806,10 @@ For details and defaults for available options please refer to the [`vmss`](../. The basic Scale Set configuration properties are as follows: -- `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` +- `name` - (`string`, required) name of the scale set, will be prefixed with the value of + `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts + subnets used to deploy network interfaces for VMs in this Scale Set. - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and @@ -813,74 +819,70 @@ The basic Scale Set configuration properties are as follows: The `disable_password_authentication` property is by default true. When using this value you have to specify at least one SSH key. You can however set this property to `true`. Then you have 2 options, either: - - do not specify anything else - a random password will be generated for you + - do not specify anything else, a random password will be generated for you. - specify at least one of `password` or `ssh_keys` properties. - For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). -- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. +- `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. The + `image` property is required but there are only 2 properties (mutually exclusive) that have to + be set up, either: - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#image). - For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). +- `virtual_machine_scale_set` - (`map`, optional, defaults to module default) a map that groups most common Scale Set + configuration options: -- `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set - configuration options. + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zones` - (`list`, optional, defaults to module default) a list of Availability Zones in which VMs from + this Scale Set will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values). + - `bootstrap_options` - (`string`, optional, defaults to module default) bootstrap options to pass to VM-Series instance. - Below we present only the most important ones, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported - - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from - this Scale Set will be created - - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series - instance + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set). - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not - the scaling profiles (metrics thresholds, etc) + the scaling profiles (metrics, thresholds, etc.). Most common properties are: + + - `default_count` - (`number`, optional, defaults to module default) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to + compare the metrics to the thresholds. - Below we present only the most important properties, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in - the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare - the metrics to the thresholds +- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the + 1<sup>st</sup> interface should be the management one. Following properties are available: -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set -- `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. Following properties are available: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets` - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module default) create Public IP for an interface. - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the - `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancer's backend pool + `var.loadbalancers` variable, network interface that has this property defined will be added to + the Load Balancer's backend pool. - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the `var.appgws`, network interface that has this property defined will be added to the Application - Gateways's backend pool + Gateways's backend pool. - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label - for each VM instance - -- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available - configuration please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) + for each VM instance. +- `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + properties please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = object({ username = optional(string) password = optional(string) @@ -918,7 +920,6 @@ map(object({ notification_emails = optional(list(string), []) webhooks_uris = optional(map(string), {}) }), {}) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -970,6 +971,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 9fd03fde..5f6ca663 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "autoscale-dedicated" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -122,9 +124,8 @@ natgws = { } } +### LOAD BALANCING ### - -# --- LOAD BALANCING PART --- # load_balancers = { "public" = { name = "public-lb" @@ -174,14 +175,16 @@ load_balancers = { } } -# --- VMSERIES PART --- # +### VM-SERIES ### + ngfw_metrics = { name = "ngwf-log-analytics-wrksp" } scale_sets = { inbound = { - name = "inbound-vmss" + name = "inbound-vmss" + vnet_key = "transit" image = { version = "10.2.4" } @@ -195,7 +198,6 @@ scale_sets = { autoscaling_configuration = { default_count = 2 } - vnet_key = "transit" interfaces = [ { name = "management" @@ -213,7 +215,8 @@ scale_sets = { ] } obew = { - name = "obew-vmss" + name = "obew-vmss" + vnet_key = "transit" image = { version = "10.2.4" } @@ -227,7 +230,6 @@ scale_sets = { autoscaling_configuration = { default_count = 2 } - vnet_key = "transit" interfaces = [ { name = "management" diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 9f208f66..151af28c 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -27,7 +28,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -47,7 +49,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -63,9 +66,11 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags @@ -84,14 +89,19 @@ module "natgw" { idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + public_ip = try(merge(each.value.public_ip, { + name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" + }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { + name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" + }), null) tags = var.tags depends_on = [module.vnet] } -# create load balancers, both internal and external +### Create Load Balancers, both internal and external ### + module "load_balancer" { source = "../../modules/loadbalancer" @@ -108,7 +118,8 @@ module "load_balancer" { 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}", + "${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( @@ -136,30 +147,7 @@ module "load_balancer" { depends_on = [module.vnet] } -module "ngfw_metrics" { - source = "../../modules/ngfw_metrics" - - count = var.ngfw_metrics != null ? 1 : 0 - - create_workspace = var.ngfw_metrics.create_workspace - - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location - - log_analytics_workspace = { - sku = var.ngfw_metrics.sku - metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days - } - - application_insights = { - for k, v in var.scale_sets : - k => { name = "${var.name_prefix}${v.name}-ai" } - if length(v.autoscaling_profiles) > 0 - } - - tags = var.tags -} +### Create Application Gateways ### module "appgw" { source = "../../modules/appgw" @@ -197,6 +185,35 @@ module "appgw" { depends_on = [module.vnet] } +### Create VM-Series VM Scale Sets and closely associated resources ### + +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" + + count = var.ngfw_metrics != null ? 1 : 0 + + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { + for k, v in var.scale_sets : + k => { name = "${var.name_prefix}${v.name}-ai" } + if length(v.autoscaling_profiles) > 0 + } + + tags = var.tags +} + module "vmss" { source = "../../modules/vmss" diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 5c90903a..a18c7c0f 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -45,33 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -125,21 +123,21 @@ variable "natgws" { For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -158,13 +156,13 @@ variable "natgws" { EOF default = {} type = map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -179,12 +177,11 @@ variable "natgws" { })) } +### LOAD BALANCING ### - -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -194,26 +191,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -245,10 +245,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -272,213 +272,6 @@ variable "load_balancers" { })) } -variable "ngfw_metrics" { - description = <<-EOF - A map controlling metrics-relates resources. - - When set to explicit `null` (default) it will disable any metrics resources in this deployment. - - When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. - - All the settings available below are common to the Log Analytics Workspace and Application Insight instances. - - Following properties are available: - - - `name` - (`string`, required) name of the (common) Log Analytics Workspace - - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace - - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. - EOF - default = null - type = object({ - name = string - create_workspace = optional(bool, true) - resource_group_name = optional(string) - sku = optional(string) - metrics_retention_in_days = optional(number) - }) -} - -### VMSERIES - -variable "scale_sets" { - description = <<-EOF - A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. - - For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. - - The basic Scale Set configuration properties are as follows: - - - `name` - (`string`, required) name of the scale set, will be prefixed with the value of `var.name_prefix` - - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. - - This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and - available in the Terraform outputs. - - **Note!** \ - The `disable_password_authentication` property is by default true. When using this value you have to specify at least one - SSH key. You can however set this property to `true`. Then you have 2 options, either: - - - do not specify anything else - a random password will be generated for you - - specify at least one of `password` or `ssh_keys` properties. - - For all properties and their default values see [module's documentation](../../modules/vmss/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set up, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure's Marketplace - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image - - For details on the other properties refer to [module's documentation](../../modules/vmss/README.md#image). - - - `virtual_machine_scale_set` - (`map`, optional, defaults to module defaults) a map that groups most common Scale Set - configuration options. - - Below we present only the most important ones, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set): - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series - Deployment Guide* as only a few selected sizes are supported - - `zones` - (`list`, optional, defaults to module defaults) a list of Availability Zones in which VMs from - this Scale Set will be created - - `disk_type` - (`string`, optional, defaults to module defaults) type of Managed Disk which should be created, - possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected - `vm_size` values) - - `bootstrap_options` - (`string`, optional, defaults to module defaults) bootstrap options to pass to VM-Series - instance - - - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not - the scaling profiles (metrics thresholds, etc) - - Below we present only the most important properties, for the rest please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). - - - `default_count` - (`number`, optional, defaults module defaults) minimum number of instances that should be present in - the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to compare - the metrics to the thresholds - - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets - used to deploy network interfaces for VMs in this Scale Set - - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. Following properties are available: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`) - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets` - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create Public IP for an interface - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the - `var.loadbalancers` variable, network interface that has this property defined will be - added to the Load Balancer's backend pool - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the - `var.appgws`, network interface that has this property defined will be added to the Application - Gateways's backend pool - - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label - for each VM instance - - - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available - configuration please refer to - [module's documentation](../../modules/vmss/README.md#autoscaling_profiles) - - EOF - default = {} - nullable = false - type = map(object({ - name = string - authentication = object({ - username = optional(string) - password = optional(string) - disable_password_authentication = optional(bool, true) - ssh_keys = optional(list(string), []) - }) - image = object({ - version = optional(string) - publisher = optional(string) - offer = optional(string) - sku = optional(string) - enable_marketplace_plan = optional(bool) - custom_id = optional(string) - }) - virtual_machine_scale_set = optional(object({ - size = optional(string) - bootstrap_options = optional(string) - zones = optional(list(string)) - disk_type = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - overprovision = optional(bool) - platform_fault_domain_count = optional(number) - disk_encryption_set_id = optional(string) - enable_boot_diagnostics = optional(bool, true) - boot_diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) - })) - autoscaling_configuration = optional(object({ - default_count = optional(number) - scale_in_policy = optional(string) - scale_in_force_deletion = optional(bool) - notification_emails = optional(list(string), []) - webhooks_uris = optional(map(string), {}) - }), {}) - vnet_key = string - interfaces = list(object({ - name = string - subnet_key = string - create_public_ip = optional(bool) - load_balancer_key = optional(string) - application_gateway_key = optional(string) - pip_domain_name_label = optional(string) - })) - autoscaling_profiles = optional(list(object({ - name = string - minimum_count = optional(number) - default_count = number - maximum_count = optional(number) - recurrence = optional(object({ - timezone = optional(string) - days = list(string) - start_time = string - end_time = string - })) - scale_rules = optional(list(object({ - name = string - scale_out_config = object({ - threshold = number - operator = optional(string) - grain_window_minutes = number - grain_aggregation_type = optional(string) - aggregation_window_minutes = number - aggregation_window_type = optional(string) - cooldown_window_minutes = number - change_count_by = optional(number) - }) - scale_in_config = object({ - threshold = number - operator = optional(string) - grain_window_minutes = optional(number) - grain_aggregation_type = optional(string) - aggregation_window_minutes = optional(number) - aggregation_window_type = optional(string) - cooldown_window_minutes = number - change_count_by = optional(number) - }) - })), []) - })), []) - })) -} - - - -### Application Gateway variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. @@ -497,25 +290,25 @@ variable "appgws" { described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. - - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal + - `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. - - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend + - `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see + - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF @@ -642,3 +435,205 @@ variable "appgws" { })) })) } + +### VM-SERIES ### + +variable "ngfw_metrics" { + description = <<-EOF + A map controlling metrics-relates resources. + + When set to explicit `null` (default) it will disable any metrics resources in this deployment. + + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. + + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + + Following properties are available: + + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace. + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. + EOF + default = null + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +} + +variable "scale_sets" { + description = <<-EOF + A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. + + For details and defaults for available options please refer to the [`vmss`](../../modules/vmss/README.md) module. + + The basic Scale Set configuration properties are as follows: + + - `name` - (`string`, required) name of the scale set, will be prefixed with the value of + `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts + subnets used to deploy network interfaces for VMs in this Scale Set. + - `authentication` - (`map`, required) authentication setting for VMs deployed in this scale set. + + This map holds the firewall admin password. When this property is not set, the password will be autogenerated for you and + available in the Terraform outputs. + + **Note!** \ + The `disable_password_authentication` property is by default true. When using this value you have to specify at least one + SSH key. You can however set this property to `true`. Then you have 2 options, either: + + - do not specify anything else, a random password will be generated for you. + - specify at least one of `password` or `ssh_keys` properties. + + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used to spawn VMs in this Scale Set. The + `image` property is required but there are only 2 properties (mutually exclusive) that have to + be set up, either: + + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#image). + + - `virtual_machine_scale_set` - (`map`, optional, defaults to module default) a map that groups most common Scale Set + configuration options: + + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zones` - (`list`, optional, defaults to module default) a list of Availability Zones in which VMs from + this Scale Set will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `vm_size` values). + - `bootstrap_options` - (`string`, optional, defaults to module default) bootstrap options to pass to VM-Series instance. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#virtual_machine_scale_set). + + - `autoscaling_configuration` - (`map`, optional, defaults to `{}`) a map that groups common autoscaling configuration, but not + the scaling profiles (metrics, thresholds, etc.). Most common properties are: + + - `default_count` - (`number`, optional, defaults to module default) minimum number of instances that should be present + in the scale set when the autoscaling engine cannot read the metrics or is otherwise unable to + compare the metrics to the thresholds. + + For details on all properties refer to [module's documentation](../../modules/vmss/README.md#autoscaling_configuration). + + - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the + 1<sup>st</sup> interface should be the management one. Following properties are available: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module default) create Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in the + `var.loadbalancers` variable, network interface that has this property defined will be added to + the Load Balancer's backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in the + `var.appgws`, network interface that has this property defined will be added to the Application + Gateways's backend pool. + - `pip_domain_name_label` - (`string`, optional, defaults to `null`) prefix which should be used for the Domain Name Label + for each VM instance. + + - `autoscaling_profiles` - (`list`, optional, defaults to `[]`) a list of autoscaling profiles, for details on available + properties please refer to + [module's documentation](../../modules/vmss/README.md#autoscaling_profiles). + EOF + default = {} + nullable = false + type = map(object({ + name = string + vnet_key = string + authentication = object({ + username = optional(string) + password = optional(string) + disable_password_authentication = optional(bool, true) + ssh_keys = optional(list(string), []) + }) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine_scale_set = optional(object({ + size = optional(string) + bootstrap_options = optional(string) + zones = optional(list(string)) + disk_type = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + overprovision = optional(bool) + platform_fault_domain_count = optional(number) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string), []) + allow_extension_operations = optional(bool) + })) + autoscaling_configuration = optional(object({ + default_count = optional(number) + scale_in_policy = optional(string) + scale_in_force_deletion = optional(bool) + notification_emails = optional(list(string), []) + webhooks_uris = optional(map(string), {}) + }), {}) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool) + load_balancer_key = optional(string) + application_gateway_key = optional(string) + pip_domain_name_label = optional(string) + })) + autoscaling_profiles = optional(list(object({ + name = string + minimum_count = optional(number) + default_count = number + maximum_count = optional(number) + recurrence = optional(object({ + timezone = optional(string) + days = list(string) + start_time = string + end_time = string + })) + scale_rules = optional(list(object({ + name = string + scale_out_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = number + grain_aggregation_type = optional(string) + aggregation_window_minutes = number + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + scale_in_config = object({ + threshold = number + operator = optional(string) + grain_window_minutes = optional(number) + grain_aggregation_type = optional(string) + aggregation_window_minutes = optional(number) + aggregation_window_type = optional(string) + cooldown_window_minutes = number + change_count_by = optional(number) + }) + })), []) + })), []) + })) +} diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 3b146381..26185cc3 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -82,8 +82,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -91,11 +91,11 @@ Name | Type | Description 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 Balancers. -[`gateway_load_balancers`](#gateway_load_balancers) | `map` | Map with Gateway Load Balancer definitions. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`gateway_load_balancers`](#gateway_load_balancers) | `map` | A map with Gateway Load Balancer (GWLB) definitions. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -133,10 +133,10 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`gwlb` | - | ../../modules/gwlb | create Gateway Load Balancers -`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`vnet` | - | ../../modules/vnet | +`load_balancer` | - | ../../modules/loadbalancer | +`gwlb` | - | ../../modules/gwlb | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | `appvm` | - | ../../modules/virtual_machine | @@ -156,46 +156,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -260,17 +259,6 @@ map(object({ ### Optional Inputs -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - #### name_prefix A prefix that will be added to all created resources. @@ -281,7 +269,7 @@ Example: ``` name_prefix = "test-" ``` - + **Note!** \ 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. @@ -297,7 +285,7 @@ Default value: `` 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. @@ -309,9 +297,20 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -321,26 +320,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -374,10 +376,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -408,18 +410,22 @@ Default value: `map[]` #### gateway_load_balancers -Map with Gateway Load Balancer definitions. +A map with Gateway Load Balancer (GWLB) definitions. Following settings are available: -- `name` - (`string`, required) name of the Gatewa Load Balancer Gateway. -- `frontend_ip` - (`object`, required) frontend IP configuration - (refer to [module documentation](../../modules/gwlb/README.md) for details) -- `health_probe - (`object`, optional) health probe settings - (refer to [module documentation](../../modules/gwlb/README.md) for details) -- `backends` - (`map`, optional) map of backends - (refer to [module documentation](../../modules/gwlb/README.md) for details) -- `lb_rule` - (`object`, optional) load balancer rule - (refer to [module documentation](../../modules/gwlb/README.md) for details) +- `name` - (`string`, required) name of the Gateway Load Balancer Gateway. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this GWLB will + be assigned to. +- `subnet_key` - (`string`, required) a name (key value) of Subnet the GWLB will be assigned to, defined in `var.vnets` for + a VNET described by `vnet_name`. +- `frontend_ip` - (`object`, required) frontend IP configuration, refer to + [module's documentation](../../modules/gwlb/README.md) for details. +- `health_probe` - (`object`, optional) health probe settings, refer to + [module's documentation](../../modules/gwlb/README.md) for details. +- `backends` - (`map`, optional) map of backends, refer to + [module's documentation](../../modules/gwlb/README.md) for details. +- `lb_rule` - (`object`, optional) load balancer rule, refer to + [module's documentation](../../modules/gwlb/README.md) for details. Type: @@ -469,12 +475,14 @@ Default value: `map[]` A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: -- `name` - name of the Application Insights. -- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). -- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). -Please verify how many update and fault domain are supported in a region before deploying this resource. +- `name` - (`string`, required) name of the Application Insights. +- `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. +- `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + +**Note!** \ +Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability +Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. Type: @@ -482,8 +490,8 @@ Type: ```hcl map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) ``` @@ -499,22 +507,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -543,43 +551,52 @@ You can create or re-use an existing Storage Account and/or File Share. For deta - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. -- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. -- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. -- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - Type: ```hcl @@ -622,123 +639,121 @@ Default value: `map[]` #### vmseries -A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - -- `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - -- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a data - Load Balancer health checks. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). - -- `interfaces` - (`list`, required) configuration of all network interfaces + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). +- `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - The most important ones are listed below: + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). +- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -754,7 +769,6 @@ map(object({ custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -762,21 +776,23 @@ map(object({ static_files = optional(map(string), {}) bootstrap_package_path = optional(string) bootstrap_xml_template = optional(string) - data_snet_key = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string @@ -786,9 +802,7 @@ map(object({ public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - gwlb_key = optional(string) - gwlb_backend_key = optional(string) - add_to_appgw_backend = optional(bool, false) + application_gateway_key = optional(string) })) })) ``` @@ -809,5 +823,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index a6f462ea..9be9a153 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "gwlb" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -102,7 +104,8 @@ vnets = { } } -# --- LOAD BALANCING PART --- # +### LOAD BALANCING ### + load_balancers = { "app1" = { name = "app1-lb" @@ -142,7 +145,6 @@ load_balancers = { } } -# --- GWLB PART --- # gateway_load_balancers = { gwlb = { name = "vmseries-gwlb" @@ -184,7 +186,8 @@ gateway_load_balancers = { } } -# --- VMSERIES PART --- # +### VM-SERIES ### + bootstrap_storages = { "bootstrap" = { name = "examplegwlbbootstrap" @@ -198,14 +201,14 @@ bootstrap_storages = { vmseries = { "fw-1" = { - name = "firewall01" + name = "firewall01" + vnet_key = "transit" image = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 1 + size = "Standard_DS3_v2" + zone = 1 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -228,14 +231,14 @@ vmseries = { ] } "fw-2" = { - name = "firewall02" + name = "firewall02" + vnet_key = "transit" image = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 2 + size = "Standard_DS3_v2" + zone = 2 bootstrap_package = { bootstrap_storage_key = "bootstrap" static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } @@ -259,7 +262,8 @@ vmseries = { } } -# --- APPLICATION VM PART --- # +### TEST INFRASTRUCTURE ### + appvms = { app1vm01 = { name = "app1-vm01" @@ -277,4 +281,4 @@ sudo systemctl enable nginx echo "Backend VM is $(hostname)" | sudo tee /var/www/html/index.html SCRIPT } -} \ No newline at end of file +} diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 8761e2c4..8fb34ab9 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -26,7 +27,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -46,7 +48,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -62,15 +65,18 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } -# create load balancers, both internal and external +### Create Load Balancers, both internal and external ### + module "load_balancer" { source = "../../modules/loadbalancer" @@ -87,7 +93,8 @@ module "load_balancer" { 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}", + "${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( @@ -116,7 +123,8 @@ module "load_balancer" { depends_on = [module.vnet] } -# create Gateway Load Balancers +### Create Gateway Load Balancers ### + module "gwlb" { for_each = var.gateway_load_balancers source = "../../modules/gwlb" @@ -141,7 +149,8 @@ module "gwlb" { } -# create the actual VM-Series VMs and resources +### Create VM-Series VMs and closely associated resources ### + module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -149,9 +158,11 @@ module "ngfw_metrics" { create_workspace = var.ngfw_metrics.create_workspace - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -163,6 +174,7 @@ module "ngfw_metrics" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : @@ -275,8 +287,10 @@ module "vmseries" { coalesce( each.value.virtual_machine.bootstrap_options, join(",", [ - "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", "file-share=${each.key}", "share-directory=None" ]), @@ -287,10 +301,12 @@ module "vmseries" { ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] - create_public_ip = v.create_public_ip - public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${ + coalesce(v.public_ip_name, "${v.name}-pip") + }" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address attach_to_lb_backend_pool = v.load_balancer_key != null || v.gwlb_key != null @@ -312,6 +328,8 @@ module "vmseries" { ] } +### Create test infrastructure ### + module "appvm" { for_each = var.appvms source = "../../modules/virtual_machine" @@ -340,4 +358,4 @@ module "appvm" { accelerated_networking = try(each.value.accelerated_networking, false) tags = var.tags -} \ No newline at end of file +} diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index 0528cb25..ac29cf18 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -20,7 +10,7 @@ variable "name_prefix" { ``` name_prefix = "test-" ``` - + **Note!** \ 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. @@ -33,7 +23,7 @@ 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 @@ -45,32 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -115,11 +114,11 @@ variable "vnets" { })) } +### LOAD BALANCING ### -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -129,26 +128,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -180,10 +182,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -207,22 +209,24 @@ variable "load_balancers" { })) } - -### GWLB variable "gateway_load_balancers" { description = <<-EOF - Map with Gateway Load Balancer definitions. + A map with Gateway Load Balancer (GWLB) definitions. Following settings are available: - - `name` - (`string`, required) name of the Gatewa Load Balancer Gateway. - - `frontend_ip` - (`object`, required) frontend IP configuration - (refer to [module documentation](../../modules/gwlb/README.md) for details) - - `health_probe - (`object`, optional) health probe settings - (refer to [module documentation](../../modules/gwlb/README.md) for details) - - `backends` - (`map`, optional) map of backends - (refer to [module documentation](../../modules/gwlb/README.md) for details) - - `lb_rule` - (`object`, optional) load balancer rule - (refer to [module documentation](../../modules/gwlb/README.md) for details) + - `name` - (`string`, required) name of the Gateway Load Balancer Gateway. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this GWLB will + be assigned to. + - `subnet_key` - (`string`, required) a name (key value) of Subnet the GWLB will be assigned to, defined in `var.vnets` for + a VNET described by `vnet_name`. + - `frontend_ip` - (`object`, required) frontend IP configuration, refer to + [module's documentation](../../modules/gwlb/README.md) for details. + - `health_probe` - (`object`, optional) health probe settings, refer to + [module's documentation](../../modules/gwlb/README.md) for details. + - `backends` - (`map`, optional) map of backends, refer to + [module's documentation](../../modules/gwlb/README.md) for details. + - `lb_rule` - (`object`, optional) load balancer rule, refer to + [module's documentation](../../modules/gwlb/README.md) for details. EOF default = {} type = map(object({ @@ -259,29 +263,31 @@ variable "gateway_load_balancers" { })) } +### VM-SERIES ### variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: - - `name` - name of the Application Insights. - - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). - Please verify how many update and fault domain are supported in a region before deploying this resource. + - `name` - (`string`, required) name of the Application Insights. + - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. + - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + + **Note!** \ + Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability + Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} nullable = false type = map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) } - variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -289,22 +295,22 @@ variable "ngfw_metrics" { When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: - - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. EOF default = null type = object({ @@ -316,7 +322,6 @@ variable "ngfw_metrics" { }) } - variable "bootstrap_storages" { description = <<-EOF A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -326,41 +331,50 @@ variable "bootstrap_storages" { - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. - - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. - - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - EOF default = {} nullable = false @@ -396,125 +410,121 @@ variable "bootstrap_storages" { })) } - -### VM-Series variable "vmseries" { description = <<-EOF - A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a data - Load Balancer health checks. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). - - - `interfaces` - (`list`, required) configuration of all network interfaces + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - The most important ones are listed below: + - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load Balancer's - backend pool - - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added - to the Application Gateway's backend pool. + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). EOF default = {} nullable = false type = map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -530,7 +540,6 @@ variable "vmseries" { custom_id = optional(string) }) virtual_machine = object({ - vnet_key = string size = optional(string) bootstrap_options = optional(string) bootstrap_package = optional(object({ @@ -538,21 +547,23 @@ variable "vmseries" { static_files = optional(map(string), {}) bootstrap_package_path = optional(string) bootstrap_xml_template = optional(string) - data_snet_key = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - accelerated_networking = optional(bool) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, true) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string @@ -562,31 +573,35 @@ variable "vmseries" { public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) - gwlb_key = optional(string) - gwlb_backend_key = optional(string) - add_to_appgw_backend = optional(bool, false) + application_gateway_key = optional(string) })) })) - validation { + validation { # virtual_machine.bootstrap_options & virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null ]) - error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + error_message = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF } - validation { + validation { # virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : - v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.data_snet_key != null : true - if v.virtual_machine.bootstrap_package != null + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? ( + v.virtual_machine.bootstrap_package.private_snet_key != null && + v.virtual_machine.bootstrap_package.public_snet_key != null + ) : true if v.virtual_machine.bootstrap_package != null ]) - error_message = "The `data_snet_key` is required when `bootstrap_xml_template` is set." + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF } } +### TEST INFRASTRUCTURE ### -### Application VMs variable "appvms" { description = <<-EOF Configuration for sample application VMs. diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 4febf400..7f6c6c31 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -124,8 +124,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -133,10 +133,9 @@ Name | Type | Description 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. -[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`panoramas`](#panoramas) | `map` | A map defining Azure Virtual Machine based on Palo Alto Networks Panorama image. @@ -167,7 +166,7 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `panorama` | - | ../../modules/panorama | @@ -184,19 +183,18 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string @@ -209,22 +207,21 @@ 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -236,8 +233,7 @@ map(object({ create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -255,7 +251,8 @@ map(object({ })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -284,21 +281,11 @@ map(object({ ### Optional Inputs -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - #### 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: ``` @@ -306,7 +293,8 @@ name_prefix = "test-" ``` **Note!** \ -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. +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 @@ -317,7 +305,9 @@ Default value: `` #### 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 `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. @@ -328,13 +318,14 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> -#### enable_zones -If `true`, enable zone support for resources. +#### tags -Type: bool +Map of tags to assign to the created resources. -Default value: `true` +Type: map(string) + +Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> @@ -344,12 +335,14 @@ Default value: `true` A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: -- `name` - name of the Application Insights. -- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). -- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -Please keep in mind that Azure defaults are not working for each region (especially small ones, w/o any Availability Zones). -Please verify how many update and fault domains are supported in a region before deploying this resource. +- `name` - (`string`, required) name of the Application Insights. +- `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. +- `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + +**Note!** \ +Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability +Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. Type: @@ -357,8 +350,8 @@ Type: ```hcl map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) ``` @@ -376,68 +369,64 @@ For details and defaults for available options please refer to the [`panorama`]( The basic Panorama VM configuration properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). -- `image` - (`map`, required) properties defining a base image used by the deployed VM. + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. +- `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#image). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. -- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#image). - Following properties are available: +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + Most common properties are: - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. - - Following properties are available: + interface should be the management one. Most common properties are: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). -- `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. - - Following properties are available: +- `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. Most + common properties are: - - `name` - (`string`, required) the Managed Disk name. - - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. + - `name` - (`string`, required) the Managed Disk name. + - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = object({ username = optional(string, "panadmin") password = optional(string) @@ -464,7 +453,6 @@ map(object({ identity_type = optional(string) identity_ids = optional(list(string)) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -487,5 +475,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 3d32e6cd..1589f06f 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "panorama" name_prefix = "example-" @@ -9,9 +10,8 @@ tags = { } enable_zones = false +### NETWORK ### - -# --- VNET PART --- # vnets = { "vnet" = { name = "panorama-vnet" @@ -44,11 +44,12 @@ vnets = { } } -# --- PANORAMA PART --- # +### PANORAMA ### panoramas = { "pn-1" = { - name = "panorama01" + name = "panorama01" + vnet_key = "vnet" authentication = { disable_password_authentication = false #ssh_keys = ["~/.ssh/id_rsa.pub"] @@ -61,7 +62,6 @@ panoramas = { zone = null disk_name = "panorama-os-disk" } - vnet_key = "vnet" interfaces = [ { name = "management" diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index 5a26246f..470f0ff6 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -26,7 +27,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -46,7 +48,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -62,15 +65,17 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } -# Create Panorama virtual appliance +### Create Panorama VMs and closely associated resources ### resource "azurerm_availability_set" "this" { for_each = var.availability_sets @@ -104,10 +109,12 @@ module "panorama" { ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_public_ip = v.create_public_ip - public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${each.value.name}-pip")}" : v.public_ip_name + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${ + coalesce(v.public_ip_name, "${each.value.name}-pip") + }" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address }] diff --git a/examples/standalone_panorama/outputs.tf b/examples/standalone_panorama/outputs.tf index 08ab57b6..07cf32c3 100644 --- a/examples/standalone_panorama/outputs.tf +++ b/examples/standalone_panorama/outputs.tf @@ -11,4 +11,4 @@ output "password" { output "panorama_mgmt_ips" { value = { for k, v in module.panorama : k => v.mgmt_ip_address } -} \ No newline at end of file +} diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 3e91c4cb..6587dc54 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -1,19 +1,10 @@ -### 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 -} +### GENERAL ### 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: ``` @@ -21,7 +12,8 @@ variable "name_prefix" { ``` **Note!** \ - 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. + 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 @@ -29,7 +21,9 @@ variable "name_prefix" { 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 `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 @@ -41,36 +35,40 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool +variable "location" { + description = "The Azure region to use." + type = string } +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF type = map(object({ name = string @@ -78,8 +76,7 @@ variable "vnets" { create_virtual_network = optional(bool, true) address_space = optional(list(string)) network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) + name = string rules = optional(map(object({ name = string priority = number @@ -97,7 +94,8 @@ variable "vnets" { })), {}) })), {}) route_tables = optional(map(object({ - name = string + name = string + disable_bgp_route_propagation = optional(bool) routes = map(object({ name = string address_prefix = string @@ -116,26 +114,28 @@ variable "vnets" { })) } +### PANORAMA ### -### PANORAMA variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: - - `name` - name of the Application Insights. - - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially small ones, w/o any Availability Zones). - Please verify how many update and fault domains are supported in a region before deploying this resource. + - `name` - (`string`, required) name of the Application Insights. + - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. + - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + + **Note!** \ + Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability + Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} nullable = false type = map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) } @@ -148,66 +148,62 @@ variable "panoramas" { The basic Panorama VM configuration properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - - `image` - (`map`, required) properties defining a base image used by the deployed VM. + For all properties and their default values see [module's documentation](../../modules/panorama/README.md#authentication). - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + - `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#image). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#image). - - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + Most common properties are: - Following properties are available: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. - `interfaces` - (`list`, required) configuration of all network interfaces, order does matter - the 1<sup>st</sup> - interface should be the management one. - - Following properties are available: + interface should be the management one. Most common properties are: - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. - - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. + - `create_public_ip` - (`bool`, optional, defaults to module defaults) create a Public IP for an interface. - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). - - `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. - - Following properties are available: + - `logging_disks` - (`map`, optional, defaults to `null`) configuration of additional data disks for Panorama logs. Most + common properties are: - - `name` - (`string`, required) the Managed Disk name. - - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. + - `name` - (`string`, required) the Managed Disk name. + - `lun` - (`string`, required) the Logical Unit Number of the Data Disk, which needs to be unique within the VM. - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#logging_disks). EOF default = {} nullable = false type = map(object({ - name = string + name = string + vnet_key = string authentication = object({ username = optional(string, "panadmin") password = optional(string) @@ -234,7 +230,6 @@ variable "panoramas" { identity_type = optional(string) identity_ids = optional(list(string)) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index b6fc80ee..46560801 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -115,8 +115,8 @@ terraform destroy Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -125,11 +125,11 @@ Name | Type | Description 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. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -167,13 +167,13 @@ Providers used in this module: Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`vnet` | - | ../../modules/vnet | `natgw` | - | ../../modules/natgw | -`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external -`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`load_balancer` | - | ../../modules/loadbalancer | +`appgw` | - | ../../modules/appgw | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | -`appgw` | - | ../../modules/appgw | Resources used in this module: @@ -190,46 +190,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -284,10 +283,6 @@ map(object({ - - - - #### appgws A map defining all Application Gateways in the current deployment. @@ -306,25 +301,25 @@ Below you can find a brief list of most important properties: described by `subnet_key`. - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal deployment. - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created Public IP will have it's name prefixes with `var.name_prefix`. - `listeners` - (`map`, required) defines Application Gateway's Listeners, see [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend will be created. - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. @@ -461,18 +456,11 @@ map(object({ -### Optional Inputs - - -#### tags -Map of tags to assign to the created resources. -Type: map(string) -Default value: `map[]` -<sup>[back to list](#modules-optional-inputs)</sup> +### Optional Inputs #### name_prefix @@ -513,6 +501,17 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### natgws A map defining NAT Gateways. @@ -522,21 +521,21 @@ explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: -- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. -- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. -- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). -- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. -- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. -- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. -- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. -- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. -- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -558,13 +557,13 @@ Type: ```hcl map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -586,7 +585,7 @@ Default value: `map[]` #### load_balancers -A map containing configuration for all (private and public) Load Balancers. +A map containing configuration for all (both 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). @@ -596,26 +595,29 @@ Following properties are available: - `name` - (`string`, required) a name of the Load Balancer. - `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`. -- `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check +- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. -- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` +- `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. +- `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, another property has been introduced instead: @@ -649,10 +651,10 @@ map(object({ })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -681,17 +683,20 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + #### availability_sets A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: -- `name` - name of the Application Insights. -- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). -- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). -Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). -Please verify how many update and fault domain are supported in a region before deploying this resource. +- `name` - (`string`, required) name of the Application Insights. +- `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. +- `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + +**Note!** \ +Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability +Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. Type: @@ -699,8 +704,8 @@ Type: ```hcl map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) ``` @@ -716,22 +721,22 @@ A map controlling metrics-relates resources. When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each -Scale Set). All instances will be automatically connected to the workspace. -The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. +Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will +be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: -- `name` - (`string`, required) name of the (common) Log Analytics Workspace +- `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace -- `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. -- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. +- `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. Type: @@ -760,43 +765,52 @@ You can create or re-use an existing Storage Account and/or File Share. For deta - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. -- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to +- `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. -- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. +- `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. -- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - Type: ```hcl @@ -839,129 +853,121 @@ Default value: `map[]` #### vmseries -A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. +A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. +- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - -- `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - -- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. -- `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). -- `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. +- `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - The most important ones are listed below: + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. +- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). Type: ```hcl map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -1002,7 +1008,6 @@ map(object({ identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -1021,6 +1026,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 972460b8..951ea7f0 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "vmseries-standalone" name_prefix = "example-" @@ -7,7 +8,8 @@ tags = { "CreatedWith" = "Terraform" } -# --- VNET PART --- # +### NETWORK ### + vnets = { "transit" = { name = "transit" @@ -40,11 +42,12 @@ vnets = { } } +### VM-SERIES ### -# --- VMSERIES PART --- # vmseries = { "fw-1" = { - name = "firewall01" + name = "firewall01" + vnet_key = "transit" image = { version = "10.2.3" } @@ -52,7 +55,6 @@ vmseries = { bootstrap_options = "type=dhcp-client" zone = null } - vnet_key = "transit" interfaces = [ { name = "vm-mgmt" diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index f69aa3bc..29fd01d5 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -1,4 +1,5 @@ -# Generate a random password. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { count = anytrue([ @@ -26,7 +27,8 @@ locals { } } -# Create or source the Resource Group. +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -46,7 +48,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -# Manage the network required for the topology. +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -62,15 +65,16 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } - module "natgw" { source = "../../modules/natgw" @@ -84,15 +88,19 @@ module "natgw" { idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } - public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) - public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + public_ip = try(merge(each.value.public_ip, { + name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" + }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { + name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" + }), null) tags = var.tags depends_on = [module.vnet] } +### Create Load Balancers, both internal and external ### -# create load balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -109,7 +117,8 @@ module "load_balancer" { 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}", + "${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( @@ -137,9 +146,65 @@ module "load_balancer" { depends_on = [module.vnet] } +### Create Application Gateways ### + +locals { + nics_with_appgw_key = flatten([ + for k, v in var.vmseries : [ + for nic in v.interfaces : { + vm_key = k + nic_name = nic.name + appgw_key = nic.application_gateway_key + } if nic.application_gateway_key != null + ]]) + + ips_4_nics_with_appgw_key = { + for v in local.nics_with_appgw_key : + v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... + } +} + +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + zones = each.value.zones + public_ip = merge( + each.value.public_ip, + { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } + ) + domain_name_label = each.value.domain_name_label + capacity = each.value.capacity + enable_http2 = each.value.enable_http2 + waf = each.value.waf + managed_identities = each.value.managed_identities + global_ssl_policy = each.value.global_ssl_policy + ssl_profiles = each.value.ssl_profiles + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = merge( + each.value.backend_pool, + length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } + ) + backend_settings = each.value.backend_settings + probes = each.value.probes + rewrites = each.value.rewrites + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + rules = each.value.rules + + tags = var.tags + depends_on = [module.vnet, module.vmseries] +} +### Create VM-Series VMs and closely associated resources ### -# create the actual VM-Series VMs and resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -147,9 +212,11 @@ module "ngfw_metrics" { create_workspace = var.ngfw_metrics.create_workspace - name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" - resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) - location = var.location + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( + coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + ) + location = var.location log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -161,10 +228,11 @@ module "ngfw_metrics" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : - k => v.virtual_machine + k => merge(v.virtual_machine, { vnet_key = v.vnet_key }) if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) } @@ -254,6 +322,7 @@ module "bootstrap" { tags = var.tags } +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set resource "azurerm_availability_set" "this" { for_each = var.availability_sets @@ -286,8 +355,10 @@ module "vmseries" { coalesce( each.value.virtual_machine.bootstrap_options, join(",", [ - "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", "file-share=${each.key}", "share-directory=None" ]), @@ -298,10 +369,12 @@ module "vmseries" { ) interfaces = [for v in each.value.interfaces : { - name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] - create_public_ip = v.create_public_ip - public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${ + var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip") + }" : v.public_ip_name public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address attach_to_lb_backend_pool = v.load_balancer_key != null @@ -317,60 +390,3 @@ module "vmseries" { module.bootstrap, ] } - -# Create Application Gateway - -locals { - nics_with_appgw_key = flatten([ - for k, v in var.vmseries : [ - for nic in v.interfaces : { - vm_key = k - nic_name = nic.name - appgw_key = nic.application_gateway_key - } if nic.application_gateway_key != null - ]]) - - ips_4_nics_with_appgw_key = { - for v in local.nics_with_appgw_key : - v.appgw_key => module.vmseries[v.vm_key].interfaces["${var.name_prefix}${v.nic_name}"].private_ip_address... - } -} - -module "appgw" { - source = "../../modules/appgw" - - for_each = var.appgws - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name - location = var.location - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - - zones = each.value.zones - public_ip = merge( - each.value.public_ip, - { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" } - ) - domain_name_label = each.value.domain_name_label - capacity = each.value.capacity - enable_http2 = each.value.enable_http2 - waf = each.value.waf - managed_identities = each.value.managed_identities - global_ssl_policy = each.value.global_ssl_policy - ssl_profiles = each.value.ssl_profiles - frontend_ip_configuration_name = each.value.frontend_ip_configuration_name - listeners = each.value.listeners - backend_pool = merge( - each.value.backend_pool, - length(local.ips_4_nics_with_appgw_key) == 0 ? {} : { vmseries_ips = local.ips_4_nics_with_appgw_key[each.key] } - ) - backend_settings = each.value.backend_settings - probes = each.value.probes - rewrites = each.value.rewrites - redirects = each.value.redirects - url_path_maps = each.value.url_path_maps - rules = each.value.rules - - tags = var.tags - depends_on = [module.vnet, module.vmseries] -} diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index d401eaab..8a26966e 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -1,14 +1,4 @@ -### 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 -} +### GENERAL ### variable "name_prefix" { description = <<-EOF @@ -45,33 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -125,21 +123,21 @@ variable "natgws" { For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). Following properties are supported: - - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), - created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. - - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full - resource name, including prefixes. - - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing - one). - - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped - AzureRM will pick a zone. - - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. - - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this - NAT Gateway will be assigned to. - - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined - in `var.vnets` for a VNET described by `vnet_name`. - - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. - - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, + defined in `var.vnets` for a VNET described by `vnet_name`. + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `resource_group_name` - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + Azure will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. Example: ``` @@ -158,13 +156,13 @@ variable "natgws" { EOF default = {} type = map(object({ - create_natgw = optional(bool, true) name = string + vnet_key = string + subnet_keys = list(string) + create_natgw = optional(bool, true) resource_group_name = optional(string) zone = optional(string) idle_timeout = optional(number, 4) - vnet_key = string - subnet_keys = list(string) public_ip = optional(object({ create = bool name = string @@ -179,12 +177,11 @@ variable "natgws" { })) } +### LOAD BALANCING ### - -### Load Balancing variable "load_balancers" { description = <<-EOF - A map containing configuration for all (private and public) Load Balancers. + A map containing configuration for all (both 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). @@ -194,26 +191,29 @@ variable "load_balancers" { - `name` - (`string`, required) a name of the Load Balancer. - `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`. - - `zones` - (`list`, optional, defaults to module defaults) a list of zones for Load Balancer's fronted IP + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module defaults) a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by - load balancing rules; please check + - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition - in the `var.vnets` map. - - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective - `in_rules` and `out_rules` + - `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 refer to + [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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. - Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + - `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, another property has been introduced instead: @@ -245,10 +245,10 @@ variable "load_balancers" { })) frontend_ips = optional(map(object({ name = string + subnet_key = optional(string) public_ip_name = optional(string) create_public_ip = optional(bool, false) public_ip_resource_group_name = optional(string) - subnet_key = optional(string) private_ip_address = optional(string) gwlb_key = optional(string) in_rules = optional(map(object({ @@ -272,29 +272,195 @@ variable "load_balancers" { })) } +variable "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. + + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, + refer to [module documentation](../../modules/appgw/README.md). + + **Note!** \ + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + It represents the Rules section of an Application Gateway in Azure Portal. + + Below you can find a brief list of most important properties: + + - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. + - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. + - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. + - `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. + - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. + - `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. + - `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. + - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. + - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. + - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + EOF + type = map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string) + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +} + +### VM-SERIES ### variable "availability_sets" { description = <<-EOF A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. Following properties are supported: - - `name` - name of the Application Insights. - - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). - - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). - Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). - Please verify how many update and fault domain are supported in a region before deploying this resource. + - `name` - (`string`, required) name of the Application Insights. + - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. + - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. + + **Note!** \ + Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability + Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. EOF default = {} nullable = false type = map(object({ name = string - update_domain_count = optional(number, 5) - fault_domain_count = optional(number, 3) + update_domain_count = optional(number) + fault_domain_count = optional(number) })) } - variable "ngfw_metrics" { description = <<-EOF A map controlling metrics-relates resources. @@ -302,22 +468,22 @@ variable "ngfw_metrics" { When set to explicit `null` (default) it will disable any metrics resources in this deployment. When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each - Scale Set). All instances will be automatically connected to the workspace. - The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + Scale Set). All instances will be automatically connected to the workspace. The name of the Application Insights instance will + be derived from the Scale Set name and suffixed with `-ai`. All the settings available below are common to the Log Analytics Workspace and Application Insight instances. Following properties are available: - - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `name` - (`string`, required) name of the (common) Log Analytics Workspace. - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log - Analytics Workspace + Analytics Workspace. - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting - the Log Analytics Workspace - - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. - - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in - days, possible values are between 30 and 730. For sourced Workspaces this applies only to - the Application Insights instances. + the Log Analytics Workspace. + - `sku` - (`string`, optional, defaults to module default) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module default) workspace and insights data retention in days, + possible values are between 30 and 730. For sourced Workspaces this applies only to the + Application Insights instances. EOF default = null type = object({ @@ -338,41 +504,50 @@ variable "bootstrap_storages" { - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - **Note** \ - For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ - Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case - letters and numbers. + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case letters + and numbers. - - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will - host (created) a Storage Account. When skipped the code will fall back to + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or + will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for - detailed documentation see - [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you - should pay attention to is: - - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in - the `name` property will be created or sourced. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. + + The property you should pay attention to is: + + - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property + will be created or sourced. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account, for details see - [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties - worth mentioning are: - - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the - `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to - work they also need to have the Storage Account Service Endpoint enabled. - - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described - in `allowed_subnet_keys`. - - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed - documentation see - [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The - properties you should pay your attention to are: - - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + storage account. + + The properties you should pay attention to are: + + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to work + they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - (`string`, optional) a key pointing to a VNET definition in the `var.vnets` map that stores the + Subnets described in `allowed_subnet_keys`. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). + + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. + + The properties you should pay attention to are: + + - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the `file_shares` property will be created or sourced. - - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module default) for sourced File Shares, controls if the bootstrap package folder structure will be created. + + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package configuration. For detailed description see [module's documentation](../../modules/bootstrap/README.md#file_shares). - EOF default = {} nullable = false @@ -410,127 +585,119 @@ variable "bootstrap_storages" { variable "vmseries" { description = <<-EOF - A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. The most basic properties are as follows: - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. - The `authentication` property is optional and holds the firewall admin access details. By default, standard username - `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). - **Note!** \ - The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to - `true`, then you have to specify `ssh_keys` property. - - For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - - `image` - (`map`, required) properties defining a base image used by the deployed VM. - - The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: - - - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. - - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. - - For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. - - The most often used option are as follows: - - - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment - Guide* as only a few selected sizes are supported. - - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) - public IP addresses will be created. - - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible - values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). - - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS - when launched for the 1st time, for details see module documentation. - - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the - bootstrap package. - - **Note!** \ - At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a - combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details - on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). - - Following properties are available: - - - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that - will host bootstrap packages. Each package will be hosted on a separate File Share. - The File Shares will be created automatically, one for each firewall. - - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File - Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) - property documentation for details. - - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap - package. - - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this - example is using full bootstrap method, the sample templates are in - [`templates`](./templates) folder. - - The templates are used to provide `day0` like configuration which consists of: - - - network interfaces configuration. - - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes - required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow - Inbound and OBEW traffic. - - *any-any* security rule. - - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. - - **Note!** \ - Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. - - When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. - - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when - `ngfw_metrics` module is defined and used in this example. The Application Insights - Instrumentation Key will be populated automatically. - - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all - private networks. When set it will override the private Subnet CIDR for inbound traffic - static routes. - - For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. - - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to - deploy network interfaces for deployed VM. + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). - - `interfaces` - (`list`, required) configuration of all network interfaces - - **Note!** \ - Order of the interfaces does matter - the 1<sup>st</sup> interface is the management one. + - `image` - (`map`, required) properties defining a base image used by the deployed VM. The `image` property is + required but there are only 2 properties (mutually exclusive) that have to be set, either: - For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + - `version` - (`string`, optional) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`, optional) absolute ID of your own custom PAN-OS image. - The most important ones are listed below: + For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). - - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in - `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. - - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. - - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` - variable, network interface that has this property defined will be added to the Load - Balancer's backend pool. - - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` - variable, network interface that has this property defined will be added to the Application - Gateway's backend pool. + - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + Most common properties are: + - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series + Deployment Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module default) the Availability Zone in which the VM and (if + deployed) public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module default) type of a Managed Disk which should be created, + possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected + `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a combination + of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details on the other + properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. The File + Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this example + is using full bootstrap method, the sample templates are in [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the Internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When + `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the + 1<sup>st</sup> interface is the management one. Most common properties are: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool. + - `application_gateway_key` - (`string`, optional, defaults to `null`) key of an Application Gateway defined in `var.appgws` + variable, network interface that has this property defined will be added to the Application + Gateway's backend pool. + + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#interfaces). EOF default = {} nullable = false type = map(object({ - name = string + name = string + vnet_key = string authentication = optional(object({ username = optional(string, "panadmin") password = optional(string) @@ -571,7 +738,6 @@ variable "vmseries" { identity_ids = optional(list(string)) allow_extension_operations = optional(bool) }) - vnet_key = string interfaces = list(object({ name = string subnet_key = string @@ -583,185 +749,26 @@ variable "vmseries" { application_gateway_key = optional(string) })) })) - validation { + validation { # virtual_machine.bootstrap_options & virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null ]) - error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + error_message = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF } - validation { + validation { # virtual_machine.bootstrap_package condition = alltrue([ for _, v in var.vmseries : - v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true - if v.virtual_machine.bootstrap_package != null + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? ( + v.virtual_machine.bootstrap_package.private_snet_key != null && + v.virtual_machine.bootstrap_package.public_snet_key != null + ) : true if v.virtual_machine.bootstrap_package != null ]) - error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF } } - -### Application Gateway -variable "appgws" { - description = <<-EOF - A map defining all Application Gateways in the current deployment. - - For detailed documentation on how to configure this resource, for available properties, especially for the defaults, - refer to [module documentation](../../modules/appgw/README.md). - - **Note!** \ - The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). - It represents the Rules section of an Application Gateway in Azure Portal. - - Below you can find a brief list of most important properties: - - - `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. - - `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. - - `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. - - `zones` - (`list`, optional, defaults to module defaults) parameter controlling if this is a zonal, or a non-zonal - deployment. - - `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. - - `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. - - `backend_pool` - (`map`, optional, defaults to module defaults) backend pool definition, when skipped an empty backend - will be created. - - `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - - `probes` - (`map`, optional, defaults to module defaults) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module defaults) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - - `rules - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - EOF - type = map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -} \ No newline at end of file diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index 1aa148b5..faa95bfb 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -9,8 +9,8 @@ The `README` is also in new, document-style format. Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`location`](#location) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -18,10 +18,10 @@ Name | Type | Description 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. -[`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of virtual_network_gateways to create. +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of Virtual Network Gateways to create. @@ -49,7 +49,7 @@ Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | -`vng` | - | ../../modules/virtual_network_gateway | Create virtual network gateway +`vng` | - | ../../modules/virtual_network_gateway | Resources used in this module: @@ -63,46 +63,45 @@ Resources used in this module: -#### location -The Azure region to use. +#### resource_group_name + +Name of the Resource Group. Type: string <sup>[back to list](#modules-required-inputs)</sup> +#### location - -#### resource_group_name - -Name of the Resource Group. +The Azure region to use. Type: string <sup>[back to list](#modules-required-inputs)</sup> + #### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). Type: @@ -161,28 +160,20 @@ map(object({ ### Optional Inputs -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - #### 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 +``` 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. +**Note!** \ +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 @@ -193,7 +184,9 @@ Default value: `` #### 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 `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. @@ -205,9 +198,20 @@ Default value: `true` +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + #### virtual_network_gateways -Map of virtual_network_gateways to create. +Map of Virtual Network Gateways to create. Type: diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars index b774e6b2..c222b56b 100644 --- a/examples/virtual_network_gateway/example.tfvars +++ b/examples/virtual_network_gateway/example.tfvars @@ -1,4 +1,5 @@ -# --- GENERAL --- # +### GENERAL ### + location = "North Europe" resource_group_name = "vng" name_prefix = "example-" @@ -7,8 +8,8 @@ tags = { "CreatedWith" = "Terraform" } +### NETWORK ### -# --- VNET PART --- # vnets = { transit = { name = "transit" @@ -60,7 +61,8 @@ vnets = { } } -# --- VNG PART --- # +### VIRTUAL NETWORK GATEWAY ### + virtual_network_gateways = { expressroute = { name = "expressroute" diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf index 959871c6..85ed5850 100644 --- a/examples/virtual_network_gateway/main.tf +++ b/examples/virtual_network_gateway/main.tf @@ -1,4 +1,5 @@ -# Create or source the Resource Group. +### Generate a random password ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 @@ -8,6 +9,8 @@ resource "azurerm_resource_group" "this" { tags = var.tags } +### Create or source a Resource Group ### + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { count = var.create_resource_group ? 0 : 1 @@ -18,6 +21,8 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } +### Manage the network required for the topology ### + module "vnet" { source = "../../modules/vnet" @@ -33,15 +38,18 @@ module "vnet" { create_subnets = each.value.create_subnets subnets = each.value.subnets - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + network_security_groups = { + for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + route_tables = { + for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) } tags = var.tags } -# Create virtual network gateway +### Create Virtual Network Gateways ### + module "vng" { source = "../../modules/virtual_network_gateway" @@ -66,4 +74,4 @@ module "vng" { vpn_clients = each.value.vpn_clients tags = var.tags -} \ No newline at end of file +} diff --git a/examples/virtual_network_gateway/outputs.tf b/examples/virtual_network_gateway/outputs.tf index 469cc0fd..2ce3480a 100644 --- a/examples/virtual_network_gateway/outputs.tf +++ b/examples/virtual_network_gateway/outputs.tf @@ -6,4 +6,4 @@ output "vng_public_ips" { output "vng_ipsec_policy" { description = "IPsec policy used for Virtual Network Gateway connection" value = length(var.virtual_network_gateways) > 0 ? { for k, v in module.vng : k => v.ipsec_policy } : null -} \ No newline at end of file +} diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf index 7a933bc1..1173e6a7 100644 --- a/examples/virtual_network_gateway/variables.tf +++ b/examples/virtual_network_gateway/variables.tf @@ -1,26 +1,19 @@ -### 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 -} +### GENERAL ### 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 + ``` 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. + **Note!** \ + 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 @@ -28,7 +21,9 @@ variable "name_prefix" { 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 `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 @@ -40,32 +35,41 @@ variable "resource_group_name" { type = string } +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +### NETWORK ### -### 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 `true`) 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_virtual_network` - (`bool`, optional, defaults to `true`) 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`, 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`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, - otherwise use source existing subnets + 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) + [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) + [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) + [VNET module documentation](../../modules/vnet/README.md#route_tables). EOF - type = map(object({ name = string resource_group_name = optional(string) @@ -110,9 +114,10 @@ variable "vnets" { })) } -### Virtual Network Gateway +### VIRTUAL NETWORK GATEWAY ### + variable "virtual_network_gateways" { - description = "Map of virtual_network_gateways to create." + description = "Map of Virtual Network Gateways to create." default = {} nullable = false type = map(object({ diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index aacbc7fc..79141b9b 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -91,7 +91,7 @@ resource "azurerm_application_gateway" "this" { public_ip_address_id = try(azurerm_public_ip.this[0].id, data.azurerm_public_ip.this[0].id) } - # There is only a single backend - the VMSeries private IPs assigned to untrusted NICs + # There is only a single backend - the VM-Series private IPs assigned to untrusted NICs backend_address_pool { name = var.backend_pool.name ip_addresses = var.backend_pool.vmseries_ips @@ -285,11 +285,15 @@ resource "azurerm_application_gateway" "this" { for_each = url_path_map.value.path_rules content { - name = path_rule.key - paths = path_rule.value.paths - backend_address_pool_name = path_rule.value.backend_key != null ? var.backend_pool.name : null - backend_http_settings_name = path_rule.value.backend_key != null ? var.backend_settings[path_rule.value.backend_key].name : null - redirect_configuration_name = path_rule.value.redirect_key != null ? var.redirects[path_rule.value.redirect_key].name : null + name = path_rule.key + paths = path_rule.value.paths + backend_address_pool_name = path_rule.value.backend_key != null ? var.backend_pool.name : null + backend_http_settings_name = path_rule.value.backend_key != null ? ( + var.backend_settings[path_rule.value.backend_key].name + ) : null + redirect_configuration_name = path_rule.value.redirect_key != null ? ( + var.redirects[path_rule.value.redirect_key].name + ) : null } } } @@ -317,7 +321,9 @@ resource "azurerm_application_gateway" "this" { request_routing_rule.value.rewrite_key != null ? var.rewrites[request_routing_rule.value.rewrite_key].name : null ) url_path_map_name = ( - request_routing_rule.value.url_path_map_key != null ? var.url_path_maps[request_routing_rule.value.url_path_map_key].name : null + request_routing_rule.value.url_path_map_key != null ? ( + var.url_path_maps[request_routing_rule.value.url_path_map_key].name + ) : null ) } } @@ -326,7 +332,9 @@ resource "azurerm_application_gateway" "this" { precondition { condition = var.probes != null ? alltrue(flatten([ for k, probe in var.probes : probe.host != null || alltrue(flatten([ - for b, backend in var.backend_settings : backend.probe_key == k ? backend.hostname != null || backend.hostname_from_backend : true + for b, backend in var.backend_settings : backend.probe_key == k ? ( + backend.hostname != null || backend.hostname_from_backend + ) : true ])) ])) : true error_message = <<EOF diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index e3be44f5..5f4f23bc 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -49,7 +49,7 @@ This code will create a storage account for 3 NGFWs. Please **note** that: ```hcl module "bootstrap" { - source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" + source = "../../modules/bootstrap" name = "samplebootstrapstorage" resource_group_name = "rg-name" diff --git a/modules/bootstrap/main.tf b/modules/bootstrap/main.tf index 55b833e8..a1d62a93 100644 --- a/modules/bootstrap/main.tf +++ b/modules/bootstrap/main.tf @@ -23,8 +23,10 @@ resource "azurerm_storage_account" "this" { resource "azurerm_storage_account_network_rules" "this" { count = var.storage_account.create ? 1 : 0 - storage_account_id = azurerm_storage_account.this[0].id - default_action = length(var.storage_network_security.allowed_public_ips) > 0 || length(var.storage_network_security.allowed_subnet_ids) > 0 ? "Deny" : "Allow" + storage_account_id = azurerm_storage_account.this[0].id + default_action = length(var.storage_network_security.allowed_public_ips) > 0 || ( + length(var.storage_network_security.allowed_subnet_ids) > 0 + ) ? "Deny" : "Allow" ip_rules = var.storage_network_security.allowed_public_ips virtual_network_subnet_ids = var.storage_network_security.allowed_subnet_ids } @@ -67,7 +69,9 @@ data "azurerm_storage_share" "this" { } locals { - file_shares = var.file_shares_configuration.create_file_shares ? azurerm_storage_share.this : data.azurerm_storage_share.this + file_shares = var.file_shares_configuration.create_file_shares ? ( + azurerm_storage_share.this + ) : data.azurerm_storage_share.this package_folders = ["content", "config", "software", "plugins", "license"] } @@ -127,8 +131,8 @@ locals { } } - # 3. Compare both packages using destinations. There is no real comparison being made. We simply merge both maps, the latter one - # takes precedence (we simply use the mechanism of the `merge` function). + # 3. Compare both packages using destinations. There is no real comparison being made. We simply merge both maps, the latter + # one takes precedence (we simply use the mechanism of the `merge` function). inverted_filenames = { for k, _ in var.file_shares : k => merge(local.bootstrap_filenames[k], local.inverted_files[k]) } diff --git a/modules/gwlb/outputs.tf b/modules/gwlb/outputs.tf index 729fc2d4..2168db7f 100644 --- a/modules/gwlb/outputs.tf +++ b/modules/gwlb/outputs.tf @@ -6,4 +6,4 @@ output "backend_pool_ids" { output "frontend_ip_config_id" { description = "Frontend IP configuration identifier." value = azurerm_lb.this.frontend_ip_configuration[0].id -} \ No newline at end of file +} diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index d560e521..a4496e7b 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -110,7 +110,9 @@ Name | Type | Description 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. +`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 @@ -225,11 +227,10 @@ Below are the properties for the `in_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`. +**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 diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index 0f23661b..ca63cfaf 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -1,6 +1,6 @@ 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. + # 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 @@ -63,8 +63,10 @@ resource "azurerm_lb" "this" { for_each = var.frontend_ips iterator = frontend_ip content { - 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) + 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 @@ -83,7 +85,10 @@ resource "azurerm_lb" "this" { [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." + error_message = <<-EOF + All frontends have to be of the same type, either public or private. Please check module's documentation (Usage section) + for details. + EOF } } } @@ -123,9 +128,11 @@ resource "azurerm_lb_probe" "this" { loadbalancer_id = azurerm_lb.this.id - 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 + 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 @@ -175,10 +182,13 @@ resource "azurerm_lb_outbound_rule" "this" { locals { # Map of all frontend IP addresses, public or private. frontend_addresses = { - 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) + 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. + # 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) @@ -193,7 +203,8 @@ 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 IPs: ${join(",", var.nsg_auto_rules_settings.source_ips)}" + 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" @@ -203,10 +214,12 @@ resource "azurerm_network_security_rule" "this" { 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. + # 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 + 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/outputs.tf b/modules/loadbalancer/outputs.tf index a525587e..fb03b34a 100644 --- a/modules/loadbalancer/outputs.tf +++ b/modules/loadbalancer/outputs.tf @@ -4,7 +4,10 @@ output "backend_pool_id" { } output "frontend_ip_configs" { - description = "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." + description = <<-EOF + 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. + EOF value = local.frontend_addresses } diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 4f0bde88..e76ba3e1 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -106,11 +106,10 @@ variable "frontend_ips" { 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`. + **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 diff --git a/modules/name_templater/main.tf b/modules/name_templater/main.tf index f3d0d6cf..66c3601c 100644 --- a/modules/name_templater/main.tf +++ b/modules/name_templater/main.tf @@ -31,4 +31,4 @@ locals { template_trimmed = trim(local.template_randomized, var.name_template.delimiter) -} \ No newline at end of file +} diff --git a/modules/name_templater/outputs.tf b/modules/name_templater/outputs.tf index 25971ef1..a7964c07 100644 --- a/modules/name_templater/outputs.tf +++ b/modules/name_templater/outputs.tf @@ -1,4 +1,4 @@ output "template" { description = "A template string that can be used with `format` function to generate a resource name." value = local.template_trimmed -} \ No newline at end of file +} diff --git a/modules/ngfw_metrics/main.tf b/modules/ngfw_metrics/main.tf index a41d5191..db2127b4 100644 --- a/modules/ngfw_metrics/main.tf +++ b/modules/ngfw_metrics/main.tf @@ -28,9 +28,13 @@ resource "azurerm_application_insights" "this" { resource_group_name = coalesce(each.value.resource_group_name, var.resource_group_name) location = var.location - workspace_id = var.create_workspace ? azurerm_log_analytics_workspace.this[0].id : data.azurerm_log_analytics_workspace.this[0].id - application_type = "other" - retention_in_days = each.value.metrics_retention_in_days == null ? var.log_analytics_workspace.metrics_retention_in_days : each.value.metrics_retention_in_days + workspace_id = var.create_workspace ? ( + azurerm_log_analytics_workspace.this[0].id + ) : data.azurerm_log_analytics_workspace.this[0].id + application_type = "other" + retention_in_days = each.value.metrics_retention_in_days == null ? ( + var.log_analytics_workspace.metrics_retention_in_days + ) : each.value.metrics_retention_in_days tags = var.tags } diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 681ffb28..543015de 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -45,6 +45,7 @@ Name | Type | Description Name | Description --- | --- `mgmt_ip_address` | Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address. + `interfaces` | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. ## Module's Nameplate diff --git a/modules/panorama/outputs.tf b/modules/panorama/outputs.tf index 03f2ffd5..5655c631 100644 --- a/modules/panorama/outputs.tf +++ b/modules/panorama/outputs.tf @@ -1,5 +1,7 @@ output "mgmt_ip_address" { - description = "Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address." + description = <<-EOF + Panorama management IP address. If `public_ip` was `true`, it is a public IP address, otherwise a private IP address. + EOF value = try( azurerm_public_ip.this[var.interfaces[0].name].ip_address, azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address diff --git a/modules/virtual_machine/main.tf b/modules/virtual_machine/main.tf index 6ac29247..b6941840 100644 --- a/modules/virtual_machine/main.tf +++ b/modules/virtual_machine/main.tf @@ -34,7 +34,9 @@ resource "azurerm_network_interface" "this" { subnet_id = var.interfaces[count.index].subnet_id private_ip_address_allocation = try(var.interfaces[count.index].private_ip_address, null) != null ? "Static" : "Dynamic" private_ip_address = try(var.interfaces[count.index].private_ip_address, null) - public_ip_address_id = try(azurerm_public_ip.this[count.index].id, var.interfaces[count.index].public_ip_address_id, null) + public_ip_address_id = try( + azurerm_public_ip.this[count.index].id, var.interfaces[count.index].public_ip_address_id, null + ) } } @@ -134,4 +136,4 @@ resource "azurerm_virtual_machine" "this" { type = var.identity_type identity_ids = var.identity_ids } -} \ No newline at end of file +} diff --git a/modules/virtual_machine/outputs.tf b/modules/virtual_machine/outputs.tf index d3bf04b5..6b8a0ec8 100644 --- a/modules/virtual_machine/outputs.tf +++ b/modules/virtual_machine/outputs.tf @@ -4,11 +4,18 @@ output "public_ips" { } output "interfaces" { - description = "List of interfaces. The elements of the list are `azurerm_network_interface` objects. The order is the same as `interfaces` input." + description = <<-EOF + List of interfaces. The elements of the list are `azurerm_network_interface` objects. The order is the same as `interfaces` + input. + EOF value = azurerm_network_interface.this } output "principal_id" { - description = "The oid of Azure Service Principal of the created virtual machine. Usable only if `identity_type` contains SystemAssigned." - value = var.identity_type != null && var.identity_type != "" ? azurerm_virtual_machine.this.identity[0].principal_id : null + description = <<-EOF + The oid of Azure Service Principal of the created virtual machine. Usable only if `identity_type` contains SystemAssigned. + EOF + value = var.identity_type != null && var.identity_type != "" ? ( + azurerm_virtual_machine.this.identity[0].principal_id + ) : null } diff --git a/modules/virtual_machine/variables.tf b/modules/virtual_machine/variables.tf index aa94a11f..e58f92f7 100644 --- a/modules/virtual_machine/variables.tf +++ b/modules/virtual_machine/variables.tf @@ -14,13 +14,19 @@ variable "name" { } variable "enable_zones" { - description = "If false, the input `avzone` is ignored 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." + description = <<-EOF + If false, the input `avzone` is ignored 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. + EOF default = true type = bool } variable "avzone" { - description = "The availability zone to use, for example \"1\", \"2\", \"3\". Ignored if `enable_zones` is false. Conflicts with `avset_id`, in which case use `avzone = null`." + description = <<-EOF + The availability zone to use, for example \"1\", \"2\", \"3\". Ignored if `enable_zones` is false. Conflicts with `avset_id`, + in which case use `avzone = null`. + EOF default = "1" type = string } @@ -49,12 +55,19 @@ variable "interfaces" { - `subnet_id` - (required|string) Identifier of an existing subnet to create interface in. - `private_ip_address` - (optional|string) Static private IP to asssign to the interface. If null, dynamic one is allocated. - `public_ip_address_id` - (optional|string) Identifier of an existing public IP to associate. - - `create_public_ip` - (optional|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. Default is false. - - `availability_zone` - (optional|string) Availability zone to create public IP in. If not specified, set based on `avzone` and `enable_zones`. - - `enable_ip_forwarding` - (optional|bool) If true, the network interface will not discard packets sent to an IP address other than the one assigned. If false, the network interface only accepts traffic destined to its IP address. - - `enable_backend_pool` - (optional|bool) If true, associate interface with backend pool specified with `lb_backend_pool_id`. Default is false. - - `lb_backend_pool_id` - (optional|string) Identifier of an existing backend pool to associate interface with. Required if `enable_backend_pool` is true. - - `tags` - (optional|map) Tags to assign to the interface and public IP (if created). Overrides contents of `tags` variable. + - `create_public_ip` - (optional|bool) If true, create a public IP for the interface and ignore the `public_ip_address_id`. + Default is false. + - `availability_zone` - (optional|string) Availability zone to create public IP in. If not specified, set based on `avzone` + and `enable_zones`. + - `enable_ip_forwarding` - (optional|bool) If true, the network interface will not discard packets sent to an IP address other + than the one assigned. If false, the network interface only accepts traffic destined to its IP + address. + - `enable_backend_pool` - (optional|bool) If true, associate interface with backend pool specified with `lb_backend_pool_id`. + Default is false. + - `lb_backend_pool_id` - (optional|string) Identifier of an existing backend pool to associate interface with. Required if + `enable_backend_pool` is true. + - `tags` - (optional|map) Tags to assign to the interface and public IP (if created). Overrides contents of + `tags` variable. Example: @@ -78,24 +91,36 @@ variable "interfaces" { } variable "bootstrap_storage_account" { - description = "Existing storage account object for bootstrapping and for holding small-sized boot diagnostics. Usually the object is passed from a bootstrap module's output." + description = <<-EOF + Existing storage account object for bootstrapping and for holding small-sized boot diagnostics. Usually the object is passed + from a bootstrap module's output. + EOF default = null type = any } variable "bootstrap_share_name" { - description = "Azure File Share holding the bootstrap data. Should reside on `bootstrap_storage_account`. Bootstrapping is omitted if `bootstrap_share_name` is left at null." + description = <<-EOF + Azure File Share holding the bootstrap data. Should reside on `bootstrap_storage_account`. Bootstrapping is omitted if + `bootstrap_share_name` is left at null. + EOF default = null type = string } variable "username" { - description = "Initial administrative username to use for the virtual machine. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm)." + description = <<-EOF + Initial administrative username to use for the virtual machine. Mind the + [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-username-requirements-when-creating-a-vm). + EOF type = string } variable "password" { - description = "Initial administrative password to use for the virtual machine. If not defined the `ssh_key` variable must be specified. Mind the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm)." + description = <<-EOF + Initial administrative password to use for the virtual machine. If not defined the `ssh_key` variable must be specified. Mind + the [Azure-imposed restrictions](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/faq#what-are-the-password-requirements-when-creating-a-vm). + EOF default = null type = string sensitive = true @@ -103,9 +128,11 @@ variable "password" { variable "ssh_keys" { description = <<-EOF - A list of initial administrative SSH public keys that allow key-pair authentication. If not defined the `password` variable must be specified. + A list of initial administrative SSH public keys that allow key-pair authentication. If not defined the `password` variable + must be specified. - This is a list of strings, so each item should be the actual public key value. If you would like to load them from files instead, following method is available: + This is a list of strings, so each item should be the actual public key value. If you would like to load them from files + instead, following method is available: ``` [ @@ -119,7 +146,10 @@ variable "ssh_keys" { } variable "managed_disk_type" { - description = "Type of OS Managed Disk to create for the virtual machine. Possible values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs." + description = <<-EOF + Type of OS Managed Disk to create for the virtual machine. Possible values are `Standard_LRS`, `StandardSSD_LRS` or + `Premium_LRS`. The `Premium_LRS` works only for selected `vm_size` values, details in Azure docs. + EOF default = "StandardSSD_LRS" type = string } @@ -137,7 +167,11 @@ variable "vm_size" { } variable "custom_image_id" { - description = "Absolute ID of your own Custom Image to be used for creating a new virtual machine. If set, the `username`, `password`, `img_version`, `img_publisher`, `img_offer`, `img_sku` inputs are all ignored (these are used only for published images, not custom ones)." + description = <<-EOF + Absolute ID of your own Custom Image to be used for creating a new virtual machine. If set, the `username`, `password`, + `img_version`, `img_publisher`, `img_offer`, `img_sku` inputs are all ignored (these are used only for published images, not + custom ones). + EOF default = null type = string } @@ -161,19 +195,27 @@ variable "img_sku" { } variable "img_version" { - description = "Virtual machine image version - list available for a default `img_offer` with `az vm image list -o table --publisher foo --offer bar --all`" + description = <<-EOF + Virtual machine image version - list available for a default `img_offer` with + `az vm image list -o table --publisher foo --offer bar --all`. + EOF default = "latest" type = string } variable "enable_plan" { - description = "Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku \"byol\", which means \"bring your own license\", still requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image." + description = <<-EOF + Enable usage of the Offer/Plan on Azure Marketplace. Even plan sku \"byol\", which means \"bring your own license\", still + requires accepting on the Marketplace (as of 2021). Can be set to `false` when using a custom image. + EOF default = false type = bool } variable "vm_os_simple" { - description = "Allows user to specify a simple name for the OS required and auto populate the publisher, offer, sku parameters" + description = <<-EOF + Allows user to specify a simple name for the OS required and auto populate the publisher, offer, sku parameters. + EOF default = "UbuntuServer" type = string } @@ -181,7 +223,7 @@ variable "vm_os_simple" { variable "standard_os" { description = <<-EOF - Definition of the standard OS with "SimpleName" = "publisher,offer,sku" + Definition of the standard OS with "SimpleName" = "publisher,offer,sku". EOF default = { "UbuntuServer" = "Canonical,UbuntuServer,18.04-LTS" @@ -202,19 +244,23 @@ variable "tags" { } variable "identity_type" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_type)." + description = <<-EOF + See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_type). + EOF default = "SystemAssigned" type = string } variable "identity_ids" { - description = "See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_ids)." + description = <<-EOF + See the [provider documentation](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#identity_ids). + EOF default = null type = list(string) } variable "accelerated_networking" { - description = "Enable Azure accelerated networking (SR-IOV) for all network interfaces" + description = "Enable Azure accelerated networking (SR-IOV) for all network interfaces." default = true type = bool } diff --git a/modules/virtual_network_gateway/outputs.tf b/modules/virtual_network_gateway/outputs.tf index 6e99cbf0..5fc7308e 100644 --- a/modules/virtual_network_gateway/outputs.tf +++ b/modules/virtual_network_gateway/outputs.tf @@ -16,4 +16,4 @@ output "ipsec_policy" { ipsec_integrity = v.ipsec_policy[0].ipsec_integrity pfs_group = v.ipsec_policy[0].pfs_group } if length(v.ipsec_policy) > 0 } -} \ No newline at end of file +} diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 3b2b93d1..6c3e78a6 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -58,9 +58,11 @@ Name | Type | Description Name | Description --- | --- `mgmt_ip_address` | VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address. + `interfaces` | Map of VM-Series network interfaces. Keys are equal to var.interfaces `name` properties. `principal_id` | The ID of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned. + ## Module's Nameplate @@ -206,13 +208,13 @@ List of either required or important properties: - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. - `bootstrap_options` - bootstrap options to pass to VM-Series instance. - Proper syntax is a string of semicolon separated properties, for example: + Proper syntax is a string of semicolon separated properties, for example: - ```hcl - bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" - ``` + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` - For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). List of other, optional properties: diff --git a/modules/vmseries/main.tf b/modules/vmseries/main.tf index f94640bf..202a44e0 100644 --- a/modules/vmseries/main.tf +++ b/modules/vmseries/main.tf @@ -130,4 +130,4 @@ resource "azurerm_network_interface_backend_address_pool_association" "this" { azurerm_network_interface.this, azurerm_linux_virtual_machine.this ] -} \ No newline at end of file +} diff --git a/modules/vmseries/outputs.tf b/modules/vmseries/outputs.tf index ee9edf3b..c251e634 100644 --- a/modules/vmseries/outputs.tf +++ b/modules/vmseries/outputs.tf @@ -1,5 +1,7 @@ output "mgmt_ip_address" { - description = "VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address." + description = <<-EOF + VM-Series management IP address. If `create_public_ip` was `true`, it is a public IP address, otherwise a private IP address. + EOF value = try( azurerm_public_ip.this[var.interfaces[0].name].ip_address, azurerm_network_interface.this[var.interfaces[0].name].ip_configuration[0].private_ip_address @@ -12,6 +14,8 @@ output "interfaces" { } output "principal_id" { - description = "The ID of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned." + description = <<-EOF + The ID of Azure Service Principal of the created VM-Series. Usable only if `identity_type` contains SystemAssigned. + EOF value = var.virtual_machine.identity_type != null ? azurerm_linux_virtual_machine.this.identity[0].principal_id : null } diff --git a/modules/vmseries/variables.tf b/modules/vmseries/variables.tf index c18aace5..d57252bf 100644 --- a/modules/vmseries/variables.tf +++ b/modules/vmseries/variables.tf @@ -105,13 +105,13 @@ variable "virtual_machine" { - `disk_name` - (`string`, optional, defaults to VM name + `-disk` suffix) name od the OS disk. - `bootstrap_options` - bootstrap options to pass to VM-Series instance. - Proper syntax is a string of semicolon separated properties, for example: + Proper syntax is a string of semicolon separated properties, for example: - ```hcl - bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" - ``` + ```hcl + bootstrap_options = "type=dhcp-client;panorama-server=1.2.3.4" + ``` - For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). + For more details on bootstrapping [see documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components). List of other, optional properties: diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 8e004880..e155fd95 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -61,7 +61,7 @@ Below you can find a simple example deploying a Scale Set w/o autoscaling, using ```hcl module "vmss" { - source = "PaloAltoNetworks/swfw-modules/azurerm//modules/vmss" + source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmss" name = "ngfw-vmss" resource_group_name = "hub-rg" diff --git a/modules/vmss/main.tf b/modules/vmss/main.tf index 90bbc696..1d994d27 100644 --- a/modules/vmss/main.tf +++ b/modules/vmss/main.tf @@ -50,16 +50,18 @@ resource "azurerm_linux_virtual_machine_scale_set" "this" { source_image_id = var.image.custom_id os_disk { caching = "ReadWrite" - disk_encryption_set_id = var.virtual_machine_scale_set.disk_encryption_set_id # The Disk Encryption Set must have the Reader Role Assignment scoped on the Key Vault - in addition to an Access Policy to the Key Vault. + disk_encryption_set_id = var.virtual_machine_scale_set.disk_encryption_set_id # the Disk Encryption Set must have the Reader Role Assignment scoped on the Key Vault, in addition to an Access Policy to the Key Vault storage_account_type = var.virtual_machine_scale_set.disk_type } instances = var.autoscaling_configuration.default_count - upgrade_mode = "Manual" # See README for more details no this setting. + upgrade_mode = "Manual" # see README for more details no this setting - custom_data = var.virtual_machine_scale_set.bootstrap_options == null ? null : base64encode(var.virtual_machine_scale_set.bootstrap_options) + custom_data = var.virtual_machine_scale_set.bootstrap_options == null ? ( + null + ) : base64encode(var.virtual_machine_scale_set.bootstrap_options) scale_in { rule = var.autoscaling_configuration.scale_in_policy @@ -230,23 +232,33 @@ resource "azurerm_monitor_autoscale_setting" "this" { for_each = profile.value.scale_rules content { metric_trigger { - metric_name = rule.value.name - metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id - metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" - operator = local.operator[rule.value.scale_out_config.operator] + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? ( + var.autoscaling_configuration.application_insights_id + ) : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? ( + "Azure.ApplicationInsights" + ) : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_out_config.operator] threshold = rule.value.scale_out_config.threshold statistic = rule.value.scale_out_config.grain_aggregation_type time_aggregation = rule.value.scale_out_config.aggregation_window_type - time_grain = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string - time_window = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + time_grain = module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes" + ].dt_string + time_window = module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes" + ].dt_string } scale_action { direction = "Increase" value = rule.value.scale_out_config.change_count_by type = "ChangeCount" - cooldown = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes"].dt_string + cooldown = module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes" + ].dt_string } } } @@ -256,21 +268,33 @@ resource "azurerm_monitor_autoscale_setting" "this" { for_each = profile.value.scale_rules content { metric_trigger { - metric_name = rule.value.name - metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id - metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" - operator = local.operator[rule.value.scale_in_config.operator] + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? ( + var.autoscaling_configuration.application_insights_id + ) : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? ( + "Azure.ApplicationInsights" + ) : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_in_config.operator] threshold = rule.value.scale_in_config.threshold statistic = rule.value.scale_in_config.grain_aggregation_type time_aggregation = rule.value.scale_in_config.aggregation_window_type time_grain = try( - module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes"].dt_string, - module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes" + ].dt_string, + module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes" + ].dt_string ) time_window = try( - module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes"].dt_string, - module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes" + ].dt_string, + module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes" + ].dt_string ) } @@ -278,7 +302,9 @@ resource "azurerm_monitor_autoscale_setting" "this" { direction = "Decrease" value = rule.value.scale_in_config.change_count_by type = "ChangeCount" - cooldown = module.ptd_time["${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes"].dt_string + cooldown = module.ptd_time[ + "${profile.value.name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes" + ].dt_string } } } @@ -313,23 +339,36 @@ resource "azurerm_monitor_autoscale_setting" "this" { for_each = var.autoscaling_profiles[0].scale_rules content { metric_trigger { - metric_name = rule.value.name - metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id - metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" - operator = local.operator[rule.value.scale_out_config.operator] + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? ( + var.autoscaling_configuration.application_insights_id + ) : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? ( + "Azure.ApplicationInsights" + ) : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_out_config.operator] threshold = rule.value.scale_out_config.threshold statistic = rule.value.scale_out_config.grain_aggregation_type time_aggregation = rule.value.scale_out_config.aggregation_window_type - time_grain = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string - time_window = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + time_grain = module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace( + lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes" + ].dt_string + time_window = module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace( + lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes" + ].dt_string } scale_action { direction = "Increase" value = rule.value.scale_out_config.change_count_by type = "ChangeCount" - cooldown = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes"].dt_string + cooldown = module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace( + lower(rule.value.name), " ", "_")}-scale_out-cooldown_window_minutes" + ].dt_string } } } @@ -339,21 +378,35 @@ resource "azurerm_monitor_autoscale_setting" "this" { for_each = var.autoscaling_profiles[0].scale_rules content { metric_trigger { - metric_name = rule.value.name - metric_resource_id = contains(local.panos_metrics, rule.value.name) ? var.autoscaling_configuration.application_insights_id : azurerm_linux_virtual_machine_scale_set.this.id - metric_namespace = contains(local.panos_metrics, rule.value.name) ? "Azure.ApplicationInsights" : "microsoft.compute/virtualmachinescalesets" - operator = local.operator[rule.value.scale_in_config.operator] + metric_name = rule.value.name + metric_resource_id = contains(local.panos_metrics, rule.value.name) ? ( + var.autoscaling_configuration.application_insights_id + ) : azurerm_linux_virtual_machine_scale_set.this.id + metric_namespace = contains(local.panos_metrics, rule.value.name) ? ( + "Azure.ApplicationInsights" + ) : "microsoft.compute/virtualmachinescalesets" + operator = local.operator[rule.value.scale_in_config.operator] threshold = rule.value.scale_in_config.threshold statistic = rule.value.scale_in_config.grain_aggregation_type time_aggregation = rule.value.scale_in_config.aggregation_window_type time_grain = try( - module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes"].dt_string, - module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes"].dt_string + module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-grain_window_minutes" + ].dt_string, + module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-grain_window_minutes" + ].dt_string ) time_window = try( - module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes"].dt_string, - module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes"].dt_string + module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace( + lower(rule.value.name), " ", "_")}-scale_in-aggregation_window_minutes" + ].dt_string, + module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace( + lower(rule.value.name), " ", "_")}-scale_out-aggregation_window_minutes" + ].dt_string ) } @@ -361,7 +414,9 @@ resource "azurerm_monitor_autoscale_setting" "this" { direction = "Decrease" value = rule.value.scale_in_config.change_count_by type = "ChangeCount" - cooldown = module.ptd_time["${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes"].dt_string + cooldown = module.ptd_time[ + "${var.autoscaling_profiles[0].name}-${replace(lower(rule.value.name), " ", "_")}-scale_in-cooldown_window_minutes" + ].dt_string } } } diff --git a/modules/vmss/outputs.tf b/modules/vmss/outputs.tf index c49ff837..6c5ee30c 100644 --- a/modules/vmss/outputs.tf +++ b/modules/vmss/outputs.tf @@ -12,4 +12,4 @@ output "password" { description = "Firewall admin password" value = local.password sensitive = true -} \ No newline at end of file +} diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 26dcb618..2419759b 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -137,5 +137,4 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet_peering/main.tf b/modules/vnet_peering/main.tf index d96db6a1..5019560e 100644 --- a/modules/vnet_peering/main.tf +++ b/modules/vnet_peering/main.tf @@ -32,4 +32,4 @@ resource "azurerm_virtual_network_peering" "remote" { allow_forwarded_traffic = var.remote_peer_config.allow_forwarded_traffic allow_gateway_transit = var.remote_peer_config.allow_gateway_transit use_remote_gateways = var.remote_peer_config.use_remote_gateways -} \ No newline at end of file +} diff --git a/modules/vnet_peering/outputs.tf b/modules/vnet_peering/outputs.tf index c43dbab8..4c489d3f 100644 --- a/modules/vnet_peering/outputs.tf +++ b/modules/vnet_peering/outputs.tf @@ -16,4 +16,4 @@ output "local_peering_id" { output "remote_peering_id" { description = "The ID of the remote VNET peering." value = azurerm_virtual_network_peering.remote.id -} \ No newline at end of file +} diff --git a/modules/vnet_peering/variables.tf b/modules/vnet_peering/variables.tf index e0e071a8..80427776 100644 --- a/modules/vnet_peering/variables.tf +++ b/modules/vnet_peering/variables.tf @@ -50,4 +50,4 @@ variable "remote_peer_config" { allow_gateway_transit = optional(bool, false) use_remote_gateways = optional(bool, false) }) -} \ No newline at end of file +} diff --git a/modules/vnet_peering/versions.tf b/modules/vnet_peering/versions.tf index 6be9475c..9abec711 100644 --- a/modules/vnet_peering/versions.tf +++ b/modules/vnet_peering/versions.tf @@ -6,4 +6,4 @@ terraform { version = "~> 3.80" } } -} \ No newline at end of file +} From b01cf56cc04283d7531a8e27474de823a0b42a8e Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Tue, 12 Mar 2024 10:15:39 +0100 Subject: [PATCH 25/49] feat: Rename location to region, fix issues with VNG (#26) --- examples/appgw/README.md | 5 +++-- examples/appgw/example.tfvars | 2 +- examples/appgw/main.tf | 8 +++---- examples/appgw/variables.tf | 2 +- examples/common_vmseries/README.md | 5 +++-- examples/common_vmseries/example.tfvars | 2 +- examples/common_vmseries/main.tf | 18 +++++++-------- examples/common_vmseries/variables.tf | 2 +- .../common_vmseries_and_autoscale/README.md | 5 +++-- .../example.tfvars | 2 +- .../common_vmseries_and_autoscale/main.tf | 14 ++++++------ .../variables.tf | 2 +- examples/dedicated_vmseries/README.md | 5 +++-- examples/dedicated_vmseries/example.tfvars | 2 +- examples/dedicated_vmseries/main.tf | 18 +++++++-------- examples/dedicated_vmseries/variables.tf | 2 +- .../README.md | 5 +++-- .../example.tfvars | 2 +- .../dedicated_vmseries_and_autoscale/main.tf | 14 ++++++------ .../variables.tf | 2 +- examples/gwlb_with_vmseries/README.md | 5 +++-- examples/gwlb_with_vmseries/example.tfvars | 2 +- examples/gwlb_with_vmseries/main.tf | 18 +++++++-------- examples/gwlb_with_vmseries/variables.tf | 2 +- examples/standalone_panorama/README.md | 5 +++-- examples/standalone_panorama/example.tfvars | 2 +- examples/standalone_panorama/main.tf | 8 +++---- examples/standalone_panorama/variables.tf | 2 +- examples/standalone_vmseries/README.md | 5 +++-- examples/standalone_vmseries/example.tfvars | 2 +- examples/standalone_vmseries/main.tf | 18 +++++++-------- examples/standalone_vmseries/variables.tf | 2 +- examples/test_infrastructure/README.md | 4 ++-- examples/test_infrastructure/example.tfvars | 2 +- examples/test_infrastructure/main.tf | 12 +++++----- examples/test_infrastructure/variables.tf | 2 +- examples/virtual_network_gateway/README.md | 4 ++-- .../virtual_network_gateway/example.tfvars | 2 +- examples/virtual_network_gateway/main.tf | 6 ++--- examples/virtual_network_gateway/variables.tf | 2 +- go.sum | 4 ++-- modules/appgw/.header.md | 2 +- modules/appgw/README.md | 6 ++--- modules/appgw/main.tf | 4 ++-- modules/appgw/variables.tf | 2 +- modules/bootstrap/.header.md | 4 ++-- modules/bootstrap/README.md | 10 ++++----- modules/bootstrap/main.tf | 4 ++-- modules/bootstrap/variables.tf | 2 +- modules/gwlb/README.md | 4 ++-- modules/gwlb/main.tf | 2 +- modules/gwlb/variables.tf | 2 +- modules/loadbalancer/.header.md | 4 ++-- modules/loadbalancer/README.md | 8 +++---- modules/loadbalancer/main.tf | 4 ++-- modules/loadbalancer/variables.tf | 2 +- modules/natgw/README.md | 4 ++-- modules/natgw/main.tf | 6 ++--- modules/natgw/variables.tf | 2 +- modules/ngfw_metrics/.header.md | 2 +- modules/ngfw_metrics/README.md | 6 ++--- modules/ngfw_metrics/main.tf | 4 ++-- modules/ngfw_metrics/variables.tf | 2 +- modules/panorama/README.md | 4 ++-- modules/panorama/main.tf | 8 +++---- modules/panorama/variables.tf | 2 +- modules/virtual_machine/main.tf | 6 ++--- modules/virtual_machine/variables.tf | 2 +- modules/virtual_network_gateway/.header.md | 2 +- modules/virtual_network_gateway/README.md | 6 ++--- modules/virtual_network_gateway/main.tf | 10 ++++----- modules/virtual_network_gateway/variables.tf | 22 +++++++++---------- modules/vmseries/README.md | 4 ++-- modules/vmseries/main.tf | 6 ++--- modules/vmseries/variables.tf | 2 +- modules/vmss/.header.md | 2 +- modules/vmss/README.md | 8 +++---- modules/vmss/main.tf | 4 ++-- modules/vmss/variables.tf | 2 +- modules/vnet/README.md | 4 ++-- modules/vnet/main.tf | 6 ++--- modules/vnet/variables.tf | 2 +- modules/vnet_peering/README.md | 1 + 83 files changed, 212 insertions(+), 203 deletions(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index bb4eb7f1..c2fa8c21 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -9,7 +9,7 @@ The `README` is also in new, document-style format. Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `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. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -58,7 +58,7 @@ Resources used in this module: -#### location +#### region The Azure region to use. @@ -376,4 +376,5 @@ Default value: `true` + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars index efbe70e7..cf2844cf 100644 --- a/examples/appgw/example.tfvars +++ b/examples/appgw/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "appgw-example" name_prefix = "fosix-" tags = { diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf index 52e4fd39..f12f69bc 100644 --- a/examples/appgw/main.tf +++ b/examples/appgw/main.tf @@ -4,7 +4,7 @@ resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -25,7 +25,7 @@ locals { resource "azurerm_public_ip" "this" { name = "pip-existing" resource_group_name = local.resource_group.name - location = var.location + location = var.region sku = "Standard" allocation_method = "Static" @@ -43,7 +43,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -69,7 +69,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf index 3db178a9..b0d9c42d 100644 --- a/examples/appgw/variables.tf +++ b/examples/appgw/variables.tf @@ -5,7 +5,7 @@ variable "tags" { type = map(string) } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 0c1649b1..bbe7a694 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -172,7 +172,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -255,7 +255,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -1082,4 +1082,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 5bdd7084..d6511e6b 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "transit-vnet-common" name_prefix = "example-" tags = { diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 29fd01d5..a385d9c2 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -33,7 +33,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -58,7 +58,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -83,7 +83,7 @@ module "natgw" { create_natgw = each.value.create_natgw name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region zone = try(each.value.zone, null) idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } @@ -107,7 +107,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -171,7 +171,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones @@ -216,7 +216,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -306,7 +306,7 @@ module "bootstrap" { storage_account = each.value.storage_account name = each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region storage_network_security = merge( each.value.storage_network_security, @@ -328,7 +328,7 @@ resource "azurerm_availability_set" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region platform_update_domain_count = each.value.update_domain_count platform_fault_domain_count = each.value.fault_domain_count @@ -341,7 +341,7 @@ module "vmseries" { for_each = var.vmseries name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name authentication = local.authentication[each.key] diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 8a26966e..46d25c34 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index f4e4f45c..74ad42ae 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -203,7 +203,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -277,7 +277,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -975,4 +975,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 7edeba58..2ce25439 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "autoscale-common" name_prefix = "example-" tags = { diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 151af28c..ee84b67a 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -34,7 +34,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -59,7 +59,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -84,7 +84,7 @@ module "natgw" { create_natgw = each.value.create_natgw name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region zone = try(each.value.zone, null) idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } @@ -108,7 +108,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -156,7 +156,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones @@ -198,7 +198,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -221,7 +221,7 @@ module "vmss" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region authentication = local.authentication[each.key] virtual_machine_scale_set = each.value.virtual_machine_scale_set diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index a18c7c0f..556a5974 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 2f6c85b9..5aa2560c 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -176,7 +176,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -259,7 +259,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -1086,4 +1086,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 932692a9..10a00800 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "transit-vnet-dedicated" name_prefix = "example-" tags = { diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 29fd01d5..a385d9c2 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -33,7 +33,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -58,7 +58,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -83,7 +83,7 @@ module "natgw" { create_natgw = each.value.create_natgw name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region zone = try(each.value.zone, null) idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } @@ -107,7 +107,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -171,7 +171,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones @@ -216,7 +216,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -306,7 +306,7 @@ module "bootstrap" { storage_account = each.value.storage_account name = each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region storage_network_security = merge( each.value.storage_network_security, @@ -328,7 +328,7 @@ resource "azurerm_availability_set" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region platform_update_domain_count = each.value.update_domain_count platform_fault_domain_count = each.value.fault_domain_count @@ -341,7 +341,7 @@ module "vmseries" { for_each = var.vmseries name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name authentication = local.authentication[each.key] diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 8a26966e..46d25c34 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index f3a74a4e..c2a3ec7b 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -199,7 +199,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -273,7 +273,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -971,4 +971,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 5f6ca663..a1a1af2a 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "autoscale-dedicated" name_prefix = "example-" tags = { diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 151af28c..ee84b67a 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -34,7 +34,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -59,7 +59,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -84,7 +84,7 @@ module "natgw" { create_natgw = each.value.create_natgw name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region zone = try(each.value.zone, null) idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } @@ -108,7 +108,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -156,7 +156,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones @@ -198,7 +198,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -221,7 +221,7 @@ module "vmss" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region authentication = local.authentication[each.key] virtual_machine_scale_set = each.value.virtual_machine_scale_set diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index a18c7c0f..556a5974 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 26185cc3..7470b9bf 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -83,7 +83,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -165,7 +165,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -823,4 +823,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 9be9a153..6b4fe995 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "gwlb" name_prefix = "example-" tags = { diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 8fb34ab9..dd56b64f 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -33,7 +33,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -58,7 +58,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -83,7 +83,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -131,7 +131,7 @@ module "gwlb" { name = "${var.name_prefix}${each.value.name}" resource_group_name = try(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region backends = try(each.value.backends, null) health_probe = try(each.value.health_probe, null) @@ -162,7 +162,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -238,7 +238,7 @@ module "bootstrap" { storage_account = each.value.storage_account name = each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region storage_network_security = merge( each.value.storage_network_security, @@ -260,7 +260,7 @@ resource "azurerm_availability_set" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region platform_update_domain_count = each.value.update_domain_count platform_fault_domain_count = each.value.fault_domain_count @@ -273,7 +273,7 @@ module "vmseries" { for_each = var.vmseries name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name authentication = local.authentication[each.key] @@ -335,7 +335,7 @@ module "appvm" { source = "../../modules/virtual_machine" name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name avzone = each.value.avzone diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index ac29cf18..703a678f 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 7f6c6c31..45fc8635 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -125,7 +125,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -192,7 +192,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -475,4 +475,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 1589f06f..b70b89f5 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "panorama" name_prefix = "example-" create_resource_group = true diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index 470f0ff6..bb21770a 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -33,7 +33,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -58,7 +58,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -82,7 +82,7 @@ resource "azurerm_availability_set" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region platform_update_domain_count = each.value.update_domain_count platform_fault_domain_count = each.value.fault_domain_count @@ -95,7 +95,7 @@ module "panorama" { for_each = var.panoramas name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name authentication = local.authentication[each.key] diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 6587dc54..7ea23d53 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 46560801..800b9d8c 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -116,7 +116,7 @@ terraform destroy Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -199,7 +199,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. @@ -1026,4 +1026,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 951ea7f0..698c0794 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "vmseries-standalone" name_prefix = "example-" tags = { diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 29fd01d5..a385d9c2 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -33,7 +33,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -58,7 +58,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -83,7 +83,7 @@ module "natgw" { create_natgw = each.value.create_natgw name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region zone = try(each.value.zone, null) idle_timeout = each.value.idle_timeout subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } @@ -107,7 +107,7 @@ module "load_balancer" { for_each = var.load_balancers name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name zones = each.value.zones backend_name = each.value.backend_name @@ -171,7 +171,7 @@ module "appgw" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + region = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] zones = each.value.zones @@ -216,7 +216,7 @@ module "ngfw_metrics" { resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : ( coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) ) - location = var.location + region = var.region log_analytics_workspace = { sku = var.ngfw_metrics.sku @@ -306,7 +306,7 @@ module "bootstrap" { storage_account = each.value.storage_account name = each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region storage_network_security = merge( each.value.storage_network_security, @@ -328,7 +328,7 @@ resource "azurerm_availability_set" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region platform_update_domain_count = each.value.update_domain_count platform_fault_domain_count = each.value.fault_domain_count @@ -341,7 +341,7 @@ module "vmseries" { for_each = var.vmseries name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name authentication = local.authentication[each.key] diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 8a26966e..46d25c34 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/test_infrastructure/README.md b/examples/test_infrastructure/README.md index a2d5cdd4..9a8f2433 100644 --- a/examples/test_infrastructure/README.md +++ b/examples/test_infrastructure/README.md @@ -20,7 +20,7 @@ Please correct the values marked with `TODO` markers at minimum. Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `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. @@ -87,7 +87,7 @@ Resources used in this module: -#### location +#### region The Azure region to use. diff --git a/examples/test_infrastructure/example.tfvars b/examples/test_infrastructure/example.tfvars index 989299b7..a95a5017 100644 --- a/examples/test_infrastructure/example.tfvars +++ b/examples/test_infrastructure/example.tfvars @@ -1,5 +1,5 @@ # --- GENERAL --- # -location = "North Europe" +region = "North Europe" resource_group_name = "vmseries-test-infra" name_prefix = "example-" tags = { diff --git a/examples/test_infrastructure/main.tf b/examples/test_infrastructure/main.tf index 41b63865..389e0ec1 100644 --- a/examples/test_infrastructure/main.tf +++ b/examples/test_infrastructure/main.tf @@ -20,7 +20,7 @@ locals { resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -44,7 +44,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -83,7 +83,7 @@ resource "azurerm_network_interface" "vm" { for_each = var.test_vms name = "${var.name_prefix}${each.value.name}-nic" - location = var.location + location = var.region resource_group_name = local.resource_group.name ip_configuration { @@ -101,7 +101,7 @@ resource "azurerm_linux_virtual_machine" "this" { name = "${var.name_prefix}${each.value.name}" resource_group_name = local.resource_group.name - location = var.location + location = var.region size = var.vm_size admin_username = var.username admin_password = local.password @@ -133,7 +133,7 @@ resource "azurerm_public_ip" "bastion" { for_each = var.bastions name = "${var.name_prefix}${each.value.name}-nic" - location = var.location + location = var.region resource_group_name = local.resource_group.name allocation_method = "Static" sku = "Standard" @@ -143,7 +143,7 @@ resource "azurerm_bastion_host" "this" { for_each = var.bastions name = "${var.name_prefix}${each.value.name}" - location = var.location + location = var.region resource_group_name = local.resource_group.name ip_configuration { diff --git a/examples/test_infrastructure/variables.tf b/examples/test_infrastructure/variables.tf index 753ac35b..02b8fed7 100644 --- a/examples/test_infrastructure/variables.tf +++ b/examples/test_infrastructure/variables.tf @@ -5,7 +5,7 @@ variable "tags" { type = map(string) } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index faa95bfb..b7745a3e 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -10,7 +10,7 @@ The `README` is also in new, document-style format. Name | Type | Description --- | --- | --- [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. -[`location`](#location) | `string` | The Azure region to use. +[`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. @@ -72,7 +72,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The Azure region to use. diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars index c222b56b..f6aa9523 100644 --- a/examples/virtual_network_gateway/example.tfvars +++ b/examples/virtual_network_gateway/example.tfvars @@ -1,6 +1,6 @@ ### GENERAL ### -location = "North Europe" +region = "North Europe" resource_group_name = "vng" name_prefix = "example-" tags = { diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf index 85ed5850..9cbc42e6 100644 --- a/examples/virtual_network_gateway/main.tf +++ b/examples/virtual_network_gateway/main.tf @@ -4,7 +4,7 @@ resource "azurerm_resource_group" "this" { count = var.create_resource_group ? 1 : 0 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + location = var.region tags = var.tags } @@ -31,7 +31,7 @@ module "vnet" { name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name create_virtual_network = each.value.create_virtual_network resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - location = var.location + region = var.region address_space = each.value.address_space @@ -56,7 +56,7 @@ module "vng" { for_each = var.virtual_network_gateways name = "${var.name_prefix}${each.value.name}" - location = var.location + region = var.region resource_group_name = local.resource_group.name subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf index 1173e6a7..7bcf748c 100644 --- a/examples/virtual_network_gateway/variables.tf +++ b/examples/virtual_network_gateway/variables.tf @@ -35,7 +35,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The Azure region to use." type = string } diff --git a/go.sum b/go.sum index c4eda4a2..77a714d4 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton v1.1.0 h1:4BnQVUZjEitHUzGFbpzCRwUVyD652vbIau1eKwHMpJQ= -github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton v1.1.0/go.mod h1:xxVd295BDYzQ81QhtzrXIdk2XMvWT8NdX6aAKoAqvDI= +github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton v1.2.0 h1:UIJmT/kNn676YGNrLJismZjXjfoDhf4DuSm8kkdHzRU= +github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton v1.2.0/go.mod h1:fpR0L5BwAHXuWjJMFveFHel8sbNMqpIYdwFTehxdrHI= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= diff --git a/modules/appgw/.header.md b/modules/appgw/.header.md index cef1efb9..69f63444 100644 --- a/modules/appgw/.header.md +++ b/modules/appgw/.header.md @@ -17,7 +17,7 @@ module "appgw" { name = each.value.name public_ip = each.value.public_ip resource_group_name = local.resource_group.name - location = var.location + location = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] managed_identities = each.value.managed_identities diff --git a/modules/appgw/README.md b/modules/appgw/README.md index c7d58039..1f01b9a8 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -18,7 +18,7 @@ module "appgw" { name = each.value.name public_ip = each.value.public_ip resource_group_name = local.resource_group.name - location = var.location + location = var.region subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] managed_identities = each.value.managed_identities @@ -816,7 +816,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Application Gateway. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`subnet_id`](#subnet_id) | `string` | An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway. [`public_ip`](#public_ip) | `object` | A map defining listener's public IP configuration. [`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. @@ -897,7 +897,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/appgw/main.tf b/modules/appgw/main.tf index 79141b9b..9d84f58e 100644 --- a/modules/appgw/main.tf +++ b/modules/appgw/main.tf @@ -28,7 +28,7 @@ resource "azurerm_public_ip" "this" { name = var.public_ip.name resource_group_name = var.resource_group_name - location = var.location + location = var.region sku = "Standard" allocation_method = "Static" @@ -41,7 +41,7 @@ resource "azurerm_public_ip" "this" { resource "azurerm_application_gateway" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region zones = var.zones enable_http2 = var.enable_http2 tags = var.tags diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index cc71f8c2..3262c249 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/bootstrap/.header.md b/modules/bootstrap/.header.md index db19a638..3485ffd6 100644 --- a/modules/bootstrap/.header.md +++ b/modules/bootstrap/.header.md @@ -31,7 +31,7 @@ module "empty_storage" { name = "someemptystorage" resource_group_name = "rg-name" - location = "North Europe" + region = "North Europe" } ``` @@ -52,7 +52,7 @@ module "bootstrap" { name = "samplebootstrapstorage" resource_group_name = "rg-name" - location = "North Europe" + region = "North Europe" file_shares_configuration = { access_tier = "Hot" diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index 5f4f23bc..173eda5d 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -32,7 +32,7 @@ module "empty_storage" { name = "someemptystorage" resource_group_name = "rg-name" - location = "North Europe" + region = "North Europe" } ``` @@ -49,11 +49,11 @@ This code will create a storage account for 3 NGFWs. Please **note** that: ```hcl module "bootstrap" { - source = "../../modules/bootstrap" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/bootstrap" name = "samplebootstrapstorage" resource_group_name = "rg-name" - location = "North Europe" + region = "North Europe" file_shares_configuration = { access_tier = "Hot" @@ -140,7 +140,7 @@ Name | Type | Description Name | Type | Description --- | --- | --- -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`storage_account`](#storage_account) | `object` | A map controlling basic Storage Account configuration. [`storage_network_security`](#storage_network_security) | `object` | A map defining network security settings for a new storage account. @@ -222,7 +222,7 @@ Type: string -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/bootstrap/main.tf b/modules/bootstrap/main.tf index a1d62a93..541a1a64 100644 --- a/modules/bootstrap/main.tf +++ b/modules/bootstrap/main.tf @@ -3,7 +3,7 @@ resource "azurerm_storage_account" "this" { count = var.storage_account.create ? 1 : 0 name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name min_tls_version = var.storage_network_security.min_tls_version account_replication_type = var.storage_account.replication_type @@ -13,7 +13,7 @@ resource "azurerm_storage_account" "this" { lifecycle { precondition { - condition = var.location != null + condition = var.region != null error_message = "When creating a storage account the `location` variable cannot be null." } } diff --git a/modules/bootstrap/variables.tf b/modules/bootstrap/variables.tf index f548355a..3daf1d20 100644 --- a/modules/bootstrap/variables.tf +++ b/modules/bootstrap/variables.tf @@ -20,7 +20,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." default = null type = string diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index d4c572e0..74743982 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -87,7 +87,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Load Balancer. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`frontend_ip`](#frontend_ip) | `object` | Frontend IP configuration of the Gateway Load Balancer. @@ -154,7 +154,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/gwlb/main.tf b/modules/gwlb/main.tf index d84df3ba..14fda5df 100644 --- a/modules/gwlb/main.tf +++ b/modules/gwlb/main.tf @@ -2,7 +2,7 @@ resource "azurerm_lb" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region sku = "Gateway" tags = var.tags diff --git a/modules/gwlb/variables.tf b/modules/gwlb/variables.tf index cc2c907a..95a08982 100644 --- a/modules/gwlb/variables.tf +++ b/modules/gwlb/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/loadbalancer/.header.md b/modules/loadbalancer/.header.md index 39153098..bdb2a082 100644 --- a/modules/loadbalancer/.header.md +++ b/modules/loadbalancer/.header.md @@ -30,7 +30,7 @@ module "lbi" { source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "private-lb" - location = "West Europe" + region = "West Europe" resource_group_name = "existing-rg" frontend_ips = { @@ -62,7 +62,7 @@ module "lbe" { source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "public-lb" - location = "West Europe" + region = "West Europe" resource_group_name = "existing-rg" frontend_ips = { diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index a4496e7b..e6ba413d 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -31,7 +31,7 @@ module "lbi" { source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "private-lb" - location = "West Europe" + region = "West Europe" resource_group_name = "existing-rg" frontend_ips = { @@ -63,7 +63,7 @@ module "lbe" { source = "PaloAltoNetworks/swfw-modules/azurerm//modules/loadbalancer" name = "public-lb" - location = "West Europe" + region = "West Europe" resource_group_name = "existing-rg" frontend_ips = { @@ -89,7 +89,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Load Balancer. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure 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. @@ -163,7 +163,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/loadbalancer/main.tf b/modules/loadbalancer/main.tf index ca63cfaf..c965a5bd 100644 --- a/modules/loadbalancer/main.tf +++ b/modules/loadbalancer/main.tf @@ -36,7 +36,7 @@ resource "azurerm_public_ip" "this" { name = each.value.public_ip_name resource_group_name = var.resource_group_name - location = var.location + location = var.region allocation_method = "Static" sku = "Standard" zones = var.zones @@ -55,7 +55,7 @@ data "azurerm_public_ip" "this" { resource "azurerm_lb" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region sku = "Standard" tags = var.tags diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index e76ba3e1..8dad32ec 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/natgw/README.md b/modules/natgw/README.md index 07465c40..f2fe33f5 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -45,7 +45,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | Name of a NAT Gateway. [`resource_group_name`](#resource_group_name) | `string` | Name of a Resource Group hosting the NAT Gateway (either the existing one or the one that will be created). -[`location`](#location) | `string` | Azure region. +[`region`](#region) | `string` | Azure region. [`subnet_ids`](#subnet_ids) | `map` | A map of subnet IDs what will be bound with this NAT Gateway. @@ -118,7 +118,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region Azure region. Only for newly created resources. diff --git a/modules/natgw/main.tf b/modules/natgw/main.tf index f90bccff..eb63a713 100644 --- a/modules/natgw/main.tf +++ b/modules/natgw/main.tf @@ -4,7 +4,7 @@ resource "azurerm_public_ip" "this" { name = var.public_ip.name resource_group_name = var.resource_group_name - location = var.location + location = var.region allocation_method = "Static" sku = "Standard" zones = var.zone != null ? [var.zone] : null @@ -26,7 +26,7 @@ resource "azurerm_public_ip_prefix" "this" { name = var.public_ip_prefix.name resource_group_name = var.resource_group_name - location = var.location + location = var.region ip_version = "IPv4" prefix_length = var.public_ip_prefix.length sku = "Standard" @@ -49,7 +49,7 @@ resource "azurerm_nat_gateway" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region sku_name = "Standard" idle_timeout_in_minutes = var.idle_timeout zones = var.zone != null ? [var.zone] : null diff --git a/modules/natgw/variables.tf b/modules/natgw/variables.tf index 296b31a1..7507e124 100644 --- a/modules/natgw/variables.tf +++ b/modules/natgw/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "Azure region. Only for newly created resources." type = string } diff --git a/modules/ngfw_metrics/.header.md b/modules/ngfw_metrics/.header.md index 67e6aa36..7dd90d30 100644 --- a/modules/ngfw_metrics/.header.md +++ b/modules/ngfw_metrics/.header.md @@ -45,7 +45,7 @@ module "ngfw_metrics" { name = "ngfw-law" resource_group_name = "ngfw-rg" - location = "West US" + region = "West US" application_insights = { ai1 = { name = "fw1-ai" } diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index 5cec3721..4917bf9e 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -46,7 +46,7 @@ module "ngfw_metrics" { name = "ngfw-law" resource_group_name = "ngfw-rg" - location = "West US" + region = "West US" application_insights = { ai1 = { name = "fw1-ai" } @@ -61,7 +61,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Log Analytics Workspace. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`application_insights`](#application_insights) | `map` | A map defining Application Insights instances. @@ -125,7 +125,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/ngfw_metrics/main.tf b/modules/ngfw_metrics/main.tf index db2127b4..af2854d6 100644 --- a/modules/ngfw_metrics/main.tf +++ b/modules/ngfw_metrics/main.tf @@ -4,7 +4,7 @@ resource "azurerm_log_analytics_workspace" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region retention_in_days = var.log_analytics_workspace.metrics_retention_in_days sku = var.log_analytics_workspace.sku @@ -26,7 +26,7 @@ resource "azurerm_application_insights" "this" { name = each.value.name resource_group_name = coalesce(each.value.resource_group_name, var.resource_group_name) - location = var.location + location = var.region workspace_id = var.create_workspace ? ( azurerm_log_analytics_workspace.this[0].id diff --git a/modules/ngfw_metrics/variables.tf b/modules/ngfw_metrics/variables.tf index f81f9adf..469feb9a 100644 --- a/modules/ngfw_metrics/variables.tf +++ b/modules/ngfw_metrics/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 543015de..1acd0bd5 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -24,7 +24,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Machine. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). [`image`](#image) | `object` | Basic Azure VM configuration. [`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. @@ -94,7 +94,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/panorama/main.tf b/modules/panorama/main.tf index c653765f..e3d2577c 100644 --- a/modules/panorama/main.tf +++ b/modules/panorama/main.tf @@ -2,7 +2,7 @@ resource "azurerm_public_ip" "this" { for_each = { for v in var.interfaces : v.name => v if v.create_public_ip } - location = var.location + location = var.region resource_group_name = var.resource_group_name name = each.value.public_ip_name allocation_method = "Static" @@ -24,7 +24,7 @@ resource "azurerm_network_interface" "this" { for_each = { for k, v in var.interfaces : v.name => merge(v, { index = k }) } name = each.value.name - location = var.location + location = var.region resource_group_name = var.resource_group_name enable_accelerated_networking = false enable_ip_forwarding = false @@ -48,7 +48,7 @@ locals { # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine resource "azurerm_linux_virtual_machine" "this" { name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name tags = var.tags @@ -118,7 +118,7 @@ resource "azurerm_managed_disk" "this" { for_each = var.logging_disks name = each.value.name - location = var.location + location = var.region resource_group_name = var.resource_group_name storage_account_type = each.value.disk_type create_option = "Empty" diff --git a/modules/panorama/variables.tf b/modules/panorama/variables.tf index a8b18fca..fd0d0964 100644 --- a/modules/panorama/variables.tf +++ b/modules/panorama/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/virtual_machine/main.tf b/modules/virtual_machine/main.tf index b6941840..b6b2d344 100644 --- a/modules/virtual_machine/main.tf +++ b/modules/virtual_machine/main.tf @@ -9,7 +9,7 @@ locals { resource "azurerm_public_ip" "this" { for_each = { for k, v in var.interfaces : k => v if try(v.create_public_ip, false) } - location = var.location + location = var.region resource_group_name = var.resource_group_name name = "${each.value.name}-pip" allocation_method = "Static" @@ -23,7 +23,7 @@ resource "azurerm_network_interface" "this" { count = length(var.interfaces) name = var.interfaces[count.index].name - location = var.location + location = var.region resource_group_name = var.resource_group_name enable_accelerated_networking = var.accelerated_networking enable_ip_forwarding = true @@ -57,7 +57,7 @@ resource "azurerm_network_interface_backend_address_pool_association" "this" { # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine resource "azurerm_virtual_machine" "this" { name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name tags = var.tags vm_size = var.vm_size diff --git a/modules/virtual_machine/variables.tf b/modules/virtual_machine/variables.tf index e58f92f7..aa3dc45b 100644 --- a/modules/virtual_machine/variables.tf +++ b/modules/virtual_machine/variables.tf @@ -1,4 +1,4 @@ -variable "location" { +variable "region" { description = "Region where to deploy and dependencies." type = string } diff --git a/modules/virtual_network_gateway/.header.md b/modules/virtual_network_gateway/.header.md index 1e087b58..c33dca2a 100644 --- a/modules/virtual_network_gateway/.header.md +++ b/modules/virtual_network_gateway/.header.md @@ -15,7 +15,7 @@ module "vng" { for_each = var.virtual_network_gateways name = "${var.name_prefix}${each.value.name}" - location = var.location + location = var.region resource_group_name = local.resource_group.name network = each.value.network diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index 92ae788c..8926a617 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -16,7 +16,7 @@ module "vng" { for_each = var.virtual_network_gateways name = "${var.name_prefix}${each.value.name}" - location = var.location + location = var.region resource_group_name = local.resource_group.name network = each.value.network @@ -319,7 +319,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Virtual Network Gateway. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`subnet_id`](#subnet_id) | `string` | An ID of a Subnet in which the Virtual Network Gateway will be created. [`instance_settings`](#instance_settings) | `object` | A map containing the basic Virtual Network Gateway instance settings. [`ip_configurations`](#ip_configurations) | `object` | A map defining the Public IPs used by the Virtual Network Gateway. @@ -393,7 +393,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/virtual_network_gateway/main.tf b/modules/virtual_network_gateway/main.tf index cee0e3cb..d6aabc36 100644 --- a/modules/virtual_network_gateway/main.tf +++ b/modules/virtual_network_gateway/main.tf @@ -3,7 +3,7 @@ resource "azurerm_public_ip" "this" { for_each = { for k, v in var.ip_configurations : k => v if try(v.create_public_ip, false) } resource_group_name = var.resource_group_name - location = var.location + location = var.region name = each.value.public_ip_name allocation_method = "Static" @@ -25,12 +25,12 @@ data "azurerm_public_ip" "exists" { resource "azurerm_virtual_network_gateway" "this" { name = var.name resource_group_name = var.resource_group_name - location = var.location + location = var.region type = var.instance_settings.type vpn_type = var.instance_settings.vpn_type sku = var.instance_settings.sku - generation = var.instance_settings.ype == "VPN" ? var.instance_settings.generation : null + generation = var.instance_settings.type == "VPN" ? var.instance_settings.generation : null active_active = var.instance_settings.active_active default_local_network_gateway_id = var.default_local_network_gateway_id edge_zone = var.edge_zone @@ -173,7 +173,7 @@ resource "azurerm_local_network_gateway" "this" { name = each.value.name resource_group_name = var.resource_group_name - location = var.location + location = var.region gateway_address = each.value.gateway_address address_space = each.value.address_space @@ -195,7 +195,7 @@ resource "azurerm_virtual_network_gateway_connection" "this" { for_each = var.local_network_gateways name = each.value.connection.name - location = var.location + location = var.region resource_group_name = var.resource_group_name type = each.value.connection.type diff --git a/modules/virtual_network_gateway/variables.tf b/modules/virtual_network_gateway/variables.tf index fc43f271..cbb9904b 100644 --- a/modules/virtual_network_gateway/variables.tf +++ b/modules/virtual_network_gateway/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } @@ -115,27 +115,27 @@ variable "instance_settings" { }) validation { # type - condition = contains(["Vpn", "ExpressRoute"], var.virtual_network_gateway.type) + condition = contains(["Vpn", "ExpressRoute"], var.instance_settings.type) error_message = <<-EOF The `virtual_network_gateway.type` property can take one of the following values: "Vpn" or "ExpressRoute". EOF } validation { # vpn_type - condition = contains(["RouteBased", "PolicyBased"], var.virtual_network_gateway.vpn_type) + condition = contains(["RouteBased", "PolicyBased"], var.instance_settings.vpn_type) error_message = <<-EOF The `virtual_network_gateway.vpn_type` property can take one of the following values: "RouteBased" or "PolicyBased". EOF } validation { # generation - condition = contains(["Generation1", "Generation2", "None"], var.virtual_network_gateway.generation) + condition = contains(["Generation1", "Generation2", "None"], var.instance_settings.generation) error_message = <<-EOF The `virtual_network_gateway.generation` property can take one of the following values: "Generation1" or "Generation2" or "None". EOF } validation { # type, generation & sku - condition = var.virtual_network_gateway.generation == "Generation2" && var.virtual_network_gateway.type == "Vpn" ? contains( - ["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.virtual_network_gateway.sku + condition = var.instance_settings.generation == "Generation2" && var.instance_settings.type == "Vpn" ? contains( + ["VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], var.instance_settings.sku ) : true error_message = <<-EOF For `sku` of "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ" or "VpnGw5AZ" the `generation` @@ -143,12 +143,12 @@ variable "instance_settings" { EOF } validation { # type & sku - condition = (var.virtual_network_gateway.type == "Vpn" && contains( + condition = (var.instance_settings.type == "Vpn" && contains( ["Basic", "VpnGw1", "VpnGw2", "VpnGw3", "VpnGw4", "VpnGw5", "VpnGw1AZ", "VpnGw2AZ", "VpnGw3AZ", "VpnGw4AZ", "VpnGw5AZ"], - var.virtual_network_gateway.sku + var.instance_settings.sku )) || ( - var.virtual_network_gateway.type == "ExpressRoute" && contains( - ["Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ"], var.virtual_network_gateway.sku + var.instance_settings.type == "ExpressRoute" && contains( + ["Standard", "HighPerformance", "UltraPerformance", "ErGw1AZ", "ErGw2AZ", "ErGw3AZ"], var.instance_settings.sku ) ) error_message = <<-EOF @@ -156,7 +156,7 @@ variable "instance_settings" { EOF } validation { # active_active - condition = var.virtual_network_gateway.type == "ExpressRoute" ? !var.virtual_network_gateway.active_active : true + condition = var.instance_settings.type == "ExpressRoute" ? !var.instance_settings.active_active : true error_message = <<-EOF The `active_active` property has to be set to `false` (default) when type is `ExpressRoute`. EOF diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 6c3e78a6..f35877f7 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -38,7 +38,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Machine. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). [`image`](#image) | `object` | Basic Azure VM image configuration. [`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. @@ -108,7 +108,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/vmseries/main.tf b/modules/vmseries/main.tf index 202a44e0..99c7272a 100644 --- a/modules/vmseries/main.tf +++ b/modules/vmseries/main.tf @@ -2,7 +2,7 @@ resource "azurerm_public_ip" "this" { for_each = { for v in var.interfaces : v.name => v if v.create_public_ip } - location = var.location + location = var.region resource_group_name = var.resource_group_name name = each.value.public_ip_name allocation_method = "Static" @@ -25,7 +25,7 @@ resource "azurerm_network_interface" "this" { for_each = { for k, v in var.interfaces : v.name => merge(v, { index = k }) } name = each.value.name - location = var.location + location = var.region resource_group_name = var.resource_group_name enable_accelerated_networking = each.value.index == 0 ? false : var.virtual_machine.accelerated_networking enable_ip_forwarding = each.value.index == 0 ? false : true @@ -51,7 +51,7 @@ locals { # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine resource "azurerm_linux_virtual_machine" "this" { name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name tags = var.tags diff --git a/modules/vmseries/variables.tf b/modules/vmseries/variables.tf index d57252bf..67f2259e 100644 --- a/modules/vmseries/variables.tf +++ b/modules/vmseries/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/vmss/.header.md b/modules/vmss/.header.md index d52efd8f..7ccb8210 100644 --- a/modules/vmss/.header.md +++ b/modules/vmss/.header.md @@ -64,7 +64,7 @@ module "vmss" { name = "ngfw-vmss" resource_group_name = "hub-rg" - location = "West Europe" + region = "West Europe" authentication = { username = "panadmin" diff --git a/modules/vmss/README.md b/modules/vmss/README.md index e155fd95..b685ae5f 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -61,11 +61,11 @@ Below you can find a simple example deploying a Scale Set w/o autoscaling, using ```hcl module "vmss" { - source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/vmss" + source = "PaloAltoNetworks/swfw-modules/azurerm//modules/vmss" name = "ngfw-vmss" resource_group_name = "hub-rg" - location = "West Europe" + region = "West Europe" authentication = { username = "panadmin" @@ -105,7 +105,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Machine Scale Set. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`authentication`](#authentication) | `object` | A map defining authentication settings (including username and password). [`image`](#image) | `object` | Basic Azure VM configuration. [`interfaces`](#interfaces) | `list` | List of the network interfaces specifications. @@ -176,7 +176,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/vmss/main.tf b/modules/vmss/main.tf index 1d994d27..835c8381 100644 --- a/modules/vmss/main.tf +++ b/modules/vmss/main.tf @@ -6,7 +6,7 @@ locals { resource "azurerm_linux_virtual_machine_scale_set" "this" { name = var.name computer_name_prefix = null - location = var.location + location = var.region resource_group_name = var.resource_group_name admin_username = var.authentication.username @@ -175,7 +175,7 @@ resource "azurerm_monitor_autoscale_setting" "this" { count = length(var.autoscaling_profiles) > 0 ? 1 : 0 name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name target_resource_id = azurerm_linux_virtual_machine_scale_set.this.id diff --git a/modules/vmss/variables.tf b/modules/vmss/variables.tf index 817578ac..5a4e37f4 100644 --- a/modules/vmss/variables.tf +++ b/modules/vmss/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 8bb92e2a..6790b96b 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -128,7 +128,7 @@ Name | Type | Description --- | --- | --- [`name`](#name) | `string` | The name of the Azure Virtual Network. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. -[`location`](#location) | `string` | The name of the Azure region to deploy the resources in. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. ## Module's Optional Inputs @@ -206,7 +206,7 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> -#### location +#### region The name of the Azure region to deploy the resources in. diff --git a/modules/vnet/main.tf b/modules/vnet/main.tf index 0a8915cd..56d0083f 100644 --- a/modules/vnet/main.tf +++ b/modules/vnet/main.tf @@ -3,7 +3,7 @@ resource "azurerm_virtual_network" "this" { count = var.create_virtual_network ? 1 : 0 name = var.name - location = var.location + location = var.region resource_group_name = var.resource_group_name address_space = var.address_space tags = var.tags @@ -57,7 +57,7 @@ resource "azurerm_network_security_group" "this" { for_each = var.network_security_groups name = each.value.name - location = var.location + location = var.region resource_group_name = var.resource_group_name tags = var.tags } @@ -105,7 +105,7 @@ resource "azurerm_route_table" "this" { for_each = var.route_tables name = each.value.name - location = var.location + location = var.region resource_group_name = var.resource_group_name tags = var.tags disable_bgp_route_propagation = each.value.disable_bgp_route_propagation diff --git a/modules/vnet/variables.tf b/modules/vnet/variables.tf index b28643aa..5dfb3b24 100644 --- a/modules/vnet/variables.tf +++ b/modules/vnet/variables.tf @@ -8,7 +8,7 @@ variable "resource_group_name" { type = string } -variable "location" { +variable "region" { description = "The name of the Azure region to deploy the resources in." type = string } diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 2419759b..26dcb618 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -137,4 +137,5 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file From 7b4e3bce9826a8fd8ea41b364d53d0f42a1568b0 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:41:25 +0100 Subject: [PATCH 26/49] refactor: refactor comments and fix validation issues (#27) --- examples/appgw/README.md | 3 +- examples/appgw/example.tfvars | 6 +- examples/appgw/main.tf | 8 +- examples/appgw/variables.tf | 10 +- examples/common_vmseries/README.md | 349 +++++++++--------- examples/common_vmseries/example.tfvars | 8 +- examples/common_vmseries/main.tf | 12 +- examples/common_vmseries/variables.tf | 16 +- .../common_vmseries_and_autoscale/README.md | 349 +++++++++--------- .../example.tfvars | 8 +- .../common_vmseries_and_autoscale/main.tf | 12 +- .../variables.tf | 16 +- examples/dedicated_vmseries/README.md | 349 +++++++++--------- examples/dedicated_vmseries/example.tfvars | 8 +- examples/dedicated_vmseries/main.tf | 12 +- examples/dedicated_vmseries/variables.tf | 16 +- .../README.md | 349 +++++++++--------- .../example.tfvars | 8 +- .../dedicated_vmseries_and_autoscale/main.tf | 14 +- .../variables.tf | 16 +- examples/gwlb_with_vmseries/README.md | 24 +- examples/gwlb_with_vmseries/example.tfvars | 10 +- examples/gwlb_with_vmseries/main.tf | 26 +- examples/gwlb_with_vmseries/variables.tf | 38 +- examples/standalone_panorama/README.md | 1 - examples/standalone_panorama/example.tfvars | 7 +- examples/standalone_panorama/main.tf | 8 +- examples/standalone_panorama/variables.tf | 6 +- examples/standalone_vmseries/README.md | 349 +++++++++--------- examples/standalone_vmseries/example.tfvars | 6 +- examples/standalone_vmseries/main.tf | 12 +- examples/standalone_vmseries/variables.tf | 16 +- examples/virtual_network_gateway/README.md | 1 - .../virtual_network_gateway/example.tfvars | 9 +- examples/virtual_network_gateway/main.tf | 8 +- examples/virtual_network_gateway/variables.tf | 6 +- modules/appgw/README.md | 19 +- modules/appgw/variables.tf | 1 - modules/loadbalancer/README.md | 19 +- modules/loadbalancer/variables.tf | 5 +- modules/virtual_network_gateway/README.md | 1 - modules/virtual_network_gateway/main.tf | 2 +- modules/virtual_network_gateway/variables.tf | 8 +- 43 files changed, 1072 insertions(+), 1079 deletions(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index c2fa8c21..6683ba90 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -232,7 +232,7 @@ map(object({ ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -376,5 +376,4 @@ Default value: `true` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars index cf2844cf..20751f9c 100644 --- a/examples/appgw/example.tfvars +++ b/examples/appgw/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "appgw-example" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { transit = { @@ -37,7 +37,7 @@ vnets = { } } -### LOAD BALANCING ### +# LOAD BALANCING appgws = { "public-empty" = { diff --git a/examples/appgw/main.tf b/examples/appgw/main.tf index f12f69bc..25d91c86 100644 --- a/examples/appgw/main.tf +++ b/examples/appgw/main.tf @@ -1,4 +1,4 @@ -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -19,7 +19,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Create a public IP in order to reuse it in one of the Application Gateways ### +# Create a public IP in order to reuse it in one of the Application Gateways # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip resource "azurerm_public_ip" "this" { @@ -33,7 +33,7 @@ resource "azurerm_public_ip" "this" { tags = var.tags } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -60,7 +60,7 @@ module "vnet" { tags = var.tags } -### Create Application Gateways ### +# Create Application Gateways module "appgw" { source = "../../modules/appgw" diff --git a/examples/appgw/variables.tf b/examples/appgw/variables.tf index b0d9c42d..1d55b8d3 100644 --- a/examples/appgw/variables.tf +++ b/examples/appgw/variables.tf @@ -1,4 +1,5 @@ -### GENERAL +# GENERAL + variable "tags" { description = "Map of tags to assign to the created resources." default = {} @@ -44,8 +45,8 @@ variable "resource_group_name" { type = string } +# VNET -### VNET variable "vnets" { description = <<-EOF A map defining VNETs. @@ -114,7 +115,8 @@ variable "vnets" { })) } -### Application Gateway +# LOAD BALANCING + variable "appgws" { description = <<-EOF A map defining all Application Gateways in the current deployment. @@ -192,7 +194,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index bbe7a694..34a56c01 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -174,7 +174,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -186,6 +185,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -339,176 +339,6 @@ map(object({ -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, -refer to [module documentation](../../modules/appgw/README.md). - -**Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). -It represents the Rules section of an Application Gateway in Azure Portal. - -Below you can find a brief list of most important properties: - -- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. -- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. -- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal - deployment. -- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. -- `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend - will be created. -- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -653,7 +483,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -688,7 +518,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -739,6 +569,178 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### availability_sets @@ -1082,5 +1084,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index d6511e6b..408f292d 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "transit-vnet-common" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -125,7 +125,7 @@ vnets = { } } -### LOAD BALANCING ### +# LOAD BALANCING load_balancers = { "public" = { @@ -217,7 +217,7 @@ appgws = { } } -### VM-SERIES ### +# VM-SERIES vmseries = { "fw-1" = { diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index a385d9c2..373ffaf8 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -27,7 +27,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -48,7 +48,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -99,7 +99,7 @@ module "natgw" { depends_on = [module.vnet] } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -146,7 +146,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Application Gateways ### +# Create Application Gateways locals { nics_with_appgw_key = flatten([ @@ -203,7 +203,7 @@ module "appgw" { depends_on = [module.vnet, module.vmseries] } -### Create VM-Series VMs and closely associated resources ### +# Create VM-Series VMs and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 46d25c34..b81bad46 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -177,7 +177,7 @@ variable "natgws" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -193,7 +193,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -226,7 +226,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -312,6 +312,8 @@ variable "appgws" { `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF + default = {} + nullable = false type = map(object({ name = string vnet_key = string @@ -349,7 +351,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -436,7 +438,7 @@ variable "appgws" { })) } -### VM-SERIES ### +# VM-SERIES variable "availability_sets" { description = <<-EOF diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 74ad42ae..a6042f7f 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -205,7 +205,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -217,6 +216,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. @@ -361,176 +361,6 @@ map(object({ -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, -refer to [module documentation](../../modules/appgw/README.md). - -**Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). -It represents the Rules section of an Application Gateway in Azure Portal. - -Below you can find a brief list of most important properties: - -- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. -- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. -- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal - deployment. -- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. -- `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend - will be created. -- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -673,7 +503,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -708,7 +538,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -759,6 +589,178 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### ngfw_metrics @@ -975,5 +977,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 2ce25439..90b29528 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "autoscale-common" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -125,7 +125,7 @@ vnets = { } } -### LOAD BALANCING ### +# LOAD BALANCING load_balancers = { "public" = { @@ -217,7 +217,7 @@ appgws = { } } -### VM-SERIES ### +# VM-SERIES ngfw_metrics = { name = "ngwf-log-analytics-wrksp" diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index ee84b67a..7fc63a3d 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -28,7 +28,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -49,7 +49,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -100,7 +100,7 @@ module "natgw" { depends_on = [module.vnet] } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -147,7 +147,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Application Gateways ### +# Create Application Gateways module "appgw" { source = "../../modules/appgw" @@ -185,7 +185,7 @@ module "appgw" { depends_on = [module.vnet] } -### Create VM-Series VM Scale Sets and closely associated resources ### +# Create VM-Series VM Scale Sets and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 556a5974..71e3692c 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -177,7 +177,7 @@ variable "natgws" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -193,7 +193,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -226,7 +226,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -312,6 +312,8 @@ variable "appgws" { `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF + default = {} + nullable = false type = map(object({ name = string vnet_key = string @@ -349,7 +351,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -436,7 +438,7 @@ variable "appgws" { })) } -### VM-SERIES ### +# VM-SERIES variable "ngfw_metrics" { description = <<-EOF diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 5aa2560c..a9068546 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -178,7 +178,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -190,6 +189,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -343,176 +343,6 @@ map(object({ -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, -refer to [module documentation](../../modules/appgw/README.md). - -**Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). -It represents the Rules section of an Application Gateway in Azure Portal. - -Below you can find a brief list of most important properties: - -- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. -- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. -- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal - deployment. -- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. -- `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend - will be created. -- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -657,7 +487,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -692,7 +522,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -743,6 +573,178 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### availability_sets @@ -1086,5 +1088,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 10a00800..a0e7bca3 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "transit-vnet-dedicated" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -111,7 +111,7 @@ vnets = { } } -### LOAD BALANCING ### +# LOAD BALANCING load_balancers = { "public" = { @@ -156,7 +156,7 @@ load_balancers = { } } -### VM-SERIES ### +# VM-SERIES ngfw_metrics = { name = "metrics" diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index a385d9c2..373ffaf8 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -27,7 +27,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -48,7 +48,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -99,7 +99,7 @@ module "natgw" { depends_on = [module.vnet] } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -146,7 +146,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Application Gateways ### +# Create Application Gateways locals { nics_with_appgw_key = flatten([ @@ -203,7 +203,7 @@ module "appgw" { depends_on = [module.vnet, module.vmseries] } -### Create VM-Series VMs and closely associated resources ### +# Create VM-Series VMs and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 46d25c34..b81bad46 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -177,7 +177,7 @@ variable "natgws" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -193,7 +193,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -226,7 +226,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -312,6 +312,8 @@ variable "appgws" { `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF + default = {} + nullable = false type = map(object({ name = string vnet_key = string @@ -349,7 +351,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -436,7 +438,7 @@ variable "appgws" { })) } -### VM-SERIES ### +# VM-SERIES variable "availability_sets" { description = <<-EOF diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index c2a3ec7b..356102c8 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -201,7 +201,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -213,6 +212,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. @@ -357,176 +357,6 @@ map(object({ -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, -refer to [module documentation](../../modules/appgw/README.md). - -**Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). -It represents the Rules section of an Application Gateway in Azure Portal. - -Below you can find a brief list of most important properties: - -- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. -- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. -- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal - deployment. -- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. -- `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend - will be created. -- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -669,7 +499,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -704,7 +534,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -755,6 +585,178 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### ngfw_metrics @@ -971,5 +973,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index a1a1af2a..3a4b7f49 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "autoscale-dedicated" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -124,7 +124,7 @@ natgws = { } } -### LOAD BALANCING ### +# LOAD BALANCING load_balancers = { "public" = { @@ -175,7 +175,7 @@ load_balancers = { } } -### VM-SERIES ### +# VM-SERIES ngfw_metrics = { name = "ngwf-log-analytics-wrksp" diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index ee84b67a..2ebc6bfc 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -28,7 +28,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -49,7 +49,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -82,7 +82,7 @@ module "natgw" { for_each = var.natgws create_natgw = each.value.create_natgw - name = each.value.natgw.create ? "${var.name_prefix}${each.value.name}" : each.value.name + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) region = var.region zone = try(each.value.zone, null) @@ -100,7 +100,7 @@ module "natgw" { depends_on = [module.vnet] } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -147,7 +147,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Application Gateways ### +# Create Application Gateways module "appgw" { source = "../../modules/appgw" @@ -185,7 +185,7 @@ module "appgw" { depends_on = [module.vnet] } -### Create VM-Series VM Scale Sets and closely associated resources ### +# Create VM-Series VM Scale Sets and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 556a5974..71e3692c 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -177,7 +177,7 @@ variable "natgws" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -193,7 +193,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -226,7 +226,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -312,6 +312,8 @@ variable "appgws" { `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF + default = {} + nullable = false type = map(object({ name = string vnet_key = string @@ -349,7 +351,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -436,7 +438,7 @@ variable "appgws" { })) } -### VM-SERIES ### +# VM-SERIES variable "ngfw_metrics" { description = <<-EOF diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 7470b9bf..69d734df 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -322,7 +322,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -357,7 +357,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -713,15 +713,11 @@ The most basic properties are as follows: **Note!** \ Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. + + - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a data + Load Balancer health checks. - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when `ngfw_metrics` module is defined and used in this example. The Application Insights Instrumentation Key will be populated automatically. @@ -776,8 +772,7 @@ map(object({ static_files = optional(map(string), {}) bootstrap_package_path = optional(string) bootstrap_xml_template = optional(string) - private_snet_key = optional(string) - public_snet_key = optional(string) + data_snet_key = optional(string) ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) @@ -802,6 +797,8 @@ map(object({ public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) application_gateway_key = optional(string) })) })) @@ -823,5 +820,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 6b4fe995..aca2a00b 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "gwlb" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -104,7 +104,7 @@ vnets = { } } -### LOAD BALANCING ### +# LOAD BALANCING load_balancers = { "app1" = { @@ -186,7 +186,7 @@ gateway_load_balancers = { } } -### VM-SERIES ### +# VM-SERIES bootstrap_storages = { "bootstrap" = { @@ -262,7 +262,7 @@ vmseries = { } } -### TEST INFRASTRUCTURE ### +# TEST INFRASTRUCTURE appvms = { app1vm01 = { diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index dd56b64f..5b1e1150 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -27,7 +27,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -48,7 +48,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -75,7 +75,7 @@ module "vnet" { tags = var.tags } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -123,7 +123,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Gateway Load Balancers ### +# Create Gateway Load Balancers module "gwlb" { for_each = var.gateway_load_balancers @@ -148,8 +148,7 @@ module "gwlb" { tags = var.tags } - -### Create VM-Series VMs and closely associated resources ### +# Create VM-Series VMs and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" @@ -178,16 +177,15 @@ module "ngfw_metrics" { resource "local_file" "bootstrap_xml" { for_each = { for k, v in var.vmseries : - k => v.virtual_machine - if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + k => v if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) } filename = "files/${each.key}-bootstrap.xml" content = templatefile( - each.value.bootstrap_package.bootstrap_xml_template, + each.value.virtual_machine.bootstrap_package.bootstrap_xml_template, { data_gateway_ip = cidrhost( - module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.data_snet_key], + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.virtual_machine.bootstrap_package.data_snet_key], 1 ) @@ -196,7 +194,7 @@ resource "local_file" "bootstrap_xml" { null ) - ai_update_interval = each.value.bootstrap_package.ai_update_interval + ai_update_interval = each.value.virtual_machine.bootstrap_package.ai_update_interval } ) @@ -302,7 +300,7 @@ module "vmseries" { interfaces = [for v in each.value.interfaces : { name = "${var.name_prefix}${v.name}" - subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[v.subnet_key] create_public_ip = v.create_public_ip public_ip_name = v.create_public_ip ? "${var.name_prefix}${ coalesce(v.public_ip_name, "${v.name}-pip") @@ -328,7 +326,7 @@ module "vmseries" { ] } -### Create test infrastructure ### +# Create test infrastructure module "appvm" { for_each = var.appvms diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index 703a678f..8504485d 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -114,7 +114,7 @@ variable "vnets" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -130,7 +130,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -163,7 +163,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -263,7 +263,7 @@ variable "gateway_load_balancers" { })) } -### VM-SERIES ### +# VM-SERIES variable "availability_sets" { description = <<-EOF @@ -486,15 +486,11 @@ variable "vmseries" { **Note!** \ Day0 configuration is **not meant** to be **secure**. It's here merely to help with the basic firewall setup. When `bootstrap_xml_template` is set, one of the following properties might be required. - - - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a private - Load Balancer health checks and for Inbound traffic. - - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key - pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to - identify a VNET). The Subnet definition is used to calculate static routes for a public - Load Balancer health checks and for Outbound traffic. + + - `data_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a data Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a data + Load Balancer health checks. - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when `ngfw_metrics` module is defined and used in this example. The Application Insights Instrumentation Key will be populated automatically. @@ -547,8 +543,7 @@ variable "vmseries" { static_files = optional(map(string), {}) bootstrap_package_path = optional(string) bootstrap_xml_template = optional(string) - private_snet_key = optional(string) - public_snet_key = optional(string) + data_snet_key = optional(string) ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) @@ -573,6 +568,8 @@ variable "vmseries" { public_ip_resource_group_name = optional(string) private_ip_address = optional(string) load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) application_gateway_key = optional(string) })) })) @@ -590,17 +587,16 @@ variable "vmseries" { condition = alltrue([ for _, v in var.vmseries : v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? ( - v.virtual_machine.bootstrap_package.private_snet_key != null && - v.virtual_machine.bootstrap_package.public_snet_key != null + v.virtual_machine.bootstrap_package.data_snet_key != null ) : true if v.virtual_machine.bootstrap_package != null ]) error_message = <<-EOF - The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + The `data_snet_key` is required when `bootstrap_xml_template` is set. EOF } } -### TEST INFRASTRUCTURE ### +# TEST INFRASTRUCTURE variable "appvms" { description = <<-EOF diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 45fc8635..07ed7a63 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -475,5 +475,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index b70b89f5..1aa23bea 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "panorama" @@ -8,9 +8,8 @@ tags = { "CreatedBy" = "Palo Alto Networks" "CreatedWith" = "Terraform" } -enable_zones = false -### NETWORK ### +# NETWORK vnets = { "vnet" = { @@ -44,7 +43,7 @@ vnets = { } } -### PANORAMA ### +# PANORAMA panoramas = { "pn-1" = { diff --git a/examples/standalone_panorama/main.tf b/examples/standalone_panorama/main.tf index bb21770a..3c9eb63e 100644 --- a/examples/standalone_panorama/main.tf +++ b/examples/standalone_panorama/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -27,7 +27,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -48,7 +48,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -75,7 +75,7 @@ module "vnet" { tags = var.tags } -### Create Panorama VMs and closely associated resources ### +# Create Panorama VMs and closely associated resources resource "azurerm_availability_set" "this" { for_each = var.availability_sets diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 7ea23d53..503ebc45 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -114,7 +114,7 @@ variable "vnets" { })) } -### PANORAMA ### +# PANORAMA variable "availability_sets" { description = <<-EOF diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 800b9d8c..9b182053 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -118,7 +118,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. -[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. ## Module's Optional Inputs @@ -130,6 +129,7 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. @@ -283,176 +283,6 @@ map(object({ -#### appgws - -A map defining all Application Gateways in the current deployment. - -For detailed documentation on how to configure this resource, for available properties, especially for the defaults, -refer to [module documentation](../../modules/appgw/README.md). - -**Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). -It represents the Rules section of an Application Gateway in Azure Portal. - -Below you can find a brief list of most important properties: - -- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. -- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet - described by `subnet_key`. -- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an - Application Gateway V2 dedicated subnet. -- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal - deployment. -- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created - Public IP will have it's name prefixes with `var.name_prefix`. -- `listeners` - (`map`, required) defines Application Gateway's Listeners, see - [module's documentation](../../modules/appgw/README.md#listeners) for details. -- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend - will be created. -- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend - settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. -- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see - [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see - [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects - definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, - see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. -- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either - `backend_setting`, `redirect` or `url_path_map`, see - [module's documentation](../../modules/appgw/README.md#rules) for details. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - zones = optional(list(string)) - public_ip = object({ - name = string - create = optional(bool, true) - resource_group_name = optional(string) - }) - domain_name_label = optional(string) - capacity = optional(object({ - static = optional(number) - autoscale = optional(object({ - min = number - max = number - })) - })) - enable_http2 = optional(bool) - waf = optional(object({ - prevention_mode = bool - rule_set_type = optional(string) - rule_set_version = optional(string) - })) - managed_identities = optional(list(string)) - global_ssl_policy = optional(object({ - type = optional(string) - name = optional(string) - min_protocol_version = optional(string) - cipher_suites = optional(list(string)) - })) - ssl_profiles = optional(map(object({ - name = string - ssl_policy_name = optional(string) - ssl_policy_min_protocol_version = optional(string) - ssl_policy_cipher_suites = optional(list(string)) - }))) - frontend_ip_configuration_name = optional(string) - listeners = map(object({ - name = string - port = number - protocol = optional(string) - host_names = optional(list(string)) - ssl_profile_name = optional(string) - ssl_certificate_path = optional(string) - ssl_certificate_pass = optional(string) - ssl_certificate_vault_id = optional(string) - custom_error_pages = optional(map(string)) - })) - backend_pool = optional(object({ - name = optional(string) - vmseries_ips = optional(list(string)) - })) - backend_settings = optional(map(object({ - name = string - port = number - protocol = string - path = optional(string) - hostname_from_backend = optional(string) - hostname = optional(string) - timeout = optional(number) - use_cookie_based_affinity = optional(bool) - affinity_cookie_name = optional(string) - probe = optional(string) - root_certs = optional(map(object({ - name = string - path = string - }))) - }))) - probes = optional(map(object({ - name = string - path = string - host = optional(string) - port = optional(number) - protocol = optional(string) - interval = optional(number) - timeout = optional(number) - threshold = optional(number) - match_code = optional(list(number)) - match_body = optional(string) - }))) - rewrites = optional(map(object({ - name = optional(string) - rules = optional(map(object({ - name = string - sequence = number - conditions = optional(map(object({ - pattern = string - ignore_case = optional(bool) - negate = optional(bool) - }))) - request_headers = optional(map(string)) - response_headers = optional(map(string)) - }))) - }))) - redirects = optional(map(object({ - name = string - type = string - target_listener_key = optional(string) - target_url = optional(string) - include_path = optional(bool) - include_query_string = optional(bool) - }))) - url_path_maps = optional(map(object({ - name = string - backend_key = string - path_rules = optional(map(object({ - paths = list(string) - backend_key = optional(string) - redirect_key = optional(string) - }))) - }))) - rules = map(object({ - name = string - priority = number - backend_key = optional(string) - listener_key = string - rewrite_key = optional(string) - url_path_map_key = optional(string) - redirect_key = optional(string) - })) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> @@ -597,7 +427,7 @@ Following properties are available: map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. -- `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. +- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -632,7 +462,7 @@ map(object({ name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -683,6 +513,178 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### appgws + +A map defining all Application Gateways in the current deployment. + +For detailed documentation on how to configure this resource, for available properties, especially for the defaults, +refer to [module documentation](../../modules/appgw/README.md). + +**Note!** \ +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +It represents the Rules section of an Application Gateway in Azure Portal. + +Below you can find a brief list of most important properties: + +- `name` - (`string`, required) the name of the Application Gateway, will be prefixed with `var.name_prefix`. +- `vnet_key` - (`string`, required) a key pointing to a VNET definition in the `var.vnets` map that stores the Subnet + described by `subnet_key`. +- `subnet_key` - (`string`, required) a key pointing to a Subnet definition in the `var.vnets` map, this has to be an + Application Gateway V2 dedicated subnet. +- `zones` - (`list`, optional, defaults to module default) parameter controlling if this is a zonal, or a non-zonal + deployment. +- `public_ip` - (`map`, required) defines a Public IP resource used by the Application Gateway instance, a newly created + Public IP will have it's name prefixes with `var.name_prefix`. +- `listeners` - (`map`, required) defines Application Gateway's Listeners, see + [module's documentation](../../modules/appgw/README.md#listeners) for details. +- `backend_pool` - (`map`, optional, defaults to module default) backend pool definition, when skipped an empty backend + will be created. +- `backend_settings` - (`map`, optional, mutually exclusive with `redirects` and `url_path_maps`) defines HTTP backend + settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. +- `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see + [module's documentation](../../modules/appgw/README.md#probes) for details. +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + [module's documentation](../../modules/appgw/README.md#rewrites) for details. +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. +- `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either + `backend_setting`, `redirect` or `url_path_map`, see + [module's documentation](../../modules/appgw/README.md#rules) for details. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = string + subnet_key = string + zones = optional(list(string)) + public_ip = object({ + name = string + create = optional(bool, true) + resource_group_name = optional(string) + }) + domain_name_label = optional(string) + capacity = optional(object({ + static = optional(number) + autoscale = optional(object({ + min = number + max = number + })) + })) + enable_http2 = optional(bool) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string) + rule_set_version = optional(string) + })) + managed_identities = optional(list(string)) + global_ssl_policy = optional(object({ + type = optional(string) + name = optional(string) + min_protocol_version = optional(string) + cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + }))) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + listeners = map(object({ + name = string + port = number + protocol = optional(string) + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string)) + })) + backend_pool = optional(object({ + name = optional(string) + vmseries_ips = optional(list(string)) + })) + backend_settings = optional(map(object({ + name = string + port = number + protocol = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + timeout = optional(number) + use_cookie_based_affinity = optional(bool) + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + }))) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string) + interval = optional(number) + timeout = optional(number) + threshold = optional(number) + match_code = optional(list(number)) + match_body = optional(string) + }))) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool) + negate = optional(bool) + }))) + request_headers = optional(map(string)) + response_headers = optional(map(string)) + }))) + }))) + redirects = optional(map(object({ + name = string + type = string + target_listener_key = optional(string) + target_url = optional(string) + include_path = optional(bool) + include_query_string = optional(bool) + }))) + url_path_maps = optional(map(object({ + name = string + backend_key = string + path_rules = optional(map(object({ + paths = list(string) + backend_key = optional(string) + redirect_key = optional(string) + }))) + }))) + rules = map(object({ + name = string + priority = number + backend_key = optional(string) + listener_key = string + rewrite_key = optional(string) + url_path_map_key = optional(string) + redirect_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### availability_sets @@ -1026,5 +1028,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 698c0794..6d119752 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "vmseries-standalone" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { "transit" = { @@ -42,7 +42,7 @@ vnets = { } } -### VM-SERIES ### +# VM-SERIES vmseries = { "fw-1" = { diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index a385d9c2..373ffaf8 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { @@ -27,7 +27,7 @@ locals { } } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -48,7 +48,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -99,7 +99,7 @@ module "natgw" { depends_on = [module.vnet] } -### Create Load Balancers, both internal and external ### +# Create Load Balancers, both internal and external module "load_balancer" { source = "../../modules/loadbalancer" @@ -146,7 +146,7 @@ module "load_balancer" { depends_on = [module.vnet] } -### Create Application Gateways ### +# Create Application Gateways locals { nics_with_appgw_key = flatten([ @@ -203,7 +203,7 @@ module "appgw" { depends_on = [module.vnet, module.vmseries] } -### Create VM-Series VMs and closely associated resources ### +# Create VM-Series VMs and closely associated resources module "ngfw_metrics" { source = "../../modules/ngfw_metrics" diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 46d25c34..b81bad46 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -177,7 +177,7 @@ variable "natgws" { })) } -### LOAD BALANCING ### +# LOAD BALANCING variable "load_balancers" { description = <<-EOF @@ -193,7 +193,7 @@ variable "load_balancers" { map that stores the Subnet described by `subnet_key`. - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP configurations. - - `backend_name` - (`string`, optional, defaults to module default) a name of the backend pool to create. + - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load balancing rules, please refer to [module documentation](../../modules/loadbalancer/README.md#health_probes) for more specific use @@ -226,7 +226,7 @@ variable "load_balancers" { name = string vnet_key = optional(string) zones = optional(list(string)) - backend_name = optional(string) + backend_name = optional(string, "vmseries_backend") health_probes = optional(map(object({ name = string protocol = string @@ -312,6 +312,8 @@ variable "appgws" { `backend_setting`, `redirect` or `url_path_map`, see [module's documentation](../../modules/appgw/README.md#rules) for details. EOF + default = {} + nullable = false type = map(object({ name = string vnet_key = string @@ -349,7 +351,7 @@ variable "appgws" { ssl_policy_min_protocol_version = optional(string) ssl_policy_cipher_suites = optional(list(string)) }))) - frontend_ip_configuration_name = optional(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") listeners = map(object({ name = string port = number @@ -436,7 +438,7 @@ variable "appgws" { })) } -### VM-SERIES ### +# VM-SERIES variable "availability_sets" { description = <<-EOF diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index b7745a3e..6dda627c 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -314,5 +314,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/example.tfvars b/examples/virtual_network_gateway/example.tfvars index f6aa9523..3b39d150 100644 --- a/examples/virtual_network_gateway/example.tfvars +++ b/examples/virtual_network_gateway/example.tfvars @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL region = "North Europe" resource_group_name = "vng" @@ -8,7 +8,7 @@ tags = { "CreatedWith" = "Terraform" } -### NETWORK ### +# NETWORK vnets = { transit = { @@ -61,14 +61,13 @@ vnets = { } } -### VIRTUAL NETWORK GATEWAY ### +# VIRTUAL NETWORK GATEWAY virtual_network_gateways = { expressroute = { name = "expressroute" vnet_key = "transit" subnet_key = "vpn" - zones = ["1"] instance_settings = { type = "ExpressRoute" @@ -88,7 +87,6 @@ virtual_network_gateways = { name = "er_policy" vnet_key = "er" subnet_key = "vpn" - zones = ["1"] instance_settings = { type = "ExpressRoute" @@ -108,7 +106,6 @@ virtual_network_gateways = { name = "simple-vpn" vnet_key = "er" subnet_key = "vpn" - zones = [] instance_settings = { type = "Vpn" diff --git a/examples/virtual_network_gateway/main.tf b/examples/virtual_network_gateway/main.tf index 9cbc42e6..91035ff9 100644 --- a/examples/virtual_network_gateway/main.tf +++ b/examples/virtual_network_gateway/main.tf @@ -1,4 +1,4 @@ -### Generate a random password ### +# Generate a random password # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group resource "azurerm_resource_group" "this" { @@ -9,7 +9,7 @@ resource "azurerm_resource_group" "this" { tags = var.tags } -### Create or source a Resource Group ### +# Create or source a Resource Group # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group data "azurerm_resource_group" "this" { @@ -21,7 +21,7 @@ locals { resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] } -### Manage the network required for the topology ### +# Manage the network required for the topology module "vnet" { source = "../../modules/vnet" @@ -48,7 +48,7 @@ module "vnet" { tags = var.tags } -### Create Virtual Network Gateways ### +# Create Virtual Network Gateways module "vng" { source = "../../modules/virtual_network_gateway" diff --git a/examples/virtual_network_gateway/variables.tf b/examples/virtual_network_gateway/variables.tf index 7bcf748c..5d2562fc 100644 --- a/examples/virtual_network_gateway/variables.tf +++ b/examples/virtual_network_gateway/variables.tf @@ -1,4 +1,4 @@ -### GENERAL ### +# GENERAL variable "name_prefix" { description = <<-EOF @@ -46,7 +46,7 @@ variable "tags" { type = map(string) } -### NETWORK ### +# NETWORK variable "vnets" { description = <<-EOF @@ -114,7 +114,7 @@ variable "vnets" { })) } -### VIRTUAL NETWORK GATEWAY ### +# VIRTUAL NETWORK GATEWAY variable "virtual_network_gateways" { description = "Map of Virtual Network Gateways to create." diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 1f01b9a8..22d29d3f 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -819,6 +819,7 @@ Name | Type | Description [`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`subnet_id`](#subnet_id) | `string` | An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway. [`public_ip`](#public_ip) | `object` | A map defining listener's public IP configuration. +[`frontend_ip_configuration_name`](#frontend_ip_configuration_name) | `string` | A frontend IP configuration name. [`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. [`rules`](#rules) | `map` | A map of rules for the Application Gateway. @@ -836,7 +837,6 @@ Name | Type | Description [`managed_identities`](#managed_identities) | `list` | A list of existing User-Assigned Managed Identities. [`global_ssl_policy`](#global_ssl_policy) | `object` | A map defining global SSL settings. [`ssl_profiles`](#ssl_profiles) | `map` | A map of SSL profiles. -[`frontend_ip_configuration_name`](#frontend_ip_configuration_name) | `string` | A frontend IP configuration name. [`backend_pool`](#backend_pool) | `object` | A map defining a backend pool, when skipped will create an empty backend. [`backend_settings`](#backend_settings) | `map` | A map of backend settings for the Application Gateway. [`probes`](#probes) | `map` | A map of probes for the Application Gateway. @@ -946,6 +946,13 @@ object({ +#### frontend_ip_configuration_name + +A frontend IP configuration name. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> #### listeners @@ -1241,15 +1248,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### frontend_ip_configuration_name - -A frontend IP configuration name. - -Type: string - -Default value: `public_ipconfig` - -<sup>[back to list](#modules-optional-inputs)</sup> #### backend_pool @@ -1487,5 +1485,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/variables.tf b/modules/appgw/variables.tf index 3262c249..1490bead 100644 --- a/modules/appgw/variables.tf +++ b/modules/appgw/variables.tf @@ -322,7 +322,6 @@ variable "ssl_profiles" { variable "frontend_ip_configuration_name" { description = "A frontend IP configuration name." - default = "public_ipconfig" type = string } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index e6ba413d..051a10e7 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -90,6 +90,7 @@ Name | Type | Description [`name`](#name) | `string` | The name of the Azure Load Balancer. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. [`region`](#region) | `string` | The name of the Azure region to deploy the resources in. +[`backend_name`](#backend_name) | `string` | The name of the backend pool to create. [`frontend_ips`](#frontend_ips) | `map` | A map of objects describing Load Balancer Frontend IP configurations with respective inbound and outbound rules. @@ -99,7 +100,6 @@ Name | Type | Description --- | --- | --- [`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`zones`](#zones) | `list` | Controls zones for Load Balancer's fronted IP configurations. -[`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. @@ -173,6 +173,13 @@ Type: string +#### backend_name + +The name of the backend pool to create. All frontends of the Load Balancer always use the same backend. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> #### frontend_ips @@ -377,15 +384,6 @@ Default value: `[1 2 3]` <sup>[back to list](#modules-optional-inputs)</sup> -#### 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` - -<sup>[back to list](#modules-optional-inputs)</sup> #### health_probes @@ -462,5 +460,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/variables.tf b/modules/loadbalancer/variables.tf index 8dad32ec..f57d4644 100644 --- a/modules/loadbalancer/variables.tf +++ b/modules/loadbalancer/variables.tf @@ -42,12 +42,11 @@ variable "zones" { variable "backend_name" { 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 validation { - condition = can(regex("^\\w[\\w_\\.-]{0,78}(\\w|_)$", var.backend_name)) + condition = can(regex("^\\w[\\w\\_\\.\\-]{0,78}(\\w|\\_)$", var.backend_name)) error_message = <<-EOF - The `backend_name` property can be maximum 80 chars long and most consist of word characters, dots, underscores and dashes. + The `backend_name` property can be maximum 80 chars long and must consist of word characters, dots, underscores and dashes. It has to start with a word character and end with one or with an underscore. EOF } diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index 8926a617..c6b525e2 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -814,5 +814,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_network_gateway/main.tf b/modules/virtual_network_gateway/main.tf index d6aabc36..37935798 100644 --- a/modules/virtual_network_gateway/main.tf +++ b/modules/virtual_network_gateway/main.tf @@ -80,7 +80,7 @@ resource "azurerm_virtual_network_gateway" "this" { } dynamic "custom_route" { - for_each = var.vpn_clients.custom_routes + for_each = try(var.vpn_clients.custom_routes, false) ? [1] : [] content { address_prefixes = custom_route.value } diff --git a/modules/virtual_network_gateway/variables.tf b/modules/virtual_network_gateway/variables.tf index cbb9904b..8ce512ad 100644 --- a/modules/virtual_network_gateway/variables.tf +++ b/modules/virtual_network_gateway/variables.tf @@ -41,7 +41,7 @@ variable "zones" { condition = var.zones == null || (var.zones != null ? ( length(var.zones) == 3 && length(setsubtract(var.zones, ["1", "2", "3"])) == 0 ) : true) - error_message = "No zones or all 3 zones are expected." + error_message = "No zones (for non-AZ SKU) or all 3 zones (for AZ SKU) are expected." } } @@ -195,14 +195,16 @@ variable "ip_configurations" { })) }) validation { # primary/secondary.name - condition = var.ip_configurations.primary.name != var.ip_configurations.secondary.name + condition = var.ip_configurations.secondary != null ? ( + var.ip_configurations.primary.name != var.ip_configurations.secondary.name + ) : true error_message = <<-EOF The `name` property has to be unique among all IP configurations. EOF } validation { # primary/secondary.private_ip_address_allocation condition = contains(["Dynamic", "Static"], var.ip_configurations.primary.private_ip_address_allocation) && ( - var.ip_configurations.secondary.name != null ? ( + var.ip_configurations.secondary != null ? ( contains(["Dynamic", "Static"], var.ip_configurations.secondary.private_ip_address_allocation) ) : true ) From 7f01d41caebfc9631ef8159c54bdb25556db3a75 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:27:20 +0100 Subject: [PATCH 27/49] refactor(module/test_infrastructure): module creation and adjusted examples (#10) --- examples/common_vmseries/.header.md | 16 +- examples/common_vmseries/README.md | 249 ++++++++++- examples/common_vmseries/example.tfvars | 137 ++++++ examples/common_vmseries/main.tf | 58 ++- examples/common_vmseries/outputs.tf | 27 ++ examples/common_vmseries/variables.tf | 221 ++++++++++ .../common_vmseries_and_autoscale/.header.md | 18 +- .../common_vmseries_and_autoscale/README.md | 251 ++++++++++- .../example.tfvars | 137 ++++++ .../common_vmseries_and_autoscale/main.tf | 52 +++ .../common_vmseries_and_autoscale/outputs.tf | 27 ++ .../variables.tf | 221 ++++++++++ examples/dedicated_vmseries/.header.md | 16 +- examples/dedicated_vmseries/README.md | 249 ++++++++++- examples/dedicated_vmseries/example.tfvars | 137 ++++++ examples/dedicated_vmseries/main.tf | 52 +++ examples/dedicated_vmseries/outputs.tf | 27 ++ examples/dedicated_vmseries/variables.tf | 221 ++++++++++ .../.header.md | 20 +- .../README.md | 253 ++++++++++- .../example.tfvars | 149 ++++++- .../dedicated_vmseries_and_autoscale/main.tf | 52 +++ .../outputs.tf | 27 ++ .../variables.tf | 221 ++++++++++ examples/gwlb_with_vmseries/.header.md | 48 +- examples/gwlb_with_vmseries/README.md | 378 +++++++++++----- examples/gwlb_with_vmseries/example.tfvars | 240 ++++++---- examples/gwlb_with_vmseries/main.tf | 134 +++--- examples/gwlb_with_vmseries/outputs.tf | 12 +- examples/gwlb_with_vmseries/variables.tf | 310 +++++++++---- examples/standalone_panorama/.header.md | 6 +- examples/standalone_panorama/README.md | 6 +- examples/standalone_vmseries/.header.md | 8 +- examples/standalone_vmseries/README.md | 238 +++++++++- examples/standalone_vmseries/main.tf | 52 +++ examples/standalone_vmseries/outputs.tf | 7 + examples/standalone_vmseries/variables.tf | 221 ++++++++++ examples/test_infrastructure/.header.md | 16 - examples/test_infrastructure/README.md | 345 --------------- examples/test_infrastructure/example.tfvars | 102 ----- examples/test_infrastructure/main.tf | 154 ------- examples/test_infrastructure/main_test.go | 62 --- examples/test_infrastructure/outputs.tf | 15 - examples/test_infrastructure/variables.tf | 180 -------- examples/test_infrastructure/versions.tf | 15 - modules/test_infrastructure/.README.md | 414 ++++++++++++++++++ modules/test_infrastructure/.header.md | 9 + modules/test_infrastructure/main.tf | 213 +++++++++ modules/test_infrastructure/main_test.go | 11 + modules/test_infrastructure/outputs.tf | 12 + modules/test_infrastructure/variables.tf | 271 ++++++++++++ modules/test_infrastructure/versions.tf | 9 + 52 files changed, 4993 insertions(+), 1333 deletions(-) delete mode 100644 examples/test_infrastructure/.header.md delete mode 100644 examples/test_infrastructure/README.md delete mode 100644 examples/test_infrastructure/example.tfvars delete mode 100644 examples/test_infrastructure/main.tf delete mode 100644 examples/test_infrastructure/main_test.go delete mode 100644 examples/test_infrastructure/outputs.tf delete mode 100644 examples/test_infrastructure/variables.tf delete mode 100644 examples/test_infrastructure/versions.tf create mode 100644 modules/test_infrastructure/.README.md create mode 100644 modules/test_infrastructure/.header.md create mode 100644 modules/test_infrastructure/main.tf create mode 100644 modules/test_infrastructure/main_test.go create mode 100644 modules/test_infrastructure/outputs.tf create mode 100644 modules/test_infrastructure/variables.tf create mode 100644 modules/test_infrastructure/versions.tf diff --git a/examples/common_vmseries/.header.md b/examples/common_vmseries/.header.md index 4de89653..bb6de35f 100644 --- a/examples/common_vmseries/.header.md +++ b/examples/common_vmseries/.header.md @@ -39,7 +39,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments because the number of firewalls low. However, the technical integration complexity is high. -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/8e8da6e0-afba-4bb5-b2c7-a95c7250dab3) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/9e330a37-3679-419a-8aa3-aa963cb4faf2) This reference architecture consists of: @@ -58,12 +58,20 @@ This reference architecture consists of: - management interface - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -82,14 +90,14 @@ A list of requirements might vary depending on the platform used to deploy the i - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 34a56c01..cd5d7aec 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -40,7 +40,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for proof-of-concepts and smaller scale deployments because the number of firewalls low. However, the technical integration complexity is high. -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/8e8da6e0-afba-4bb5-b2c7-a95c7250dab3) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/9e330a37-3679-419a-8aa3-aa963cb4faf2) This reference architecture consists of: @@ -59,12 +59,20 @@ This reference architecture consists of: - management interface - public interface - due to use of a public Load Balancer this public IP is used mainly for outgoing traffic - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -83,14 +91,14 @@ A list of requirements might vary depending on the platform used to deploy the i - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -190,6 +198,7 @@ Name | Type | Description [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -204,6 +213,10 @@ Name | Description `lb_frontend_ips` | IP Addresses of the load balancers. `vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. `bootstrap_storage_urls` | +`test_vms_usernames` | Initial administrative username to use for test VMs. +`test_vms_passwords` | Initial administrative password to use for test VMs. +`test_vms_ips` | IP Addresses of the test VMs. +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -230,6 +243,7 @@ Name | Version | Source | Description `ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -346,6 +360,7 @@ map(object({ + ### Optional Inputs @@ -1080,6 +1095,232 @@ map(object({ ``` +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + + +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + + Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 408f292d..f8cfdd4b 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -283,3 +283,140 @@ vmseries = { ] } } + +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app1" = { + name = "app1-nsg" + rules = { + web_inbound = { + name = "app1-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.0.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app1-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] + } + } + } + } + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" + } + } + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } + } + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app2" = { + name = "app2-nsg" + rules = { + web_inbound = { + name = "app2-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.1.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app2-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.1.0/26"] + network_security_group_key = "app2" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.1.64/26"] + } + } + } + } + spoke_vms = { + "app2_vm" = { + name = "app2-vm" + vnet_key = "app2" + subnet_key = "vms" + } + } + bastions = { + "app2_bastion" = { + name = "app2-bastion" + vnet_key = "app2" + subnet_key = "bastion" + } + } + } +} diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 373ffaf8..7aa0163e 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -2,9 +2,9 @@ # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { - count = anytrue([ - for _, v in var.vmseries : v.authentication.password == null - ]) ? 1 : 0 + count = anytrue([for _, v in var.vmseries : v.authentication.password == null]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 @@ -390,3 +390,55 @@ module "vmseries" { module.bootstrap, ] } + +# Create test infrastructure + +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} + +module "test_infrastructure" { + source = "../../modules/test_infrastructure" + + for_each = var.test_infrastructure + + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } + + tags = var.tags + depends_on = [module.vnet] +} diff --git a/examples/common_vmseries/outputs.tf b/examples/common_vmseries/outputs.tf index f946a80b..03613e39 100644 --- a/examples/common_vmseries/outputs.tf +++ b/examples/common_vmseries/outputs.tf @@ -37,3 +37,30 @@ output "bootstrap_storage_urls" { value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } + +output "test_vms_usernames" { + description = "Initial administrative username to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.username + } : null +} + +output "test_vms_passwords" { + description = "Initial administrative password to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.password + } : null + sensitive = true +} + +output "test_vms_ips" { + description = "IP Addresses of the test VMs." + value = length(var.test_infrastructure) > 0 ? { for k, v in module.test_infrastructure : k => v.vm_private_ips } : null +} + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} \ No newline at end of file diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index b81bad46..08812d7d 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -774,3 +774,224 @@ variable "vmseries" { EOF } } + +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { + description = <<-EOF + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + EOF + default = {} + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +} diff --git a/examples/common_vmseries_and_autoscale/.header.md b/examples/common_vmseries_and_autoscale/.header.md index c001fc82..5c756293 100644 --- a/examples/common_vmseries_and_autoscale/.header.md +++ b/examples/common_vmseries_and_autoscale/.header.md @@ -47,7 +47,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. -![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/7d363d6a-b394-4851-99b9-03ce8abf379a) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/10289e10-6652-423b-94cd-0dc0b35f9997) This reference architecture consists of: @@ -56,7 +56,7 @@ This reference architecture consists of: - 3 of them dedicated to the firewalls: management, private and public - one dedicated to an Application Gateway - Route Tables and Network Security Groups -- 1 Virtual Machine Scale set: +- 1 Virtual Machine Scale Set: - deployed across availability zones - for inbound, outbound and east-west traffic - with 3 network interfaces: management, public, private @@ -68,6 +68,14 @@ This reference architecture consists of: - private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic - an Application Insights, used to store the custom PanOS metrics sent from firewalls in scale set - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. **Disclaimer!** \ Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private @@ -91,7 +99,7 @@ firewalls in common option, and are automatically registered to Azure Load Balan A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -120,14 +128,14 @@ A non-platform requirement would be a running Panorama instance. For full automa - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for the scale set [`common`](./example.tfvars#L224). -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index a6042f7f..34675120 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -48,7 +48,7 @@ and may present scale limitations with all traffic flowing through a single set that occurs when traffic crosses virtual routers. This option is suitable for smaller scale deployments because inbound and outbound traffic flows occur on the same set of firewalls. However, the technical integration complexity is high. -![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/7d363d6a-b394-4851-99b9-03ce8abf379a) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/10289e10-6652-423b-94cd-0dc0b35f9997) This reference architecture consists of: @@ -57,7 +57,7 @@ This reference architecture consists of: - 3 of them dedicated to the firewalls: management, private and public - one dedicated to an Application Gateway - Route Tables and Network Security Groups -- 1 Virtual Machine Scale set: +- 1 Virtual Machine Scale Set: - deployed across availability zones - for inbound, outbound and east-west traffic - with 3 network interfaces: management, public, private @@ -69,6 +69,14 @@ This reference architecture consists of: - private - in front of the private interfaces of the firewalls in VMSS, for outgoing and east-west traffic - an Application Insights, used to store the custom PanOS metrics sent from firewalls in scale set - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. **Disclaimer!** \ Public IP addresses are assigned to management interfaces in this example in order to simplify the deployment. With a private @@ -92,7 +100,7 @@ firewalls in common option, and are automatically registered to Azure Load Balan A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -121,14 +129,14 @@ A non-platform requirement would be a running Panorama instance. For full automa - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for the scale set [`common`](./example.tfvars#L224). -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -219,6 +227,7 @@ Name | Type | Description [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -230,6 +239,10 @@ Name | Description `passwords` | Initial firewall administrative passwords for all deployed Scale Sets. `metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. `lb_frontend_ips` | IP Addresses of the load balancers. +`test_vms_usernames` | Initial administrative username to use for test VMs. +`test_vms_passwords` | Initial administrative password to use for test VMs. +`test_vms_ips` | IP Addresses of the test VMs. +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -254,6 +267,7 @@ Name | Version | Source | Description `appgw` | - | ../../modules/appgw | `ngfw_metrics` | - | ../../modules/ngfw_metrics | `vmss` | - | ../../modules/vmss | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -366,6 +380,7 @@ map(object({ + ### Optional Inputs @@ -973,6 +988,232 @@ map(object({ ``` +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + + +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + + Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 90b29528..14118439 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -293,3 +293,140 @@ scale_sets = { ] } } + +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app1" = { + name = "app1-nsg" + rules = { + web_inbound = { + name = "app1-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.0.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app1-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] + } + } + } + } + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" + } + } + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } + } + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app2" = { + name = "app2-nsg" + rules = { + web_inbound = { + name = "app2-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.1.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app2-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.1.0/26"] + network_security_group_key = "app2" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.1.64/26"] + } + } + } + } + spoke_vms = { + "app2_vm" = { + name = "app2-vm" + vnet_key = "app2" + subnet_key = "vms" + } + } + bastions = { + "app2_bastion" = { + name = "app2-bastion" + vnet_key = "app2" + subnet_key = "bastion" + } + } + } +} diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 7fc63a3d..60b8ea76 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -247,3 +247,55 @@ module "vmss" { tags = var.tags depends_on = [module.vnet, module.load_balancer, module.appgw] } + +# Create test infrastructure + +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} + +module "test_infrastructure" { + source = "../../modules/test_infrastructure" + + for_each = var.test_infrastructure + + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } + + tags = var.tags + depends_on = [module.vnet] +} diff --git a/examples/common_vmseries_and_autoscale/outputs.tf b/examples/common_vmseries_and_autoscale/outputs.tf index eb6c8a5e..ecd90478 100644 --- a/examples/common_vmseries_and_autoscale/outputs.tf +++ b/examples/common_vmseries_and_autoscale/outputs.tf @@ -19,3 +19,30 @@ output "lb_frontend_ips" { description = "IP Addresses of the load balancers." value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null } + +output "test_vms_usernames" { + description = "Initial administrative username to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.username + } : null +} + +output "test_vms_passwords" { + description = "Initial administrative password to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.password + } : null + sensitive = true +} + +output "test_vms_ips" { + description = "IP Addresses of the test VMs." + value = length(var.test_infrastructure) > 0 ? { for k, v in module.test_infrastructure : k => v.vm_private_ips } : null +} + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 71e3692c..2dd056cb 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -639,3 +639,224 @@ variable "scale_sets" { })), []) })) } + +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { + description = <<-EOF + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + EOF + default = {} + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +} diff --git a/examples/dedicated_vmseries/.header.md b/examples/dedicated_vmseries/.header.md index 0c812dab..f4edff80 100644 --- a/examples/dedicated_vmseries/.header.md +++ b/examples/dedicated_vmseries/.header.md @@ -40,7 +40,7 @@ The second set of VM-Series firewalls services all outbound, east-west, and ente choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/3644469f-5f0f-44f9-8990-010c8bcf1cec) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a5054270-514e-4c90-9601-133c6dc2ca66) This reference architecture consists of: @@ -58,12 +58,20 @@ This reference architecture consists of: - with public IP addresses assigned to: - management interface - public interface +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -84,14 +92,14 @@ A list of requirements might vary depending on the platform used to deploy the i look at the `TODO` markers) - copy the [`init-cfg.sample.txt`](./files/init-cfg.sample.txt) to `init-cfg.txt` and fill it out with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components#id07933d91-15be-414d-bc8d-f2a5f3d8df6b) for details) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index a9068546..3e3b32a3 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -41,7 +41,7 @@ The second set of VM-Series firewalls services all outbound, east-west, and ente choice offers increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/3644469f-5f0f-44f9-8990-010c8bcf1cec) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a5054270-514e-4c90-9601-133c6dc2ca66) This reference architecture consists of: @@ -59,12 +59,20 @@ This reference architecture consists of: - with public IP addresses assigned to: - management interface - public interface +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. ## Prerequisites A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -85,14 +93,14 @@ A list of requirements might vary depending on the platform used to deploy the i look at the `TODO` markers) - copy the [`init-cfg.sample.txt`](./files/init-cfg.sample.txt) to `init-cfg.txt` and fill it out with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/9-1/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components#id07933d91-15be-414d-bc8d-f2a5f3d8df6b) for details) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -194,6 +202,7 @@ Name | Type | Description [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -208,6 +217,10 @@ Name | Description `lb_frontend_ips` | IP Addresses of the load balancers. `vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. `bootstrap_storage_urls` | +`test_vms_usernames` | Initial administrative username to use for test VMs. +`test_vms_passwords` | Initial administrative password to use for test VMs. +`test_vms_ips` | IP Addresses of the test VMs. +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -234,6 +247,7 @@ Name | Version | Source | Description `ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -350,6 +364,7 @@ map(object({ + ### Optional Inputs @@ -1084,6 +1099,232 @@ map(object({ ``` +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + + +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + + Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index a0e7bca3..77246de3 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -314,3 +314,140 @@ vmseries = { ] } } + +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app1" = { + name = "app1-nsg" + rules = { + web_inbound = { + name = "app1-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.0.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app1-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] + } + } + } + } + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" + } + } + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } + } + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app2" = { + name = "app2-nsg" + rules = { + web_inbound = { + name = "app2-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.1.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app2-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.1.0/26"] + network_security_group_key = "app2" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.1.64/26"] + } + } + } + } + spoke_vms = { + "app2_vm" = { + name = "app2-vm" + vnet_key = "app2" + subnet_key = "vms" + } + } + bastions = { + "app2_bastion" = { + name = "app2-bastion" + vnet_key = "app2" + subnet_key = "bastion" + } + } + } +} diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 373ffaf8..c3038507 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -390,3 +390,55 @@ module "vmseries" { module.bootstrap, ] } + +# Create test infrastructure + +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} + +module "test_infrastructure" { + source = "../../modules/test_infrastructure" + + for_each = var.test_infrastructure + + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } + + tags = var.tags + depends_on = [module.vnet] +} diff --git a/examples/dedicated_vmseries/outputs.tf b/examples/dedicated_vmseries/outputs.tf index f946a80b..03613e39 100644 --- a/examples/dedicated_vmseries/outputs.tf +++ b/examples/dedicated_vmseries/outputs.tf @@ -37,3 +37,30 @@ output "bootstrap_storage_urls" { value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } + +output "test_vms_usernames" { + description = "Initial administrative username to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.username + } : null +} + +output "test_vms_passwords" { + description = "Initial administrative password to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.password + } : null + sensitive = true +} + +output "test_vms_ips" { + description = "IP Addresses of the test VMs." + value = length(var.test_infrastructure) > 0 ? { for k, v in module.test_infrastructure : k => v.vm_private_ips } : null +} + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} \ No newline at end of file diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index b81bad46..08812d7d 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -774,3 +774,224 @@ variable "vmseries" { EOF } } + +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { + description = <<-EOF + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + EOF + default = {} + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +} diff --git a/examples/dedicated_vmseries_and_autoscale/.header.md b/examples/dedicated_vmseries_and_autoscale/.header.md index cb288864..63ba1637 100644 --- a/examples/dedicated_vmseries_and_autoscale/.header.md +++ b/examples/dedicated_vmseries_and_autoscale/.header.md @@ -48,7 +48,7 @@ set of VM-Series firewalls services all outbound, east-west, and enterprise netw increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/757005dc-3e24-4b39-8a69-7b3fbf9819cb) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a3104f98-4981-47ce-b96e-84e026268d1d) This reference architecture consists of: @@ -57,7 +57,7 @@ This reference architecture consists of: - 3 of them dedicated to the firewalls: management, private and public - one dedicated to an Application Gateway - Route Tables and Network Security Groups -- 2 Virtual Machine Scale sets: +- 2 Virtual Machine Scale Sets: - one for inbound, one for outbound and east-west traffic - with 3 network interfaces: management, public, private - no public addresses are assigned to firewalls' interfaces @@ -68,9 +68,15 @@ This reference architecture consists of: - firewalls mainly) interfaces - 2 Application Insights, one per each scale set, used to store the custom PanOS metrics - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts -A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's -default mechanism). +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. +- This is an example of a non-zonal deployment. Resiliency is maintained by using fault domains (Scale Set's default mechanism). ### Auto Scaling VM-Series @@ -85,7 +91,7 @@ firewalls, and are automatically registered to Azure Load Balancers. A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -116,14 +122,14 @@ requirements: look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for each scale set ([inbound](./example.tfvars#L205) and [obew](./example.tfvars#L249) separately). -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 356102c8..a823dae2 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -49,7 +49,7 @@ set of VM-Series firewalls services all outbound, east-west, and enterprise netw increased scale and operational resiliency and reduces the chances of high bandwidth use from the inbound traffic flows affecting other traffic flows within the deployment. -![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/757005dc-3e24-4b39-8a69-7b3fbf9819cb) +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a3104f98-4981-47ce-b96e-84e026268d1d) This reference architecture consists of: @@ -58,7 +58,7 @@ This reference architecture consists of: - 3 of them dedicated to the firewalls: management, private and public - one dedicated to an Application Gateway - Route Tables and Network Security Groups -- 2 Virtual Machine Scale sets: +- 2 Virtual Machine Scale Sets: - one for inbound, one for outbound and east-west traffic - with 3 network interfaces: management, public, private - no public addresses are assigned to firewalls' interfaces @@ -69,9 +69,15 @@ This reference architecture consists of: - firewalls mainly) interfaces - 2 Application Insights, one per each scale set, used to store the custom PanOS metrics - an Application Gateway, serving as a reverse proxy for incoming traffic, with a sample rule setting the XFF header properly +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts -A note on resiliency - this is an example of a none zonal deployment. Resiliency is maintained by using fault domains (Scale Set's -default mechanism). +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. +- This is an example of a non-zonal deployment. Resiliency is maintained by using fault domains (Scale Set's default mechanism). ### Auto Scaling VM-Series @@ -86,7 +92,7 @@ firewalls, and are automatically registered to Azure Load Balancers. A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -117,14 +123,14 @@ requirements: look at the `TODO` markers). If you already have a configured Panorama (with at least minimum configuration described above) you might want to also adjust the `bootstrap_options` for each scale set ([inbound](./example.tfvars#L205) and [obew](./example.tfvars#L249) separately). -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -215,6 +221,7 @@ Name | Type | Description [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -226,6 +233,10 @@ Name | Description `passwords` | Initial firewall administrative passwords for all deployed Scale Sets. `metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. `lb_frontend_ips` | IP Addresses of the load balancers. +`test_vms_usernames` | Initial administrative username to use for test VMs. +`test_vms_passwords` | Initial administrative password to use for test VMs. +`test_vms_ips` | IP Addresses of the test VMs. +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -250,6 +261,7 @@ Name | Version | Source | Description `appgw` | - | ../../modules/appgw | `ngfw_metrics` | - | ../../modules/ngfw_metrics | `vmss` | - | ../../modules/vmss | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -362,6 +374,7 @@ map(object({ + ### Optional Inputs @@ -969,6 +982,232 @@ map(object({ ``` +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + + +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + + Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 3a4b7f49..f60feaae 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -128,10 +128,8 @@ natgws = { load_balancers = { "public" = { - name = "public-lb" - load_balancer = { - zones = null - } + name = "public-lb" + zones = null nsg_auto_rules_settings = { nsg_vnet_key = "transit" nsg_key = "public" @@ -153,10 +151,8 @@ load_balancers = { } } "private" = { - name = "private-lb" - load_balancer = { - zones = null - } + name = "private-lb" + zones = null vnet_key = "transit" frontend_ips = { "ha-ports" = { @@ -247,3 +243,140 @@ scale_sets = { ] } } + +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app1" = { + name = "app1-nsg" + rules = { + web_inbound = { + name = "app1-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.0.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app1-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] + } + } + } + } + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" + } + } + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } + } + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app2" = { + name = "app2-nsg" + rules = { + web_inbound = { + name = "app2-web-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.100.1.0/25" + destination_port_ranges = ["80", "443"] + } + } + } + } + route_tables = { + nva = { + name = "app2-rt" + routes = { + "toNVA" = { + name = "toNVA-udr" + address_prefix = "0.0.0.0/0" + next_hop_type = "VirtualAppliance" + next_hop_ip_address = "10.0.0.30" + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.1.0/26"] + network_security_group_key = "app2" + route_table_key = "nva" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.1.64/26"] + } + } + } + } + spoke_vms = { + "app2_vm" = { + name = "app2-vm" + vnet_key = "app2" + subnet_key = "vms" + } + } + bastions = { + "app2_bastion" = { + name = "app2-bastion" + vnet_key = "app2" + subnet_key = "bastion" + } + } + } +} diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 2ebc6bfc..c61aedae 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -247,3 +247,55 @@ module "vmss" { tags = var.tags depends_on = [module.vnet, module.load_balancer, module.appgw] } + +# Create test infrastructure + +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} + +module "test_infrastructure" { + source = "../../modules/test_infrastructure" + + for_each = var.test_infrastructure + + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } + + tags = var.tags + depends_on = [module.vnet] +} diff --git a/examples/dedicated_vmseries_and_autoscale/outputs.tf b/examples/dedicated_vmseries_and_autoscale/outputs.tf index eb6c8a5e..ecd90478 100644 --- a/examples/dedicated_vmseries_and_autoscale/outputs.tf +++ b/examples/dedicated_vmseries_and_autoscale/outputs.tf @@ -19,3 +19,30 @@ output "lb_frontend_ips" { description = "IP Addresses of the load balancers." value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null } + +output "test_vms_usernames" { + description = "Initial administrative username to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.username + } : null +} + +output "test_vms_passwords" { + description = "Initial administrative password to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.password + } : null + sensitive = true +} + +output "test_vms_ips" { + description = "IP Addresses of the test VMs." + value = length(var.test_infrastructure) > 0 ? { for k, v in module.test_infrastructure : k => v.vm_private_ips } : null +} + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 71e3692c..2dd056cb 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -639,3 +639,224 @@ variable "scale_sets" { })), []) })) } + +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { + description = <<-EOF + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + EOF + default = {} + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +} diff --git a/examples/gwlb_with_vmseries/.header.md b/examples/gwlb_with_vmseries/.header.md index c69ec1d9..ce21c24a 100644 --- a/examples/gwlb_with_vmseries/.header.md +++ b/examples/gwlb_with_vmseries/.header.md @@ -1,9 +1,53 @@ +--- +short_title: GWLB Firewall Option +type: example +show_in_hub: false +--- # VM-Series Azure Gateway Load Balancer example The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing Azure Gateway Load Balancer in service chain model as described in the following [document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). +## Topology + +This reference architecture consists of: + +- a VNET containing: + - 2 subnets dedicated to the firewalls: management and data + - Route Tables and Network Security Groups +- 1 Gateway Load Balancer: + - bound to Standard Load Balancers in front of application VMs, tunneling all traffic through VM-Series in its backend +- 2 firewalls: + - deployed in different zones + - with 3 network interfaces: management, data + - with public IP addresses assigned to management interface +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Public Standard Load Balancers, in front of the Spoke VMs + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, + see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)). + +**NOTE!** +- after the deployment the firewalls remain not configured and not licensed +- this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain + `bootstrap_options` properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. + It's main purpose is to introduce the Terraform modules. + ## Usage ### Deployment Steps @@ -13,14 +57,14 @@ Azure Gateway Load Balancer in service chain model as described in the following * Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) for details). -* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* _(optional)_ Authenticate to AzureRM, switch to the Subscription of your choice if necessary. * Initialize the Terraform module: ```bash terraform init ``` -* (optional) Plan you infrastructure to see what will be actually deployed: +* _(optional)_ Plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 69d734df..4d26a721 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -1,10 +1,54 @@ <!-- BEGIN_TF_DOCS --> +--- +short\_title: GWLB Firewall Option +type: example +show\_in\_hub: false +--- # VM-Series Azure Gateway Load Balancer example The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing Azure Gateway Load Balancer in service chain model as described in the following [document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). +## Topology + +This reference architecture consists of: + +- a VNET containing: + - 2 subnets dedicated to the firewalls: management and data + - Route Tables and Network Security Groups +- 1 Gateway Load Balancer: + - bound to Standard Load Balancers in front of application VMs, tunneling all traffic through VM-Series in its backend +- 2 firewalls: + - deployed in different zones + - with 3 network interfaces: management, data + - with public IP addresses assigned to management interface +- _(optional)_ test workloads with accompanying infrastructure: + - 2 Spoke VNETs with Route Tables and Network Security Groups + - 2 Public Standard Load Balancers, in front of the Spoke VMs + - 2 Spoke VMs serving as WordPress-based web servers + - 2 Azure Bastion managed jump hosts + +**NOTE!** +- In order to deploy the architecture without test workloads described above, empty the `test_infrastructure` map in + `example.tfvars` file. + +## Prerequisites + +A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: + +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, + see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) +- if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first + ([see this note](../../modules/vmseries/README.md#accept-azure-marketplace-terms)). + +**NOTE!** +- after the deployment the firewalls remain not configured and not licensed +- this example contains some **files** that **can contain sensitive data**, namely the `TFVARS` file can contain + `bootstrap_options` properties in `var.vmseries` definition. Keep in mind that **this code** is **only an example**. + It's main purpose is to introduce the Terraform modules. + ## Usage ### Deployment Steps @@ -14,14 +58,14 @@ Azure Gateway Load Balancer in service chain model as described in the following * Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this [documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) for details). -* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* _(optional)_ Authenticate to AzureRM, switch to the Subscription of your choice if necessary. * Initialize the Terraform module: ```bash terraform init ``` -* (optional) Plan you infrastructure to see what will be actually deployed: +* _(optional)_ Plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -94,13 +138,12 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. -[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`gateway_load_balancers`](#gateway_load_balancers) | `map` | A map with Gateway Load Balancer (GWLB) definitions. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. -[`appvms`](#appvms) | `any` | Configuration for sample application VMs. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -111,9 +154,9 @@ Name | Description `usernames` | Initial administrative username to use for VM-Series. `passwords` | Initial administrative password to use for VM-Series. `metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. -`lb_frontend_ips` | IP Addresses of the load balancers. `vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. `bootstrap_storage_urls` | +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -134,12 +177,11 @@ Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | -`load_balancer` | - | ../../modules/loadbalancer | `gwlb` | - | ../../modules/gwlb | `ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | -`appvm` | - | ../../modules/virtual_machine | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -255,7 +297,6 @@ map(object({ - ### Optional Inputs @@ -308,106 +349,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### load_balancers - -A map containing configuration for all (both 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. -- `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`. -- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP - configurations. -- `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. -- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load - balancing rules, please refer to - [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 refer to - [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the - `var.vnets` map. - -- `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, another property has been introduced instead: - - - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. - - -Type: - -```hcl -map(object({ - name = string - vnet_key = optional(string) - zones = optional(list(string)) - backend_name = optional(string, "vmseries_backend") - 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 - subnet_key = optional(string) - public_ip_name = optional(string) - create_public_ip = optional(bool, false) - public_ip_resource_group_name = 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) - })), {}) - })), {}) - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - #### gateway_load_balancers A map with Gateway Load Balancer (GWLB) definitions. @@ -809,12 +750,227 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> -#### appvms +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. -Configuration for sample application VMs. + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). -Type: any +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + Default value: `map[]` diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index aca2a00b..dc78f00c 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -73,78 +73,10 @@ vnets = { } } } - "app1" = { - name = "app1" - address_space = ["10.0.2.0/25"] - network_security_groups = { - "application_inbound" = { - name = "application-inbound-nsg" - rules = { - app_inbound = { - name = "application-allow-inbound" - priority = 100 - direction = "Inbound" - access = "Allow" - protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances - source_port_range = "*" - destination_address_prefix = "*" - destination_port_ranges = ["22", "80", "443"] - } - } - } - } - subnets = { - "app1" = { - name = "app1-snet" - address_prefixes = ["10.0.2.0/28"] - network_security_group_key = "application_inbound" - } - } - } } # LOAD BALANCING -load_balancers = { - "app1" = { - name = "app1-lb" - nsg_auto_rules_settings = { - nsg_vnet_key = "app1" - nsg_key = "application_inbound" - source_ips = ["0.0.0.0/0"] - } - frontend_ips = { - "app1" = { - name = "app1" - public_ip_name = "public-lb-app1-pip" - create_public_ip = true - gwlb_key = "gwlb" - in_rules = { - "balanceHttp" = { - name = "HTTP" - protocol = "Tcp" - port = 80 - floating_ip = false - } - "balanceHttps" = { - name = "HTTPS" - protocol = "Tcp" - port = 443 - floating_ip = false - } - } - out_rules = { - outbound = { - name = "tcp-outbound" - protocol = "Tcp" - } - } - } - } - } -} - gateway_load_balancers = { gwlb = { name = "vmseries-gwlb" @@ -264,21 +196,161 @@ vmseries = { # TEST INFRASTRUCTURE -appvms = { - app1vm01 = { - name = "app1-vm01" - avzone = "3" - vnet_key = "app1" - subnet_key = "app1" - load_balancer_key = "app1" - username = "appadmin" - custom_data = <<SCRIPT -#!/bin/sh -sudo apt-get update -sudo apt-get install -y nginx -sudo systemctl start nginx -sudo systemctl enable nginx -echo "Backend VM is $(hostname)" | sudo tee /var/www/html/index.html -SCRIPT +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app1" = { + name = "app1-nsg" + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] + } + } + } + } + load_balancers = { + "app1" = { + name = "app1-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "app1" + nsg_key = "app1" + source_ips = ["0.0.0.0/0"] + } + frontend_ips = { + "app1" = { + name = "app1-frontend" + public_ip_name = "public-lb-app1-frontend-pip" + create_public_ip = true + gwlb_key = "gwlb" + in_rules = { + "balanceHttp" = { + name = "HTTP" + protocol = "Tcp" + port = 80 + floating_ip = false + } + "balanceHttps" = { + name = "HTTPS" + protocol = "Tcp" + port = 443 + floating_ip = false + } + } + out_rules = { + outbound = { + name = "tcp-outbound" + protocol = "Tcp" + } + } + } + } + } + } + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" + load_balancer_key = "app1" + } + } + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } + } + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "example-transit" + network_security_groups = { + "app2" = { + name = "app2-nsg" + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.1.0/26"] + network_security_group_key = "app2" + } + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.1.64/26"] + } + } + } + } + load_balancers = { + "app2" = { + name = "app2-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "app2" + nsg_key = "app2" + source_ips = ["0.0.0.0/0"] + } + frontend_ips = { + "app2" = { + name = "app2-frontend" + public_ip_name = "public-lb-app2-frontend-pip" + create_public_ip = true + gwlb_key = "gwlb" + in_rules = { + "balanceHttp" = { + name = "HTTP" + protocol = "Tcp" + port = 80 + floating_ip = false + } + "balanceHttps" = { + name = "HTTPS" + protocol = "Tcp" + port = 443 + floating_ip = false + } + } + out_rules = { + outbound = { + name = "tcp-outbound" + protocol = "Tcp" + } + } + } + } + } + } + spoke_vms = { + "app2_vm" = { + name = "app2-vm" + vnet_key = "app2" + subnet_key = "vms" + load_balancer_key = "app2" + } + } + bastions = { + "app2_bastion" = { + name = "app2-bastion" + vnet_key = "app2" + subnet_key = "bastion" + } + } } } diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 5b1e1150..609b22e6 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -2,9 +2,9 @@ # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { - count = anytrue([ - for _, v in var.vmseries : v.authentication.password == null - ]) ? 1 : 0 + count = anytrue([for _, v in var.vmseries : v.authentication.password == null]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 @@ -75,54 +75,6 @@ module "vnet" { tags = var.tags } -# Create Load Balancers, both internal and external - -module "load_balancer" { - source = "../../modules/loadbalancer" - - for_each = var.load_balancers - - name = "${var.name_prefix}${each.value.name}" - region = var.region - resource_group_name = local.resource_group.name - zones = each.value.zones - backend_name = each.value.backend_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 - ) - - 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) - gwlb_fip_id = try(module.gwlb[v.gwlb_key].frontend_ip_config_id, null) - } - ) - } - - tags = var.tags - depends_on = [module.vnet] -} - # Create Gateway Load Balancers module "gwlb" { @@ -308,52 +260,68 @@ module "vmseries" { public_ip_resource_group_name = v.public_ip_resource_group_name private_ip_address = v.private_ip_address attach_to_lb_backend_pool = v.load_balancer_key != null || v.gwlb_key != null - lb_backend_pool_id = try( - module.load_balancer[v.load_balancer_key].backend_pool_id, - try( - module.gwlb[v.gwlb_key].backend_pool_ids[v.gwlb_backend_key], - null - ) - ) + lb_backend_pool_id = try(module.gwlb[v.gwlb_key].backend_pool_ids[v.gwlb_backend_key], null) }] tags = var.tags depends_on = [ module.vnet, azurerm_availability_set.this, - module.load_balancer, module.bootstrap, ] } # Create test infrastructure -module "appvm" { - for_each = var.appvms - source = "../../modules/virtual_machine" - - name = "${var.name_prefix}${each.value.name}" - region = var.region - resource_group_name = local.resource_group.name - avzone = each.value.avzone +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} - interfaces = [ - { - name = "${var.name_prefix}${each.value.name}" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - enable_backend_pool = true - lb_backend_pool_id = module.load_balancer[each.value.load_balancer_key].backend_pool_id - }, - ] +module "test_infrastructure" { + source = "../../modules/test_infrastructure" - username = try(each.value.username, null) - password = try(random_password.this[0].result, null) - ssh_keys = try(each.value.ssh_keys, []) - custom_data = try(each.value.custom_data, null) + for_each = var.test_infrastructure - vm_size = try(each.value.vm_size, "Standard_B1ls") - managed_disk_type = try(each.value.disk_type, "Standard_LRS") - accelerated_networking = try(each.value.accelerated_networking, false) + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + frontend_ips = { for kv, vv in v.frontend_ips : kv => merge(vv, { + gwlb_fip_id = try(module.gwlb[vv.gwlb_key].frontend_ip_config_id, null) + }) } + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } - tags = var.tags + tags = var.tags + depends_on = [module.vnet] } diff --git a/examples/gwlb_with_vmseries/outputs.tf b/examples/gwlb_with_vmseries/outputs.tf index 368e85a2..f448e723 100644 --- a/examples/gwlb_with_vmseries/outputs.tf +++ b/examples/gwlb_with_vmseries/outputs.tf @@ -15,11 +15,6 @@ output "metrics_instrumentation_keys" { sensitive = true } -output "lb_frontend_ips" { - description = "IP Addresses of the load balancers." - value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null -} - output "vmseries_mgmt_ips" { description = "IP addresses for the VM-Series management interface." value = { for k, v in module.vmseries : k => v.mgmt_ip_address } @@ -29,3 +24,10 @@ output "bootstrap_storage_urls" { value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index 8504485d..dde9ac6b 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -116,99 +116,6 @@ variable "vnets" { # LOAD BALANCING -variable "load_balancers" { - description = <<-EOF - A map containing configuration for all (both 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. - - `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`. - - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP - configurations. - - `backend_name` - (`string`, optional, defaults to "vmseries_backend") a name of the backend pool to create. - - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load - balancing rules, please refer to - [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 refer to - [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_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`. - - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the - `var.vnets` map. - - - `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, another property has been introduced instead: - - - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. - EOF - default = {} - nullable = false - type = map(object({ - name = string - vnet_key = optional(string) - zones = optional(list(string)) - backend_name = optional(string, "vmseries_backend") - 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 - subnet_key = optional(string) - public_ip_name = optional(string) - create_public_ip = optional(bool, false) - public_ip_resource_group_name = 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 "gateway_load_balancers" { description = <<-EOF A map with Gateway Load Balancer (GWLB) definitions. @@ -598,10 +505,221 @@ variable "vmseries" { # TEST INFRASTRUCTURE -variable "appvms" { +variable "test_infrastructure" { description = <<-EOF - Configuration for sample application VMs. + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). EOF default = {} - type = any + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) } diff --git a/examples/standalone_panorama/.header.md b/examples/standalone_panorama/.header.md index 534dd550..d7ecc1a5 100644 --- a/examples/standalone_panorama/.header.md +++ b/examples/standalone_panorama/.header.md @@ -29,7 +29,7 @@ This is a non zonal deployment. The deployed infrastructure consists of: A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto Networks Panorama images in a subscription it might be necessary to accept the license first @@ -47,14 +47,14 @@ A list of requirements might vary depending on the platform used to deploy the i - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 07ed7a63..30b9efb9 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -30,7 +30,7 @@ This is a non zonal deployment. The deployed infrastructure consists of: A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto Networks Panorama images in a subscription it might be necessary to accept the license first @@ -48,14 +48,14 @@ A list of requirements might vary depending on the platform used to deploy the i - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/standalone_vmseries/.header.md b/examples/standalone_vmseries/.header.md index c625483b..063ba2ba 100644 --- a/examples/standalone_vmseries/.header.md +++ b/examples/standalone_vmseries/.header.md @@ -1,4 +1,6 @@ --- +short_title: Standalone VM-Series Deployment +type: example show_in_hub: false --- # Palo Alto Networks Next Generation deployment example @@ -25,7 +27,7 @@ This is a non zonal deployment. The deployed infrastructure consists of: A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -38,14 +40,14 @@ Steps to deploy the infrastructure are as following: - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 9b182053..2e0b379c 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -1,5 +1,7 @@ <!-- BEGIN_TF_DOCS --> --- +short\_title: Standalone VM-Series Deployment +type: example show\_in\_hub: false --- # Palo Alto Networks Next Generation deployment example @@ -26,7 +28,7 @@ This is a non zonal deployment. The deployed infrastructure consists of: A list of requirements might vary depending on the platform used to deploy the infrastructure but a minimum one includes: -- (in case of non cloud shell deployment) credentials and (optionally) tools required to authenticate against Azure Cloud, see +- _(in case of non cloud shell deployment)_ credentials and (optionally) tools required to authenticate against Azure Cloud, see [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) - [supported](#requirements) version of [`Terraform`](<https://developer.hashicorp.com/terraform/downloads>) - if you have not run Palo Alto NGFW images in a subscription it might be necessary to accept the license first @@ -39,14 +41,14 @@ Steps to deploy the infrastructure are as following: - checkout the code locally (if you haven't done so yet) - copy the [`example.tfvars`](./example.tfvars) file, rename it to `terraform.tfvars` and adjust it to your needs (take a closer look at the `TODO` markers) -- (optional) authenticate to AzureRM, switch to the Subscription of your choice if necessary +- _(optional)_ authenticate to AzureRM, switch to the Subscription of your choice if necessary - initialize the Terraform module: ```bash terraform init ``` -- (optional) plan you infrastructure to see what will be actually deployed: +- _(optional)_ plan you infrastructure to see what will be actually deployed: ```bash terraform plan @@ -134,6 +136,7 @@ Name | Type | Description [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. [`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. @@ -148,6 +151,7 @@ Name | Description `lb_frontend_ips` | IP Addresses of the load balancers. `vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. `bootstrap_storage_urls` | +`app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate @@ -174,6 +178,7 @@ Name | Version | Source | Description `ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | +`test_infrastructure` | - | ../../modules/test_infrastructure | Resources used in this module: @@ -290,6 +295,7 @@ map(object({ + ### Optional Inputs @@ -1024,6 +1030,232 @@ map(object({ ``` +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + +#### test_infrastructure + +A map defining test infrastructure including test VMs and Azure Bastion hosts. + +For details and defaults for available options please refer to the +[`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + +Following properties are supported: + +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. +- `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. +- `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + +- `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. +- `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + +- `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + + +Type: + +```hcl +map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +``` + + Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index 373ffaf8..c3038507 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -390,3 +390,55 @@ module "vmseries" { module.bootstrap, ] } + +# Create test infrastructure + +locals { + test_vm_authentication = { + for k, v in var.test_infrastructure : k => + merge( + v.authentication, + { + password = coalesce(v.authentication.password, try(random_password.this[1].result, null)) + } + ) + } +} + +module "test_infrastructure" { + source = "../../modules/test_infrastructure" + + for_each = var.test_infrastructure + + resource_group_name = try( + "${var.name_prefix}${each.value.resource_group_name}", "${local.resource_group.name}-testenv" + ) + region = var.region + vnets = { for k, v in each.value.vnets : k => merge(v, { + name = "${var.name_prefix}${v.name}" + hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) + network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + route_tables = { for kv, vv in v.route_tables : kv => merge(vv, { + name = "${var.name_prefix}${vv.name}" }) + } + }) } + load_balancers = { for k, v in each.value.load_balancers : k => merge(v, { + name = "${var.name_prefix}${v.name}" + backend_name = coalesce(v.backend_name, "${v.name}-backend") + }) } + authentication = local.test_vm_authentication[each.key] + spoke_vms = { for k, v in each.value.spoke_vms : k => merge(v, { + name = "${var.name_prefix}${v.name}" + interface_name = "${var.name_prefix}${coalesce(v.interface_name, "${v.name}-nic")}" + disk_name = "${var.name_prefix}${coalesce(v.disk_name, "${v.name}-osdisk")}" + }) } + bastions = { for k, v in each.value.bastions : k => merge(v, { + name = "${var.name_prefix}${v.name}" + public_ip_name = "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" + }) } + + tags = var.tags + depends_on = [module.vnet] +} diff --git a/examples/standalone_vmseries/outputs.tf b/examples/standalone_vmseries/outputs.tf index f946a80b..887f6e29 100644 --- a/examples/standalone_vmseries/outputs.tf +++ b/examples/standalone_vmseries/outputs.tf @@ -37,3 +37,10 @@ output "bootstrap_storage_urls" { value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null sensitive = true } + +output "app_lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { + for k, v in module.test_infrastructure : k => v.frontend_ip_configs + } : null +} \ No newline at end of file diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index b81bad46..08812d7d 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -774,3 +774,224 @@ variable "vmseries" { EOF } } + +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { + description = <<-EOF + A map defining test infrastructure including test VMs and Azure Bastion hosts. + + For details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. + + Following properties are supported: + + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + set to `false`, an existing Resource Group is sourced. + - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. + - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic + properties are as follows: + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. + - `create_subnets` - (`bool`, optional, 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). + + For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). + + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. + The most basic properties are as follows: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, optional) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [loadbalancer 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 refer to + [loadbalancer 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_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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [loadbalancer module documentation](../../modules/loadbalancer/README.md#frontend_ips) for + available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#load_balancers). + + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VMs. + - `spoke_vms` - (`map`, required) a map defining test VMs. The most basic properties are as follows: + + - `name` - (`string`, required) a name of the VM. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key describing a Load Balancer defined in `load_balancers` property. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#test_vms). + + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as + follows: + + - `name` - (`string`, required) an Azure Bastion name. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to a Bastion deployment. + + For all properties and their default values see + [module's documentation](../../modules/test_infrastructure/README.md#bastions). + EOF + default = {} + nullable = false + type = map(object({ + create_resource_group = optional(bool, true) + resource_group_name = optional(string) + vnets = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = string + network_security_groups = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + 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 + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) + load_balancers = optional(map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + })), {}) + })), {}) + })), {}) + authentication = optional(object({ + username = optional(string, "bitnami") + password = optional(string) + }), {}) + spoke_vms = map(object({ + name = string + interface_name = optional(string) + disk_name = optional(string) + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string) + image = optional(object({ + publisher = optional(string) + offer = optional(string) + sku = optional(string) + version = optional(string) + enable_marketplace_plan = optional(bool) + }), {}) + custom_data = optional(string) + })) + bastions = map(object({ + name = string + public_ip_name = optional(string) + vnet_key = string + subnet_key = string + })) + })) +} diff --git a/examples/test_infrastructure/.header.md b/examples/test_infrastructure/.header.md deleted file mode 100644 index 0e66695b..00000000 --- a/examples/test_infrastructure/.header.md +++ /dev/null @@ -1,16 +0,0 @@ -# Test Infrastructure code - -Terraform code to deploy a test infrastructure consisting of: - -* two VNETs that can be peered with the transit VNET deployed in any of the examples, each contains: - * a Linux-based VM running NGINX server to mock a web application - * an Azure Bastion (enables SSH access to the VM) - * UDRs forcing the traffic to flow through the NVA deployed by any of NGFW examples. - -## Usage - -To use this code, please deploy one of the examples first. Then copy the [`examples.tfvars`](./example.tfvars) to `terraform.tfvars` and edit it to your needs. - -Please correct the values marked with `TODO` markers at minimum. - -## Reference diff --git a/examples/test_infrastructure/README.md b/examples/test_infrastructure/README.md deleted file mode 100644 index 9a8f2433..00000000 --- a/examples/test_infrastructure/README.md +++ /dev/null @@ -1,345 +0,0 @@ -<!-- BEGIN_TF_DOCS --> -# Test Infrastructure code - -Terraform code to deploy a test infrastructure consisting of: - -* two VNETs that can be peered with the transit VNET deployed in any of the examples, each contains: - * a Linux-based VM running NGINX server to mock a web application - * an Azure Bastion (enables SSH access to the VM) - * UDRs forcing the traffic to flow through the NVA deployed by any of NGFW examples. - -## Usage - -To use this code, please deploy one of the examples first. Then copy the [`examples.tfvars`](./example.tfvars) to `terraform.tfvars` and edit it to your needs. - -Please correct the values marked with `TODO` markers at minimum. - -## Reference - -## Module's Required Inputs - -Name | Type | Description ---- | --- | --- -[`region`](#region) | `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. -[`hub_resource_group_name`](#hub_resource_group_name) | `string` | Name of the Resource Group hosting the hub/transit infrastructure. -[`hub_vnet_name`](#hub_vnet_name) | `string` | Name of the hub/transit VNET. -[`vm_size`](#vm_size) | `string` | Azure test VM size. -[`username`](#username) | `string` | Name of the VM admin account. -[`password`](#password) | `string` | A password for the admin account. -[`test_vms`](#test_vms) | `map` | A map defining test VMs. -[`bastions`](#bastions) | `map` | A map containing Azure Bastion definitions. - - - -## Module's Outputs - -Name | Description ---- | --- -`username` | Test VMs admin account. -`password` | Password for the admin user. -`vm_private_ips` | A map of private IPs assigned to test VMs. - -## Module's Nameplate - - -Requirements needed by this module: - -- `terraform`, version: >= 1.5, < 2.0 - - -Providers used in this module: - -- `random` -- `azurerm` - - -Modules used in this module: -Name | Version | Source | Description ---- | --- | --- | --- -`vnet` | - | ../../modules/vnet | Manage the network required for the topology. -`vnet_peering` | - | ../../modules/vnet_peering | - - -Resources used in this module: - -- `bastion_host` (managed) -- `linux_virtual_machine` (managed) -- `network_interface` (managed) -- `public_ip` (managed) -- `resource_group` (managed) -- `password` (managed) -- `resource_group` (data) - -## Inputs/Outpus details - -### Required Inputs - - - -#### region - -The Azure region to use. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - - - -#### resource_group_name - -Name of the Resource Group. - -Type: string - -<sup>[back to list](#modules-required-inputs)</sup> - -#### 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 `true`) 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`, optional, 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 - resource_group_name = optional(string) - create_virtual_network = optional(bool, true) - address_space = optional(list(string)) - hub_resource_group_name = optional(string) - hub_vnet_name = optional(string) - network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) - 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 - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_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) - })), {}) - })) -``` - - -<sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - - - -### Optional Inputs - - -#### tags - -Map of tags to assign to the created resources. - -Type: map(string) - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -#### 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: -``` -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: `` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### 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` - -<sup>[back to list](#modules-optional-inputs)</sup> - - - -#### hub_resource_group_name - -Name of the Resource Group hosting the hub/transit infrastructure. This value is required to create peering between the spoke and the hub VNET. - -Type: string - -Default value: `&{}` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### hub_vnet_name - -Name of the hub/transit VNET. This value is required to create peering between the spoke and the hub VNET. - -Type: string - -Default value: `&{}` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### vm_size - -Azure test VM size. - -Type: string - -Default value: `Standard_D1_v2` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### username - -Name of the VM admin account. - -Type: string - -Default value: `panadmin` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### password - -A password for the admin account. - -Type: string - -Default value: `&{}` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### test_vms - -A map defining test VMs. - -Values contain the following elements: - -- `name`: a name of the VM -- `vnet_key`: a key describing a VNET defined in `var.vnets` -- `subnet_key`: a key describing a subnet found in a VNET definition - - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - -#### bastions - -A map containing Azure Bastion definitions. - -This map follows resource definition convention, following values are available: -- `name`: Bastion name -- `vnet_key`: a key describing a VNET defined in `var.vnets`. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). -- `subnet_key`: a key pointing to a subnet dedicated to a Bastion deployment (the name should be `AzureBastionSubnet`.) - - - -Type: - -```hcl -map(object({ - name = string - vnet_key = string - subnet_key = string - })) -``` - - -Default value: `map[]` - -<sup>[back to list](#modules-optional-inputs)</sup> - - -<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/test_infrastructure/example.tfvars b/examples/test_infrastructure/example.tfvars deleted file mode 100644 index a95a5017..00000000 --- a/examples/test_infrastructure/example.tfvars +++ /dev/null @@ -1,102 +0,0 @@ -# --- GENERAL --- # -region = "North Europe" -resource_group_name = "vmseries-test-infra" -name_prefix = "example-" -tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" -} - - -# --- VNET PART --- # -vnets = { - "spoke_east" = { - name = "spoke-east" - address_space = ["10.100.0.0/25"] - # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-common" # FIXME: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # FIXME: replace with the name of the transit VNET - route_tables = { - nva = { - name = "east2NVA" - routes = { - "2NVA" = { - name = "2NVA-udr" - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_ip_address = "10.0.0.30" # FIXME: this by default matches the private IP of the private Load Balancer deployed in any of the examples; adjust if needed - } - } - } - } - subnets = { - "vms" = { - name = "vms" - address_prefixes = ["10.100.0.0/26"] - route_table_key = "nva" - } - "bastion" = { - name = "AzureBastionSubnet" - address_prefixes = ["10.100.0.64/26"] - } - } - } - "spoke_west" = { - name = "spoke-west" - address_space = ["10.100.1.0/25"] - # # Uncomment the lines below to enable peering between spokes created in this module and an existing transit VNET - # hub_resource_group_name = "example-transit-vnet-common" # FIXME: replace with the name of transit VNET's Resource Group Name - # hub_vnet_name = "example-transit" # FIXME: replace with the name of the transit VNET - route_tables = { - nva = { - name = "west2NVA" - routes = { - "2NVA" = { - name = "2NVA-udr" - address_prefix = "0.0.0.0/0" - next_hop_type = "VirtualAppliance" - next_hop_ip_address = "10.0.0.30" # FIXME: replace with IP address of the private Load Balancer in the transit VNET - } - } - } - } - subnets = { - "vms" = { - name = "vms" - address_prefixes = ["10.100.1.0/26"] - route_table_key = "nva" - } - "bastion" = { - name = "AzureBastionSubnet" - address_prefixes = ["10.100.1.64/26"] - } - } - } -} - - -test_vms = { - "east_vm" = { - name = "east-vm" - vnet_key = "spoke_east" - subnet_key = "vms" - } - "west_vm" = { - name = "west-vm" - vnet_key = "spoke_west" - subnet_key = "vms" - } -} - -bastions = { - "bastion_east" = { - name = "east-bastion" - vnet_key = "spoke_east" - subnet_key = "bastion" - } - "bastion_west" = { - name = "west-bastion" - vnet_key = "spoke_west" - subnet_key = "bastion" - } -} \ No newline at end of file diff --git a/examples/test_infrastructure/main.tf b/examples/test_infrastructure/main.tf deleted file mode 100644 index 389e0ec1..00000000 --- a/examples/test_infrastructure/main.tf +++ /dev/null @@ -1,154 +0,0 @@ -# Generate a random password. -# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password -resource "random_password" "this" { - count = var.password == null ? 1 : 0 - - length = 16 - min_lower = 16 - 4 - min_numeric = 1 - min_special = 1 - min_upper = 1 - override_special = "_%@" -} - -locals { - password = coalesce(var.password, try(random_password.this[0].result, null)) -} - -# Create or source the Resource Group. -# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group -resource "azurerm_resource_group" "this" { - count = var.create_resource_group ? 1 : 0 - name = "${var.name_prefix}${var.resource_group_name}" - location = var.region - - tags = var.tags -} - -# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group -data "azurerm_resource_group" "this" { - count = var.create_resource_group ? 0 : 1 - name = var.resource_group_name -} - -locals { - resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] -} - -# Manage the network required for the topology. -module "vnet" { - source = "../../modules/vnet" - - for_each = var.vnets - - name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name - create_virtual_network = each.value.create_virtual_network - resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) - region = var.region - - address_space = each.value.address_space - - create_subnets = each.value.create_subnets - subnets = each.value.subnets - - network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) - } - - tags = var.tags -} - -module "vnet_peering" { - source = "../../modules/vnet_peering" - for_each = { for k, v in var.vnets : k => v if v.hub_vnet_name != null } - - - local_peer_config = { - name = "peer-${each.value.name}-to-${each.value.hub_vnet_name}" - resource_group_name = local.resource_group.name - vnet_name = "${var.name_prefix}${each.value.name}" - } - remote_peer_config = { - name = "peer-${each.value.hub_vnet_name}-to-${each.value.name}" - resource_group_name = try(each.value.hub_resource_group_name, local.resource_group.name) - vnet_name = each.value.hub_vnet_name - } - - depends_on = [module.vnet] -} - -# Create test VM running a web server -resource "azurerm_network_interface" "vm" { - for_each = var.test_vms - - name = "${var.name_prefix}${each.value.name}-nic" - location = var.region - resource_group_name = local.resource_group.name - - ip_configuration { - name = "internal" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - private_ip_address_allocation = "Dynamic" - } -} - -resource "azurerm_linux_virtual_machine" "this" { - for_each = var.test_vms - - # checkov:skip=CKV_AZURE_178:This is a test, non-production VM - # checkov:skip=CKV_AZURE_149:This is a test, non-production VM - - name = "${var.name_prefix}${each.value.name}" - resource_group_name = local.resource_group.name - location = var.region - size = var.vm_size - admin_username = var.username - admin_password = local.password - disable_password_authentication = false - network_interface_ids = [azurerm_network_interface.vm[each.key].id] - allow_extension_operations = false - - os_disk { - caching = "ReadWrite" - storage_account_type = "Standard_LRS" - } - - source_image_reference { - publisher = "bitnami" - offer = "wordpress" - sku = "4-4" - version = "latest" - } - plan { - name = "4-4" - product = "wordpress" - publisher = "bitnami" - } -} - -# Create Bastion host for management - -resource "azurerm_public_ip" "bastion" { - for_each = var.bastions - - name = "${var.name_prefix}${each.value.name}-nic" - location = var.region - resource_group_name = local.resource_group.name - allocation_method = "Static" - sku = "Standard" -} - -resource "azurerm_bastion_host" "this" { - for_each = var.bastions - - name = "${var.name_prefix}${each.value.name}" - location = var.region - resource_group_name = local.resource_group.name - - ip_configuration { - name = "bastion-ip-config" - subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] - public_ip_address_id = azurerm_public_ip.bastion[each.key].id - } -} \ No newline at end of file diff --git a/examples/test_infrastructure/main_test.go b/examples/test_infrastructure/main_test.go deleted file mode 100644 index 1346cf15..00000000 --- a/examples/test_infrastructure/main_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package test_infrastructure - -import ( - "testing" - - "github.com/gruntwork-io/terratest/modules/logger" - "github.com/gruntwork-io/terratest/modules/terraform" - - "github.com/PaloAltoNetworks/terraform-modules-swfw-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/test_infrastructure/outputs.tf b/examples/test_infrastructure/outputs.tf deleted file mode 100644 index afc5c510..00000000 --- a/examples/test_infrastructure/outputs.tf +++ /dev/null @@ -1,15 +0,0 @@ -output "username" { - description = "Test VMs admin account." - value = var.username -} - -output "password" { - description = "Password for the admin user." - value = local.password - sensitive = true -} - -output "vm_private_ips" { - description = "A map of private IPs assigned to test VMs." - value = { for k, v in azurerm_network_interface.vm : k => v.private_ip_address } -} \ No newline at end of file diff --git a/examples/test_infrastructure/variables.tf b/examples/test_infrastructure/variables.tf deleted file mode 100644 index 02b8fed7..00000000 --- a/examples/test_infrastructure/variables.tf +++ /dev/null @@ -1,180 +0,0 @@ -### GENERAL -variable "tags" { - description = "Map of tags to assign to the created resources." - default = {} - type = map(string) -} - -variable "region" { - 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: - ``` - 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 `true`) 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`, optional, 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 - resource_group_name = optional(string) - create_virtual_network = optional(bool, true) - address_space = optional(list(string)) - hub_resource_group_name = optional(string) - hub_vnet_name = optional(string) - network_security_groups = optional(map(object({ - name = string - disable_bgp_route_propagation = optional(bool) - 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 - routes = map(object({ - name = string - address_prefix = string - next_hop_type = string - next_hop_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 "hub_resource_group_name" { - description = "Name of the Resource Group hosting the hub/transit infrastructure. This value is required to create peering between the spoke and the hub VNET." - type = string - default = null -} - -variable "hub_vnet_name" { - description = "Name of the hub/transit VNET. This value is required to create peering between the spoke and the hub VNET." - type = string - default = null -} - -variable "vm_size" { - description = "Azure test VM size." - default = "Standard_D1_v2" - type = string -} - -variable "username" { - description = "Name of the VM admin account." - default = "panadmin" - type = string -} - -variable "password" { - description = "A password for the admin account." - default = null - type = string -} - -variable "test_vms" { - description = <<-EOF - A map defining test VMs. - - Values contain the following elements: - - - `name`: a name of the VM - - `vnet_key`: a key describing a VNET defined in `var.vnets` - - `subnet_key`: a key describing a subnet found in a VNET definition - - EOF - default = {} - type = map(object({ - name = string - vnet_key = string - subnet_key = string - })) -} - -variable "bastions" { - description = <<-EOF - A map containing Azure Bastion definitions. - - This map follows resource definition convention, following values are available: - - `name`: Bastion name - - `vnet_key`: a key describing a VNET defined in `var.vnets`. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). - - `subnet_key`: a key pointing to a subnet dedicated to a Bastion deployment (the name should be `AzureBastionSubnet`.) - - EOF - default = {} - type = map(object({ - name = string - vnet_key = string - subnet_key = string - })) -} \ No newline at end of file diff --git a/examples/test_infrastructure/versions.tf b/examples/test_infrastructure/versions.tf deleted file mode 100644 index 14f2e802..00000000 --- a/examples/test_infrastructure/versions.tf +++ /dev/null @@ -1,15 +0,0 @@ -terraform { - required_version = ">= 1.5, < 2.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - } - random = { - source = "hashicorp/random" - } - } -} - -provider "azurerm" { - features {} -} \ No newline at end of file diff --git a/modules/test_infrastructure/.README.md b/modules/test_infrastructure/.README.md new file mode 100644 index 00000000..aa0c50dc --- /dev/null +++ b/modules/test_infrastructure/.README.md @@ -0,0 +1,414 @@ +<!-- BEGIN_TF_DOCS --> +# Palo Alto Networks Test Infrastructure Module for Azure + +A Terraform module for deploying a Test Infrastructure in Azure cloud, containing peered VNETs with test VMs serving as +WordPress-based web servers, Standard Load Balancers to optionally put them in front of the test workloads and Azure Bastion jump +hosts for secure access to the test VMs. + +## Usage + +For usage please refer to any reference architecture example. + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. +[`region`](#region) | `string` | The name of the Azure region to deploy the resources in. +[`vnets`](#vnets) | `map` | A map defining VNETs. +[`authentication`](#authentication) | `object` | A map defining authentication details for spoke VMs. +[`spoke_vms`](#spoke_vms) | `map` | A map defining spoke VMs for testing. +[`bastions`](#bastions) | `map` | A map containing Azure Bastion definition. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`create_resource_group`](#create_resource_group) | `bool` | When set to `true` it will cause a Resource Group creation. +[`tags`](#tags) | `map` | The map of tags to assign to all created resources. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. + + + +## Module's Outputs + +Name | Description +--- | --- +`vm_private_ips` | A map of private IPs assigned to test VMs. +`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. + + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 +- `azurerm`, version: ~> 3.80 + + +Providers used in this module: + +- `azurerm`, version: ~> 3.80 + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../vnet | https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/vnet +`vnet_peering` | - | ../vnet_peering | https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/vnet_peering +`load_balancer` | - | ../loadbalancer | https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/loadbalancer + + +Resources used in this module: + +- `bastion_host` (managed) +- `linux_virtual_machine` (managed) +- `network_interface` (managed) +- `network_interface_backend_address_pool_association` (managed) +- `public_ip` (managed) +- `resource_group` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### resource_group_name + +The name of the Resource Group to use. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + +#### region + +The name of the Azure region to deploy the resources in. + +Type: string + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET. +- `hub_resource_group_name` - (`string`, optional) name of the Resource Group hosting the hub/transit infrastructure. This + value is necessary to create peering between the spoke and the hub VNET. +- `hub_vnet_name` - (`string`, optional) Name of the hub/transit VNET. This value is required to create peering + between the spoke and the hub VNET. +- `create_subnets` - (`bool`, optional, 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](../vnet/README.md#subnets). +- `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see + [VNET module documentation](../vnet/README.md#network_security_groups). +- `route_tables` - (`map`, optional) map of Route Tables to create, for details see + [VNET module documentation](../vnet/README.md#route_tables). + + +Type: + +```hcl +map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = optional(string) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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) + })), {}) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + +#### authentication + +A map defining authentication details for spoke VMs. + +Following properties are available: +- `username` - (`string`, optional, defaults to `bitnami`) the initial administrative spoke VM username. +- `password` - (`string`, required) the initial administrative spoke VM password. + + +Type: + +```hcl +object({ + username = optional(string, "bitnami") + password = string + }) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### spoke_vms + +A map defining spoke VMs for testing. + +Values contain the following elements: + +- `name` - (`string`, required) a name of the spoke VM. +- `interface_name` - (`string`, required) a name of the spoke VM's network interface. +- `disk_name` - (`string`, required) a name of the OS disk. +- `vnet_key` - (`string`, required) a key describing a VNET defined in `var.vnets`. +- `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. +- `load_balancer_key` - (`string`, optional) a key of a Load Balancer defined in `var.load_balancers` variable, network + interface that has this property defined will be added to the Load Balancer's backend pool. +- `private_ip_address` - (`string`, optional) static private IP to assign to the interface. When skipped Azure will assign one + dynamically. Keep in mind that a dynamic IP is guarantied not to change as long as the VM is running. + Any stop/deallocate/restart operation might cause the IP to change. +- `size` - (`string`, optional, default to `Standard_D1_v2`) a size of the spoke VM. +- `image` - (`map`, optional) a map defining basic spoke VM image configuration. By default, latest Bitnami + WordPress VM is deployed. + - `publisher` - (`string`, optional, defaults to `bitnami`) the Azure Publisher identifier for an image which + should be deployed. + - `offer` - (`string`, optional, defaults to `wordpress`) the Azure Offer identifier corresponding to a + published image. + - `sku` - (`string`, optional, defaults to `4-4`) the Azure SKU identifier corresponding to a published + image and offer. + - `version` - (`string`, optional, defaults to `latest`) the version of the image available on Azure + Marketplace. + - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Marketplace. +- `custom_data` - (`string`, optional) custom data to pass to the spoke VM. This can be used as cloud-init for Linux + systems. + + +Type: + +```hcl +map(object({ + name = string + interface_name = string + disk_name = string + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string, "Standard_D1_v2") + image = object({ + publisher = optional(string, "bitnami") + offer = optional(string, "wordpress") + sku = optional(string, "4-4") + version = optional(string, "latest") + enable_marketplace_plan = optional(bool, true) + }) + custom_data = optional(string) + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + +#### bastions + +A map containing Azure Bastion definition. + +This map follows resource definition convention, following values are available: +- `name` - (`string`, required) an Azure Bastion name. +- `public_ip_name` - (`string`, required) a name of the public IP associated with the Bastion. +- `vnet_key` - (`string`, required) a key describing a VNET defined in `var.vnets`. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). +- `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to the Bastion deployment. + + +Type: + +```hcl +map(object({ + name = string + public_ip_name = string + vnet_key = string + subnet_key = string + })) +``` + + +<sup>[back to list](#modules-required-inputs)</sup> + + + +### 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` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + +#### tags + +The map of tags to assign to all created resources. + +Type: map(string) + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + +#### load_balancers + +A map containing configuration for all (both private and public) Load Balancers. + +This is a brief description of available properties. For a detailed one please refer to +[module documentation](../loadbalancer/README.md). + +Following properties are available: + +- `name` - (`string`, required) a name of the Load Balancer. +- `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`. +- `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. +- `backend_name` - (`string`, required) a name of the backend pool to create. +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [module documentation](../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 refer to + [module documentation](../loadbalancer/README.md#nsg_auto_rules_settings) for available + properties. + + Please note that in this example two additional properties are available: + + - `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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + +- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [module documentation](../loadbalancer/README.md#frontend_ips) for available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + + +Type: + +```hcl +map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + 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) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> + + + + +<!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/test_infrastructure/.header.md b/modules/test_infrastructure/.header.md new file mode 100644 index 00000000..3944f37b --- /dev/null +++ b/modules/test_infrastructure/.header.md @@ -0,0 +1,9 @@ +# Palo Alto Networks Test Infrastructure Module for Azure + +A Terraform module for deploying a Test Infrastructure in Azure cloud, containing peered VNETs with test VMs serving as +WordPress-based web servers, Standard Load Balancers to optionally put them in front of the test workloads and Azure Bastion jump +hosts for secure access to the test VMs. + +## Usage + +For usage please refer to any reference architecture example. \ No newline at end of file diff --git a/modules/test_infrastructure/main.tf b/modules/test_infrastructure/main.tf new file mode 100644 index 00000000..bc2c4727 --- /dev/null +++ b/modules/test_infrastructure/main.tf @@ -0,0 +1,213 @@ +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = var.resource_group_name + location = var.region + + tags = var.tags +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/vnet +module "vnet" { + source = "../vnet" + + for_each = var.vnets + + name = each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = local.resource_group.name + region = var.region + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = each.value.network_security_groups + route_tables = each.value.route_tables + + tags = var.tags +} + +# https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/vnet_peering +module "vnet_peering" { + source = "../vnet_peering" + + for_each = { for k, v in var.vnets : k => v if v.hub_vnet_name != null } + + local_peer_config = { + name = "peer-${each.value.name}-to-${each.value.hub_vnet_name}" + resource_group_name = local.resource_group.name + vnet_name = each.value.name + } + remote_peer_config = { + name = "peer-${each.value.hub_vnet_name}-to-${each.value.name}" + resource_group_name = try(each.value.hub_resource_group_name, local.resource_group.name) + vnet_name = each.value.hub_vnet_name + } + + depends_on = [module.vnet] +} + +# https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/loadbalancer +module "load_balancer" { + source = "../loadbalancer" + + for_each = var.load_balancers + + name = each.value.name + region = var.region + resource_group_name = local.resource_group.name + zones = each.value.zones + backend_name = each.value.backend_name + + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + 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 ? v.public_ip_name : null + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + gwlb_fip_id = try(v.gwlb_fip_id, null) + } + ) + } + + tags = var.tags + depends_on = [module.vnet] +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface +resource "azurerm_network_interface" "vm" { + for_each = var.spoke_vms + + name = each.value.interface_name + location = var.region + resource_group_name = local.resource_group.name + + ip_configuration { + name = "internal" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + private_ip_address_allocation = each.value.private_ip_address != null ? "Static" : "Dynamic" + private_ip_address = each.value.private_ip_address + } +} + +locals { + password = sensitive(var.authentication.password) +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine +resource "azurerm_linux_virtual_machine" "this" { + for_each = var.spoke_vms + + # checkov:skip=CKV_AZURE_178:This is a test, non-production VM + # checkov:skip=CKV_AZURE_149:This is a test, non-production VM + + name = each.value.name + resource_group_name = local.resource_group.name + location = var.region + size = each.value.size + admin_username = var.authentication.username + admin_password = local.password + disable_password_authentication = false + network_interface_ids = [azurerm_network_interface.vm[each.key].id] + allow_extension_operations = false + custom_data = each.value.custom_data + + os_disk { + name = each.value.disk_name + caching = "ReadWrite" + storage_account_type = "Standard_LRS" + } + + source_image_reference { + publisher = each.value.image.publisher + offer = each.value.image.offer + sku = each.value.image.sku + version = each.value.image.version + } + + dynamic "plan" { + for_each = each.value.image.enable_marketplace_plan ? [1] : [] + content { + name = each.value.image.sku + product = each.value.image.offer + publisher = each.value.image.publisher + } + } + + lifecycle { + ignore_changes = [source_image_reference["version"]] + } +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_backend_address_pool_association +resource "azurerm_network_interface_backend_address_pool_association" "this" { + for_each = { for k, v in var.spoke_vms : k => v if v.load_balancer_key != null } + + backend_address_pool_id = module.load_balancer[each.value.load_balancer_key].backend_pool_id + ip_configuration_name = azurerm_network_interface.vm[each.key].ip_configuration[0].name + network_interface_id = azurerm_network_interface.vm[each.key].id + + depends_on = [ + module.load_balancer, + azurerm_network_interface.vm, + azurerm_linux_virtual_machine.this + ] +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip +resource "azurerm_public_ip" "bastion" { + for_each = var.bastions + + name = each.value.public_ip_name + location = var.region + resource_group_name = local.resource_group.name + allocation_method = "Static" + sku = "Standard" +} + +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/bastion_host +resource "azurerm_bastion_host" "this" { + for_each = var.bastions + + name = each.value.name + location = var.region + resource_group_name = local.resource_group.name + + ip_configuration { + name = "bastion-ip-config" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + public_ip_address_id = azurerm_public_ip.bastion[each.key].id + } +} diff --git a/modules/test_infrastructure/main_test.go b/modules/test_infrastructure/main_test.go new file mode 100644 index 00000000..7d753288 --- /dev/null +++ b/modules/test_infrastructure/main_test.go @@ -0,0 +1,11 @@ +package test_infrastructure + +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/test_infrastructure/outputs.tf b/modules/test_infrastructure/outputs.tf new file mode 100644 index 00000000..779e9e0a --- /dev/null +++ b/modules/test_infrastructure/outputs.tf @@ -0,0 +1,12 @@ +output "vm_private_ips" { + description = "A map of private IPs assigned to test VMs." + value = { for k, v in azurerm_network_interface.vm : k => v.private_ip_address } +} + +output "frontend_ip_configs" { + description = <<-EOF + 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. + EOF + value = { for k, v in module.load_balancer : k => v.frontend_ip_configs } +} diff --git a/modules/test_infrastructure/variables.tf b/modules/test_infrastructure/variables.tf new file mode 100644 index 00000000..69fe8343 --- /dev/null +++ b/modules/test_infrastructure/variables.tf @@ -0,0 +1,271 @@ +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 = "The name of the Resource Group to use." + type = string +} + +variable "region" { + description = "The name of the Azure region to deploy the resources in." + type = string +} + +variable "tags" { + description = "The map of tags to assign to all created resources." + default = {} + type = map(string) +} + +variable "vnets" { + description = <<-EOF + A map defining VNETs. + + For detailed documentation on each property refer to [module documentation](../vnet/README.md) + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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`, required when `create_virtual_network = false`) a list of CIDRs for a newly created VNET. + - `hub_resource_group_name` - (`string`, optional) name of the Resource Group hosting the hub/transit infrastructure. This + value is necessary to create peering between the spoke and the hub VNET. + - `hub_vnet_name` - (`string`, optional) Name of the hub/transit VNET. This value is required to create peering + between the spoke and the hub VNET. + - `create_subnets` - (`bool`, optional, 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](../vnet/README.md#subnets). + - `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see + [VNET module documentation](../vnet/README.md#network_security_groups). + - `route_tables` - (`map`, optional) map of Route Tables to create, for details see + [VNET module documentation](../vnet/README.md#route_tables). + EOF + type = map(object({ + name = string + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + hub_resource_group_name = optional(string) + hub_vnet_name = optional(string) + network_security_groups = optional(map(object({ + name = 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 + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_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 (both private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer. + - `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`. + - `zones` - (`list`, optional, defaults to module default) a list of zones for Load Balancer's frontend IP + configurations. + - `backend_name` - (`string`, required) a name of the backend pool to create. + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by load + balancing rules, please refer to + [module documentation](../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 refer to + [module documentation](../loadbalancer/README.md#nsg_auto_rules_settings) for available + properties. + + Please note that in this example two additional properties are available: + + - `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`. + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition in the + `var.vnets` map. + + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules`, please refer to + [module documentation](../loadbalancer/README.md#frontend_ips) for available properties. + + **Note!** \ + In this example the `subnet_id` is not available directly, another property has been introduced instead: + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map. + EOF + default = {} + nullable = false + type = map(object({ + name = string + vnet_key = optional(string) + zones = optional(list(string)) + backend_name = optional(string) + 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 + subnet_key = optional(string) + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = 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) + 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 "authentication" { + description = <<-EOF + A map defining authentication details for spoke VMs. + + Following properties are available: + - `username` - (`string`, optional, defaults to `bitnami`) the initial administrative spoke VM username. + - `password` - (`string`, required) the initial administrative spoke VM password. + EOF + type = object({ + username = optional(string, "bitnami") + password = string + }) +} + +variable "spoke_vms" { + description = <<-EOF + A map defining spoke VMs for testing. + + Values contain the following elements: + + - `name` - (`string`, required) a name of the spoke VM. + - `interface_name` - (`string`, required) a name of the spoke VM's network interface. + - `disk_name` - (`string`, required) a name of the OS disk. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `var.vnets`. + - `subnet_key` - (`string`, required) a key describing a Subnet found in a VNET definition. + - `load_balancer_key` - (`string`, optional) a key of a Load Balancer defined in `var.load_balancers` variable, network + interface that has this property defined will be added to the Load Balancer's backend pool. + - `private_ip_address` - (`string`, optional) static private IP to assign to the interface. When skipped Azure will assign one + dynamically. Keep in mind that a dynamic IP is guarantied not to change as long as the VM is running. + Any stop/deallocate/restart operation might cause the IP to change. + - `size` - (`string`, optional, default to `Standard_D1_v2`) a size of the spoke VM. + - `image` - (`map`, optional) a map defining basic spoke VM image configuration. By default, latest Bitnami + WordPress VM is deployed. + - `publisher` - (`string`, optional, defaults to `bitnami`) the Azure Publisher identifier for an image which + should be deployed. + - `offer` - (`string`, optional, defaults to `wordpress`) the Azure Offer identifier corresponding to a + published image. + - `sku` - (`string`, optional, defaults to `4-4`) the Azure SKU identifier corresponding to a published + image and offer. + - `version` - (`string`, optional, defaults to `latest`) the version of the image available on Azure + Marketplace. + - `enable_marketplace_plan` - (`bool`, optional, defaults to `true`) when set to `true` accepts the license for an offer/plan + on Azure Marketplace. + - `custom_data` - (`string`, optional) custom data to pass to the spoke VM. This can be used as cloud-init for Linux + systems. + EOF + type = map(object({ + name = string + interface_name = string + disk_name = string + vnet_key = string + subnet_key = string + load_balancer_key = optional(string) + private_ip_address = optional(string) + size = optional(string, "Standard_D1_v2") + image = object({ + publisher = optional(string, "bitnami") + offer = optional(string, "wordpress") + sku = optional(string, "4-4") + version = optional(string, "latest") + enable_marketplace_plan = optional(bool, true) + }) + custom_data = optional(string) + })) +} + +variable "bastions" { + description = <<-EOF + A map containing Azure Bastion definition. + + This map follows resource definition convention, following values are available: + - `name` - (`string`, required) an Azure Bastion name. + - `public_ip_name` - (`string`, required) a name of the public IP associated with the Bastion. + - `vnet_key` - (`string`, required) a key describing a VNET defined in `var.vnets`. This VNET should already have an + existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). + - `subnet_key` - (`string`, required) a key pointing to a Subnet dedicated to the Bastion deployment. + EOF + type = map(object({ + name = string + public_ip_name = string + vnet_key = string + subnet_key = string + })) +} diff --git a/modules/test_infrastructure/versions.tf b/modules/test_infrastructure/versions.tf new file mode 100644 index 00000000..9abec711 --- /dev/null +++ b/modules/test_infrastructure/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.80" + } + } +} From cd73452db4234a1f61c5a88e5fb6410df5c4865e Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:13:20 +0200 Subject: [PATCH 28/49] refactor: Bump pre-commit and remediate checkov warnings (#35) --- .github/workflows/help-command.yml | 3 ++ .pre-commit-config.yaml | 6 ++-- examples/common_vmseries/README.md | 3 +- examples/common_vmseries/variables.tf | 3 +- .../common_vmseries_and_autoscale/README.md | 2 +- .../variables.tf | 2 +- examples/dedicated_vmseries/README.md | 3 +- examples/dedicated_vmseries/variables.tf | 3 +- .../README.md | 2 +- .../variables.tf | 2 +- examples/gwlb_with_vmseries/README.md | 3 +- examples/gwlb_with_vmseries/variables.tf | 3 +- examples/standalone_panorama/README.md | 21 +++++++------- examples/standalone_panorama/variables.tf | 21 +++++++------- examples/standalone_vmseries/README.md | 3 +- examples/standalone_vmseries/variables.tf | 3 +- modules/bootstrap/README.md | 4 ++- modules/bootstrap/main.tf | 25 ++++++++++------ modules/bootstrap/variables.tf | 11 +++++++ modules/gwlb/README.md | 1 - modules/name_templater/README.md | 1 - modules/natgw/README.md | 1 - modules/ngfw_metrics/README.md | 1 - modules/panorama/README.md | 29 ++++++++++--------- modules/panorama/main.tf | 10 ++++--- modules/panorama/variables.tf | 28 +++++++++--------- modules/test_infrastructure/main_test.go | 2 +- modules/vmseries/README.md | 5 ++-- modules/vmseries/main.tf | 2 ++ modules/vmseries/variables.tf | 4 +-- modules/vmss/README.md | 6 ++-- modules/vmss/dt_string_converter/README.md | 1 - modules/vmss/variables.tf | 5 ++-- modules/vnet/README.md | 1 - modules/vnet_peering/README.md | 1 - requirements.txt | 2 +- 36 files changed, 126 insertions(+), 97 deletions(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 5baa5656..8c11eaa3 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -1,6 +1,9 @@ name: ChatOPS Help run-name: "Display ChatOPS help (#${{ github.event.inputs.pr-id }}) ${{ github.event.inputs.pr-title }}" +permissions: + contents: read + on: workflow_dispatch: inputs: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca409184..7c1a9075 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,14 +12,14 @@ repos: - --args=--only=terraform_workspace_remote id: terraform_tflint repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.83.0 + rev: v1.88.4 - hooks: - args: - --compact - --quiet - --skip-check - - CKV_AZURE_118,CKV_AZURE_119,CKV_AZURE_120,CKV2_AZURE_10,CKV2_AZURE_12,CKV_AZURE_35,CKV_AZURE_206,CKV_AZURE_93,CKV2_AZURE_1,CKV2_AZURE_18,CKV_AZURE_97,CKV_AZURE_59,CKV_AZURE_190,CKV2_AZURE_33,CKV_AZURE_179,CKV_AZURE_1,CKV_AZURE_49,CKV_AZURE_217,CKV_AZURE_218 + - CKV_GHA_7,CKV_AZURE_1,CKV_AZURE_35,CKV_AZURE_44,CKV_AZURE_49,CKV_AZURE_59,CKV_AZURE_93,CKV_AZURE_97,CKV_AZURE_118,CKV_AZURE_119,CKV_AZURE_120,CKV_AZURE_179,CKV_AZURE_190,CKV_AZURE_206,CKV_AZURE_217,CKV_AZURE_218,CKV2_AZURE_1,CKV2_AZURE_10,CKV2_AZURE_12,CKV2_AZURE_18,CKV2_AZURE_33,CKV2_AZURE_39,CKV2_AZURE_40,CKV2_AZURE_41 id: checkov verbose: true repo: https://github.com/bridgecrewio/checkov.git - rev: 2.4.22 + rev: 3.2.50 diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index cd5d7aec..ae660f4b 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -895,6 +895,7 @@ map(object({ replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -1073,13 +1074,13 @@ map(object({ disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index 08812d7d..b0184dac 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -561,6 +561,7 @@ variable "bootstrap_storages" { replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -732,13 +733,13 @@ variable "vmseries" { disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 34675120..aba250e4 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -924,6 +924,7 @@ map(object({ zones = optional(list(string)) disk_type = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) overprovision = optional(bool) platform_fault_domain_count = optional(number) @@ -932,7 +933,6 @@ map(object({ boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 2dd056cb..1e257d73 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -577,6 +577,7 @@ variable "scale_sets" { zones = optional(list(string)) disk_type = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) overprovision = optional(bool) platform_fault_domain_count = optional(number) @@ -585,7 +586,6 @@ variable "scale_sets" { boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 3e3b32a3..3e0c8874 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -899,6 +899,7 @@ map(object({ replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -1077,13 +1078,13 @@ map(object({ disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index 08812d7d..b0184dac 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -561,6 +561,7 @@ variable "bootstrap_storages" { replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -732,13 +733,13 @@ variable "vmseries" { disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index a823dae2..9f5b128a 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -918,6 +918,7 @@ map(object({ zones = optional(list(string)) disk_type = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) overprovision = optional(bool) platform_fault_domain_count = optional(number) @@ -926,7 +927,6 @@ map(object({ boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 2dd056cb..1e257d73 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -577,6 +577,7 @@ variable "scale_sets" { zones = optional(list(string)) disk_type = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) overprovision = optional(bool) platform_fault_domain_count = optional(number) @@ -585,7 +586,6 @@ variable "scale_sets" { boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool) })) autoscaling_configuration = optional(object({ default_count = optional(number) diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 4d26a721..8d990872 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -549,6 +549,7 @@ map(object({ replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -722,13 +723,13 @@ map(object({ disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index dde9ac6b..e557ac8a 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -293,6 +293,7 @@ variable "bootstrap_storages" { replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -459,13 +460,13 @@ variable "vmseries" { disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 30b9efb9..0df5fff6 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -442,16 +442,17 @@ map(object({ custom_id = optional(string) }) virtual_machine = object({ - size = optional(string) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) + size = optional(string) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) }) interfaces = list(object({ name = string diff --git a/examples/standalone_panorama/variables.tf b/examples/standalone_panorama/variables.tf index 503ebc45..d8169bef 100644 --- a/examples/standalone_panorama/variables.tf +++ b/examples/standalone_panorama/variables.tf @@ -219,16 +219,17 @@ variable "panoramas" { custom_id = optional(string) }) virtual_machine = object({ - size = optional(string) - zone = string - disk_type = optional(string) - disk_name = optional(string) - avset_key = optional(string) - encryption_at_host_enabled = optional(bool) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string) - identity_ids = optional(list(string)) + size = optional(string) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) }) interfaces = list(object({ name = string diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 2e0b379c..d1d742a6 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -830,6 +830,7 @@ map(object({ replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -1008,13 +1009,13 @@ map(object({ disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index 08812d7d..b0184dac 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -561,6 +561,7 @@ variable "bootstrap_storages" { replication_type = optional(string) kind = optional(string) tier = optional(string) + blob_retention = optional(number) }), {}) storage_network_security = optional(object({ min_tls_version = optional(string) @@ -732,13 +733,13 @@ variable "vmseries" { disk_name = optional(string) avset_key = optional(string) accelerated_networking = optional(bool) + allow_extension_operations = optional(bool) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, true) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string) identity_ids = optional(list(string)) - allow_extension_operations = optional(bool) }) interfaces = list(object({ name = string diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index 173eda5d..e017d58c 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -257,6 +257,8 @@ Following properties are available: - `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or `FileStorage` the `tier` can only be set to `Premium`. +- `blob_retention` - (`number`, optional, defaults to Azure default) specifies the number of days that the blob should be + retained before irreversibly deleted. When set to `0`, soft delete is disabled for the Storage Account. Type: @@ -267,6 +269,7 @@ object({ replication_type = optional(string, "LRS") kind = optional(string, "StorageV2") tier = optional(string, "Standard") + blob_retention = optional(number) }) ``` @@ -403,5 +406,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/main.tf b/modules/bootstrap/main.tf index 541a1a64..44d458df 100644 --- a/modules/bootstrap/main.tf +++ b/modules/bootstrap/main.tf @@ -2,19 +2,26 @@ resource "azurerm_storage_account" "this" { count = var.storage_account.create ? 1 : 0 - name = var.name - location = var.region - resource_group_name = var.resource_group_name - min_tls_version = var.storage_network_security.min_tls_version - account_replication_type = var.storage_account.replication_type - account_tier = var.storage_account.tier - account_kind = var.storage_account.kind - tags = var.tags + name = var.name + location = var.region + resource_group_name = var.resource_group_name + min_tls_version = var.storage_network_security.min_tls_version + allow_nested_items_to_be_public = false + account_replication_type = var.storage_account.replication_type + account_tier = var.storage_account.tier + account_kind = var.storage_account.kind + tags = var.tags + + blob_properties { + delete_retention_policy { + days = var.storage_account.blob_retention + } + } lifecycle { precondition { condition = var.region != null - error_message = "When creating a storage account the `location` variable cannot be null." + error_message = "When creating a storage account the `region` variable cannot be null." } } } diff --git a/modules/bootstrap/variables.tf b/modules/bootstrap/variables.tf index 3daf1d20..7cc17210 100644 --- a/modules/bootstrap/variables.tf +++ b/modules/bootstrap/variables.tf @@ -47,6 +47,8 @@ variable "storage_account" { - `tier` - (`string`, optional, defaults to `Standard`) only for newly created Storage Accounts, defines the account tier. Can be either `Standard` or `Premium`. Note, that for `kind` set to `BlockBlobStorage` or `FileStorage` the `tier` can only be set to `Premium`. + - `blob_retention` - (`number`, optional, defaults to Azure default) specifies the number of days that the blob should be + retained before irreversibly deleted. When set to `0`, soft delete is disabled for the Storage Account. EOF default = {} nullable = false @@ -55,6 +57,7 @@ variable "storage_account" { replication_type = optional(string, "LRS") kind = optional(string, "StorageV2") tier = optional(string, "Standard") + blob_retention = optional(number) }) validation { # replication_type condition = contains(["LRS", "GRS", "RAGRS", "ZRS", "GZRS", "RAGZRS"], var.storage_account.replication_type) @@ -84,6 +87,14 @@ variable "storage_account" { If the `kind` property is set to either \"BlockBlobStorage\" or \"FileStorage\", the `tier` has to be set to \"Premium\"." EOF } + validation { # blob_retention + condition = var.storage_account.blob_retention != null ? ( + var.storage_account.blob_retention >= 0 && var.storage_account.blob_retention <= 365 + ) : true + error_message = <<-EOF + The `blob_retention` property can take values between 0 and 365. + EOF + } } variable "storage_network_security" { diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index 74743982..560487af 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -324,5 +324,4 @@ Default value: `map[name:lb_rule]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index b5ed85bd..ce4ea094 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -196,5 +196,4 @@ Default value: `map[application_gw:agw application_insights:appi availability_se <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/README.md b/modules/natgw/README.md index f2fe33f5..d239caa7 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -314,5 +314,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index 4917bf9e..3a474378 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -224,5 +224,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 1acd0bd5..54e4a930 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -200,8 +200,9 @@ List of other, optional properties: - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `encryption_at_host_enabled` - (`bool`, optional, defaults to `false`) should all the disks be encrypted by enabling Encryption at Host. -- `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files. +- `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. +- `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold diagnostic + files. - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or "SystemAssigned, UserAssigned". @@ -213,17 +214,18 @@ Type: ```hcl object({ - size = optional(string, "Standard_D5_v2") - zone = string - disk_type = optional(string, "StandardSSD_LRS") - disk_name = string - avset_id = optional(string) - allow_extension_operations = optional(bool, false) - encryption_at_host_enabled = optional(bool, false) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) + size = optional(string, "Standard_D5_v2") + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + allow_extension_operations = optional(bool, false) + encryption_at_host_enabled = optional(bool, false) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) }) ``` @@ -368,5 +370,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/main.tf b/modules/panorama/main.tf index e3d2577c..e96d47d7 100644 --- a/modules/panorama/main.tf +++ b/modules/panorama/main.tf @@ -101,10 +101,12 @@ resource "azurerm_linux_virtual_machine" "this" { } } - # After converting to azurerm_linux_virtual_machine, an empty block boot_diagnostics {} will use managed storage. Want. - # 2.36 in required_providers per https://github.com/terraform-providers/terraform-provider-azurerm/pull/8917 - boot_diagnostics { - storage_account_uri = var.virtual_machine.diagnostics_storage_uri + # An empty block boot_diagnostics {} will use managed storage + dynamic "boot_diagnostics" { + for_each = var.virtual_machine.enable_boot_diagnostics ? [1] : [] + content { + storage_account_uri = var.virtual_machine.boot_diagnostics_storage_uri + } } identity { diff --git a/modules/panorama/variables.tf b/modules/panorama/variables.tf index fd0d0964..716c7e62 100644 --- a/modules/panorama/variables.tf +++ b/modules/panorama/variables.tf @@ -115,8 +115,9 @@ variable "virtual_machine" { - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `encryption_at_host_enabled` - (`bool`, optional, defaults to `false`) should all the disks be encrypted by enabling Encryption at Host. - - `diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold - diagnostic files. + - `enable_boot_diagnostics` - (`bool`, optional, defaults to `false`) enables boot diagnostics for a VM. + - `boot_diagnostics_storage_uri` - (`string`, optional, defaults to `null`) storage account's blob endpoint to hold diagnostic + files. - `identity_type` - (`string`, optional, defaults to `SystemAssigned`) type of Managed Service Identity that should be configured on this VM. Can be one of "SystemAssigned", "UserAssigned" or "SystemAssigned, UserAssigned". @@ -124,17 +125,18 @@ variable "virtual_machine" { assigned to this VM. Required only if `identity_type` is not "SystemAssigned". EOF type = object({ - size = optional(string, "Standard_D5_v2") - zone = string - disk_type = optional(string, "StandardSSD_LRS") - disk_name = string - avset_id = optional(string) - allow_extension_operations = optional(bool, false) - encryption_at_host_enabled = optional(bool, false) - disk_encryption_set_id = optional(string) - diagnostics_storage_uri = optional(string) - identity_type = optional(string, "SystemAssigned") - identity_ids = optional(list(string), []) + size = optional(string, "Standard_D5_v2") + zone = string + disk_type = optional(string, "StandardSSD_LRS") + disk_name = string + avset_id = optional(string) + allow_extension_operations = optional(bool, false) + encryption_at_host_enabled = optional(bool, false) + disk_encryption_set_id = optional(string) + enable_boot_diagnostics = optional(bool, false) + boot_diagnostics_storage_uri = optional(string) + identity_type = optional(string, "SystemAssigned") + identity_ids = optional(list(string), []) }) validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) diff --git a/modules/test_infrastructure/main_test.go b/modules/test_infrastructure/main_test.go index 7d753288..97386360 100644 --- a/modules/test_infrastructure/main_test.go +++ b/modules/test_infrastructure/main_test.go @@ -3,7 +3,7 @@ package test_infrastructure import ( "testing" - "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton/pkg/testskeleton" ) func TestValidate(t *testing.T) { diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index f35877f7..6ae66955 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -222,6 +222,7 @@ List of other, optional properties: - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the management interface (always disabled). +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted @@ -234,7 +235,6 @@ List of other, optional properties: "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be assigned to this VM. Required only if `identity_type` is not "SystemAssigned". -- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. Type: @@ -248,13 +248,13 @@ object({ disk_name = string avset_id = optional(string) accelerated_networking = optional(bool, true) + allow_extension_operations = optional(bool, false) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, false) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string, "SystemAssigned") identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) }) ``` @@ -358,5 +358,4 @@ Default value: `map[]` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/main.tf b/modules/vmseries/main.tf index 99c7272a..c3c3eae3 100644 --- a/modules/vmseries/main.tf +++ b/modules/vmseries/main.tf @@ -58,6 +58,7 @@ resource "azurerm_linux_virtual_machine" "this" { size = var.virtual_machine.size zone = var.virtual_machine.zone availability_set_id = var.virtual_machine.avset_id + allow_extension_operations = var.virtual_machine.allow_extension_operations encryption_at_host_enabled = var.virtual_machine.encryption_at_host_enabled network_interface_ids = [for v in var.interfaces : azurerm_network_interface.this[v.name].id] @@ -105,6 +106,7 @@ resource "azurerm_linux_virtual_machine" "this" { custom_data = var.virtual_machine.bootstrap_options == null ? null : base64encode(var.virtual_machine.bootstrap_options) + # An empty block boot_diagnostics {} will use managed storage dynamic "boot_diagnostics" { for_each = var.virtual_machine.enable_boot_diagnostics ? [1] : [] content { diff --git a/modules/vmseries/variables.tf b/modules/vmseries/variables.tf index 67f2259e..8d0449a0 100644 --- a/modules/vmseries/variables.tf +++ b/modules/vmseries/variables.tf @@ -119,6 +119,7 @@ variable "virtual_machine" { - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the management interface (always disabled). + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted @@ -131,7 +132,6 @@ variable "virtual_machine" { "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be assigned to this VM. Required only if `identity_type` is not "SystemAssigned". - - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. EOF type = object({ size = optional(string, "Standard_D3_v2") @@ -141,13 +141,13 @@ variable "virtual_machine" { disk_name = string avset_id = optional(string) accelerated_networking = optional(bool, true) + allow_extension_operations = optional(bool, false) encryption_at_host_enabled = optional(bool) disk_encryption_set_id = optional(string) enable_boot_diagnostics = optional(bool, false) boot_diagnostics_storage_uri = optional(string) identity_type = optional(string, "SystemAssigned") identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) }) validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine.disk_type) diff --git a/modules/vmss/README.md b/modules/vmss/README.md index b685ae5f..ae21bfd1 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -374,6 +374,7 @@ List of other, optional properties: - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the management interface (always disabled). +- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling @@ -392,8 +393,6 @@ List of other, optional properties: "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be assigned to this VM. Required only if `identity_type` is not "SystemAssigned". -- `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - Type: @@ -405,6 +404,7 @@ object({ zones = optional(list(string)) disk_type = optional(string, "StandardSSD_LRS") accelerated_networking = optional(bool, true) + allow_extension_operations = optional(bool, false) encryption_at_host_enabled = optional(bool) overprovision = optional(bool, true) platform_fault_domain_count = optional(number) @@ -414,7 +414,6 @@ object({ boot_diagnostics_storage_uri = optional(string) identity_type = optional(string, "SystemAssigned") identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) }) ``` @@ -648,5 +647,4 @@ Default value: `[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/dt_string_converter/README.md b/modules/vmss/dt_string_converter/README.md index 28a24b29..1d77b27e 100644 --- a/modules/vmss/dt_string_converter/README.md +++ b/modules/vmss/dt_string_converter/README.md @@ -46,5 +46,4 @@ Type: number <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/variables.tf b/modules/vmss/variables.tf index 5a4e37f4..aa7c3288 100644 --- a/modules/vmss/variables.tf +++ b/modules/vmss/variables.tf @@ -135,6 +135,7 @@ variable "virtual_machine_scale_set" { - `accelerated_networking` - (`bool`, optional, defaults to `true`) when set to `true` enables Azure accelerated networking (SR-IOV) for all dataplane network interfaces, this does not affect the management interface (always disabled). + - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - `disk_encryption_set_id` - (`string`, optional, defaults to `null`) the ID of the Disk Encryption Set which should be used to encrypt this VM's disk. - `encryption_at_host_enabled` - (`bool`, optional, defaults to Azure defaults) should all of disks be encrypted by enabling @@ -153,8 +154,6 @@ variable "virtual_machine_scale_set" { "SystemAssigned, UserAssigned". - `identity_ids` - (`list`, optional, defaults to `[]`) a list of User Assigned Managed Identity IDs to be assigned to this VM. Required only if `identity_type` is not "SystemAssigned". - - `allow_extension_operations` - (`bool`, optional, defaults to `false`) should Extension Operations be allowed on this VM. - EOF default = {} nullable = false @@ -164,6 +163,7 @@ variable "virtual_machine_scale_set" { zones = optional(list(string)) disk_type = optional(string, "StandardSSD_LRS") accelerated_networking = optional(bool, true) + allow_extension_operations = optional(bool, false) encryption_at_host_enabled = optional(bool) overprovision = optional(bool, true) platform_fault_domain_count = optional(number) @@ -173,7 +173,6 @@ variable "virtual_machine_scale_set" { boot_diagnostics_storage_uri = optional(string) identity_type = optional(string, "SystemAssigned") identity_ids = optional(list(string), []) - allow_extension_operations = optional(bool, false) }) validation { # disk_type condition = contains(["Standard_LRS", "StandardSSD_LRS", "Premium_LRS"], var.virtual_machine_scale_set.disk_type) diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 6790b96b..dfa4ca1f 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -534,5 +534,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 26dcb618..2419759b 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -137,5 +137,4 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2cc93d1c..6e175e3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pre-commit==3.6.0 \ No newline at end of file +pre-commit==3.7.0 \ No newline at end of file From 655eba03e2880f4e117b09e874b6892209d7ff66 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Fri, 5 Apr 2024 10:31:10 +0200 Subject: [PATCH 29/49] feat: Unify #TODO markers in examples tfvars (#33) --- examples/appgw/README.md | 1 + examples/common_vmseries/README.md | 1 + examples/common_vmseries/example.tfvars | 8 ++++---- examples/common_vmseries_and_autoscale/README.md | 1 + examples/common_vmseries_and_autoscale/example.tfvars | 8 ++++---- examples/common_vmseries_and_autoscale/main.tf | 4 +++- examples/dedicated_vmseries/README.md | 1 + examples/dedicated_vmseries/example.tfvars | 10 +++++----- examples/dedicated_vmseries/main.tf | 6 +++--- examples/dedicated_vmseries_and_autoscale/README.md | 1 + .../dedicated_vmseries_and_autoscale/example.tfvars | 8 ++++---- examples/dedicated_vmseries_and_autoscale/main.tf | 4 +++- examples/gwlb_with_vmseries/README.md | 1 + examples/gwlb_with_vmseries/example.tfvars | 8 ++++---- examples/standalone_panorama/README.md | 1 + examples/standalone_panorama/example.tfvars | 2 +- examples/standalone_vmseries/README.md | 1 + examples/standalone_vmseries/example.tfvars | 2 +- examples/virtual_network_gateway/README.md | 1 + modules/appgw/README.md | 1 + modules/bootstrap/.header.md | 2 +- modules/bootstrap/README.md | 3 ++- modules/gwlb/README.md | 1 + modules/loadbalancer/README.md | 1 + modules/name_templater/README.md | 1 + modules/natgw/README.md | 1 + modules/ngfw_metrics/README.md | 1 + modules/panorama/README.md | 1 + modules/virtual_network_gateway/README.md | 1 + modules/vmseries/README.md | 1 + modules/vmss/README.md | 1 + modules/vmss/dt_string_converter/README.md | 1 + modules/vnet/.header.md | 4 ++-- modules/vnet/README.md | 5 +++-- modules/vnet_peering/README.md | 1 + 35 files changed, 61 insertions(+), 34 deletions(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 6683ba90..247f7dac 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -376,4 +376,5 @@ Default value: `true` + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index ae660f4b..5a2019c7 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -1326,4 +1326,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index f8cfdd4b..81898aaf 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -133,7 +133,7 @@ load_balancers = { nsg_auto_rules_settings = { nsg_vnet_key = "transit" nsg_key = "public" - source_ips = ["0.0.0.0/0"] + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app1" = { @@ -303,7 +303,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.0.0/25" destination_port_ranges = ["80", "443"] @@ -369,7 +369,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.1.0/25" destination_port_ranges = ["80", "443"] diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index aba250e4..75b60e44 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -1218,4 +1218,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 14118439..e39f5ce4 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -133,7 +133,7 @@ load_balancers = { nsg_auto_rules_settings = { 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 + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app1" = { @@ -313,7 +313,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to manage the appliances source_port_range = "*" destination_address_prefix = "10.100.0.0/25" destination_port_ranges = ["80", "443"] @@ -379,7 +379,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.1.0/25" destination_port_ranges = ["80", "443"] diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 60b8ea76..127c7b1e 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -5,7 +5,9 @@ resource "random_password" "this" { count = anytrue([ for _, v in var.scale_sets : v.authentication.password == null if !v.authentication.disable_password_authentication - ]) ? 1 : 0 + ]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 3e0c8874..0beae701 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -1330,4 +1330,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 77246de3..b47a64c1 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -119,7 +119,7 @@ load_balancers = { nsg_auto_rules_settings = { nsg_vnet_key = "transit" nsg_key = "public" - source_ips = ["0.0.0.0/0"] + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app1" = { @@ -168,7 +168,7 @@ bootstrap_storages = { storage_network_security = { vnet_key = "transit" allowed_subnet_keys = ["management"] - allowed_public_ips = ["134.238.135.14", "134.238.135.140"] + allowed_public_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } } } @@ -334,7 +334,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.0.0/25" destination_port_ranges = ["80", "443"] @@ -400,7 +400,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.1.0/25" destination_port_ranges = ["80", "443"] diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index c3038507..7aa0163e 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -2,9 +2,9 @@ # https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { - count = anytrue([ - for _, v in var.vmseries : v.authentication.password == null - ]) ? 1 : 0 + count = anytrue([for _, v in var.vmseries : v.authentication.password == null]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 9f5b128a..b1682877 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -1212,4 +1212,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index f60feaae..d0d8f6d4 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -133,7 +133,7 @@ load_balancers = { nsg_auto_rules_settings = { 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 + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app1" = { @@ -263,7 +263,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.0.0/25" destination_port_ranges = ["80", "443"] @@ -329,7 +329,7 @@ test_infrastructure = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure source_port_range = "*" destination_address_prefix = "10.100.1.0/25" destination_port_ranges = ["80", "443"] diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index c61aedae..58b6f499 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -5,7 +5,9 @@ resource "random_password" "this" { count = anytrue([ for _, v in var.scale_sets : v.authentication.password == null if !v.authentication.disable_password_authentication - ]) ? 1 : 0 + ]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 8d990872..f10a6e0e 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -977,4 +977,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index dc78f00c..691a0865 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -126,7 +126,7 @@ bootstrap_storages = { storage_network_security = { vnet_key = "transit" allowed_subnet_keys = ["management"] - allowed_public_ips = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances + allowed_public_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } } } @@ -227,7 +227,7 @@ test_infrastructure = { nsg_auto_rules_settings = { nsg_vnet_key = "app1" nsg_key = "app1" - source_ips = ["0.0.0.0/0"] + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app1" = { @@ -305,7 +305,7 @@ test_infrastructure = { nsg_auto_rules_settings = { nsg_vnet_key = "app2" nsg_key = "app2" - source_ips = ["0.0.0.0/0"] + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB } frontend_ips = { "app2" = { diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 0df5fff6..187e1a51 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -476,4 +476,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 1aa23bea..2f338520 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -25,7 +25,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["0.0.0.0/0"] # TODO: whitelist public IP addresses that will be used to manage the appliances + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to manage the appliances source_port_range = "*" destination_address_prefix = "10.1.0.0/24" destination_port_ranges = ["22", "443"] diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index d1d742a6..2356e2eb 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -1261,4 +1261,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 6d119752..c4f0ddb9 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -24,7 +24,7 @@ vnets = { direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index 6dda627c..b7745a3e 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -314,4 +314,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 22d29d3f..124e25f2 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -1485,4 +1485,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/.header.md b/modules/bootstrap/.header.md index 3485ffd6..4ecce6d4 100644 --- a/modules/bootstrap/.header.md +++ b/modules/bootstrap/.header.md @@ -60,7 +60,7 @@ module "bootstrap" { } storage_network_security = { min_tls_version = "TLS1_1" - allowed_public_ips = ["1.2.3.4"] + allowed_public_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } file_shares = { "vm01" = { diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index e017d58c..9baf16b9 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -61,7 +61,7 @@ module "bootstrap" { } storage_network_security = { min_tls_version = "TLS1_1" - allowed_public_ips = ["1.2.3.4"] + allowed_public_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } file_shares = { "vm01" = { @@ -406,4 +406,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index 560487af..74743982 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -324,4 +324,5 @@ Default value: `map[name:lb_rule]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index 051a10e7..ad721e14 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -460,4 +460,5 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index ce4ea094..b5ed85bd 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -196,4 +196,5 @@ Default value: `map[application_gw:agw application_insights:appi availability_se <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/README.md b/modules/natgw/README.md index d239caa7..f2fe33f5 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -314,4 +314,5 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index 3a474378..4917bf9e 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -224,4 +224,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 54e4a930..ce56b382 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -370,4 +370,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index c6b525e2..8926a617 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -814,4 +814,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 6ae66955..547f9de1 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -358,4 +358,5 @@ Default value: `map[]` + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/README.md b/modules/vmss/README.md index ae21bfd1..9f491416 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -647,4 +647,5 @@ Default value: `[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/dt_string_converter/README.md b/modules/vmss/dt_string_converter/README.md index 1d77b27e..28a24b29 100644 --- a/modules/vmss/dt_string_converter/README.md +++ b/modules/vmss/dt_string_converter/README.md @@ -46,4 +46,5 @@ Type: number <sup>[back to list](#modules-required-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/.header.md b/modules/vnet/.header.md index 3a835852..274c677b 100644 --- a/modules/vnet/.header.md +++ b/modules/vnet/.header.md @@ -22,7 +22,7 @@ This module is designed to work in several *modes* depending on which variables direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -91,7 +91,7 @@ This module is designed to work in several *modes* depending on which variables direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] diff --git a/modules/vnet/README.md b/modules/vnet/README.md index dfa4ca1f..755169e0 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -22,7 +22,7 @@ This module is designed to work in several *modes* depending on which variables direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -91,7 +91,7 @@ This module is designed to work in several *modes* depending on which variables direction = "Inbound" access = "Allow" protocol = "Tcp" - source_address_prefixes = ["1.2.3.4"] + source_address_prefixes = ["1.1.1.1/32"] # 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"] @@ -534,4 +534,5 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 2419759b..26dcb618 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -137,4 +137,5 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> + <!-- END_TF_DOCS --> \ No newline at end of file From 668ca494da80cffeb1c3bd8345875fea6e8f4869 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Fri, 5 Apr 2024 23:02:51 +0200 Subject: [PATCH 30/49] Idempotence fix to common_vmseries example --- examples/common_vmseries/README.md | 1 - examples/common_vmseries/example.tfvars | 2 +- examples/common_vmseries/main.tf | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index 5a2019c7..ae660f4b 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -1326,5 +1326,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 81898aaf..5e8b2572 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -358,7 +358,7 @@ test_infrastructure = { "app2" = { name = "app2-vnet" address_space = ["10.100.1.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app2" = { name = "app2-nsg" diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 7aa0163e..879d90cf 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -416,6 +416,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) From 707155941d35a3fffa9d92be8eae43be84cc13e1 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Fri, 5 Apr 2024 23:26:44 +0200 Subject: [PATCH 31/49] Idempotence fix to common_vmseries example v2 --- examples/common_vmseries/example.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 5e8b2572..113fab9c 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -292,7 +292,7 @@ test_infrastructure = { "app1" = { name = "app1-vnet" address_space = ["10.100.0.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app1" = { name = "app1-nsg" From 1e830fd24d6dee1419213cad1647f6d6cccc1d68 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Sat, 6 Apr 2024 00:04:33 +0200 Subject: [PATCH 32/49] Idempotence fix to all examples (vnet peering) --- examples/common_vmseries_and_autoscale/README.md | 1 - examples/common_vmseries_and_autoscale/example.tfvars | 4 ++-- examples/common_vmseries_and_autoscale/main.tf | 1 + examples/dedicated_vmseries/README.md | 1 - examples/dedicated_vmseries/example.tfvars | 4 ++-- examples/dedicated_vmseries/main.tf | 1 + examples/dedicated_vmseries_and_autoscale/README.md | 1 - examples/dedicated_vmseries_and_autoscale/example.tfvars | 4 ++-- examples/dedicated_vmseries_and_autoscale/main.tf | 1 + examples/gwlb_with_vmseries/README.md | 1 - examples/gwlb_with_vmseries/example.tfvars | 4 ++-- examples/gwlb_with_vmseries/main.tf | 1 + examples/standalone_vmseries/README.md | 1 - examples/standalone_vmseries/main.tf | 1 + 14 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index 75b60e44..aba250e4 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -1218,5 +1218,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index e39f5ce4..e1dc6aac 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -302,7 +302,7 @@ test_infrastructure = { "app1" = { name = "app1-vnet" address_space = ["10.100.0.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app1" = { name = "app1-nsg" @@ -368,7 +368,7 @@ test_infrastructure = { "app2" = { name = "app2-vnet" address_space = ["10.100.1.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app2" = { name = "app2-nsg" diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 127c7b1e..1b904868 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -275,6 +275,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 0beae701..3e0c8874 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -1330,5 +1330,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index b47a64c1..d46f8176 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -323,7 +323,7 @@ test_infrastructure = { "app1" = { name = "app1-vnet" address_space = ["10.100.0.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app1" = { name = "app1-nsg" @@ -389,7 +389,7 @@ test_infrastructure = { "app2" = { name = "app2-vnet" address_space = ["10.100.1.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app2" = { name = "app2-nsg" diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 7aa0163e..879d90cf 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -416,6 +416,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index b1682877..9f5b128a 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -1212,5 +1212,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index d0d8f6d4..814b59b1 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -252,7 +252,7 @@ test_infrastructure = { "app1" = { name = "app1-vnet" address_space = ["10.100.0.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app1" = { name = "app1-nsg" @@ -318,7 +318,7 @@ test_infrastructure = { "app2" = { name = "app2-vnet" address_space = ["10.100.1.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app2" = { name = "app2-nsg" diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index 58b6f499..affff1f2 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -275,6 +275,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index f10a6e0e..8d990872 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -977,5 +977,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 691a0865..c11d530a 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -202,7 +202,7 @@ test_infrastructure = { "app1" = { name = "app1-vnet" address_space = ["10.100.0.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app1" = { name = "app1-nsg" @@ -280,7 +280,7 @@ test_infrastructure = { "app2" = { name = "app2-vnet" address_space = ["10.100.1.0/25"] - hub_vnet_name = "example-transit" + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string network_security_groups = { "app2" = { name = "app2-nsg" diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 609b22e6..17944a9a 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -296,6 +296,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index 2356e2eb..d1d742a6 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -1261,5 +1261,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index c3038507..f2b30167 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -416,6 +416,7 @@ module "test_infrastructure" { region = var.region vnets = { for k, v in each.value.vnets : k => merge(v, { name = "${var.name_prefix}${v.name}" + hub_vnet_name = "${var.name_prefix}${v.hub_vnet_name}" hub_resource_group_name = coalesce(v.hub_resource_group_name, local.resource_group.name) network_security_groups = { for kv, vv in v.network_security_groups : kv => merge(vv, { name = "${var.name_prefix}${vv.name}" }) From c3f77e33d7b21dc1438169d4ef5c7fae292be156 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Sat, 6 Apr 2024 13:19:48 +0200 Subject: [PATCH 33/49] Autoscaling profiles update in common autoscale example --- examples/common_vmseries_and_autoscale/example.tfvars | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index e1dc6aac..55f6c6b1 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -238,7 +238,7 @@ scale_sets = { zones = ["1", "2", "3"] } autoscaling_configuration = { - default_count = 1 + default_count = 2 } interfaces = [ { @@ -262,13 +262,13 @@ scale_sets = { autoscaling_profiles = [ { name = "default_profile" - default_count = 0 + default_count = 2 }, { name = "weekday_profile" default_count = 2 - minimum_count = 1 - maximum_count = 3 + minimum_count = 2 + maximum_count = 4 recurrence = { days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] start_time = "07:30" From 8c9708abb7aed4ac36ee0497b5e94fe74e4114d3 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Fri, 12 Apr 2024 16:27:29 +0200 Subject: [PATCH 34/49] Bumped AzureRM provider version --- examples/appgw/README.md | 1 - examples/standalone_panorama/README.md | 1 - examples/virtual_network_gateway/README.md | 1 - modules/appgw/README.md | 5 ++--- modules/appgw/versions.tf | 2 +- modules/bootstrap/README.md | 5 ++--- modules/bootstrap/versions.tf | 2 +- modules/gwlb/README.md | 5 ++--- modules/gwlb/versions.tf | 2 +- modules/loadbalancer/README.md | 5 ++--- modules/loadbalancer/versions.tf | 2 +- modules/name_templater/README.md | 1 - modules/natgw/README.md | 5 ++--- modules/natgw/versions.tf | 2 +- modules/ngfw_metrics/README.md | 5 ++--- modules/ngfw_metrics/versions.tf | 2 +- modules/panorama/README.md | 5 ++--- modules/panorama/versions.tf | 2 +- modules/test_infrastructure/versions.tf | 2 +- modules/virtual_network_gateway/README.md | 5 ++--- modules/virtual_network_gateway/versions.tf | 2 +- modules/vmseries/README.md | 5 ++--- modules/vmseries/versions.tf | 2 +- modules/vmss/README.md | 5 ++--- modules/vmss/dt_string_converter/README.md | 1 - modules/vmss/versions.tf | 2 +- modules/vnet/README.md | 5 ++--- modules/vnet/versions.tf | 2 +- modules/vnet_peering/README.md | 5 ++--- modules/vnet_peering/versions.tf | 2 +- 30 files changed, 37 insertions(+), 54 deletions(-) diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 247f7dac..6683ba90 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -376,5 +376,4 @@ Default value: `true` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 187e1a51..0df5fff6 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -476,5 +476,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index b7745a3e..6dda627c 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -314,5 +314,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 124e25f2..2c3cb8e1 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -860,12 +860,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -1485,5 +1485,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/appgw/versions.tf b/modules/appgw/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/appgw/versions.tf +++ b/modules/appgw/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index 9baf16b9..507e7f3b 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -163,12 +163,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -406,5 +406,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/versions.tf b/modules/bootstrap/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/bootstrap/versions.tf +++ b/modules/bootstrap/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index 74743982..1408307e 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -116,12 +116,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -324,5 +324,4 @@ Default value: `map[name:lb_rule]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/gwlb/versions.tf b/modules/gwlb/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/gwlb/versions.tf +++ b/modules/gwlb/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index ad721e14..ec521cbd 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -121,12 +121,12 @@ private IP address otherwise. Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -460,5 +460,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/loadbalancer/versions.tf b/modules/loadbalancer/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/loadbalancer/versions.tf +++ b/modules/loadbalancer/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index b5ed85bd..ce4ea094 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -196,5 +196,4 @@ Default value: `map[application_gw:agw application_insights:appi availability_se <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/README.md b/modules/natgw/README.md index f2fe33f5..2ab2f2a4 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -75,12 +75,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -314,5 +314,4 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/natgw/versions.tf b/modules/natgw/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/natgw/versions.tf +++ b/modules/natgw/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index 4917bf9e..e2e0287a 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -88,12 +88,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -224,5 +224,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/ngfw_metrics/versions.tf b/modules/ngfw_metrics/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/ngfw_metrics/versions.tf +++ b/modules/ngfw_metrics/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/panorama/README.md b/modules/panorama/README.md index ce56b382..318ac310 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -54,12 +54,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -370,5 +370,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/versions.tf b/modules/panorama/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/panorama/versions.tf +++ b/modules/panorama/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/test_infrastructure/versions.tf b/modules/test_infrastructure/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/test_infrastructure/versions.tf +++ b/modules/test_infrastructure/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index 8926a617..b5a513c3 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -354,12 +354,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -814,5 +814,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/virtual_network_gateway/versions.tf b/modules/virtual_network_gateway/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/virtual_network_gateway/versions.tf +++ b/modules/virtual_network_gateway/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 547f9de1..483c32ab 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -69,12 +69,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -358,5 +358,4 @@ Default value: `map[]` - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmseries/versions.tf b/modules/vmseries/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/vmseries/versions.tf +++ b/modules/vmseries/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 9f491416..1f853021 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -136,12 +136,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Modules used in this module: @@ -647,5 +647,4 @@ Default value: `[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/dt_string_converter/README.md b/modules/vmss/dt_string_converter/README.md index 28a24b29..1d77b27e 100644 --- a/modules/vmss/dt_string_converter/README.md +++ b/modules/vmss/dt_string_converter/README.md @@ -46,5 +46,4 @@ Type: number <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/versions.tf b/modules/vmss/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/vmss/versions.tf +++ b/modules/vmss/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 755169e0..2fdf8504 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -162,12 +162,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -534,5 +534,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/versions.tf b/modules/vnet/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/vnet/versions.tf +++ b/modules/vnet/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index 26dcb618..b27cf9ae 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -46,12 +46,12 @@ Name | Description Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 +- `azurerm`, version: ~> 3.98 @@ -137,5 +137,4 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet_peering/versions.tf b/modules/vnet_peering/versions.tf index 9abec711..d5f13e9a 100644 --- a/modules/vnet_peering/versions.tf +++ b/modules/vnet_peering/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "~> 3.80" + version = "~> 3.98" } } } From 8c6d17406558660be2d194030f09e61de0e22b89 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Fri, 12 Apr 2024 16:28:25 +0200 Subject: [PATCH 35/49] Replace deprecated property in bootstrap module --- modules/bootstrap/main.tf | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/bootstrap/main.tf b/modules/bootstrap/main.tf index 44d458df..c2c64133 100644 --- a/modules/bootstrap/main.tf +++ b/modules/bootstrap/main.tf @@ -93,9 +93,8 @@ resource "azurerm_storage_share_directory" "this" { if !var.file_shares_configuration.disable_package_dirs_creation } - name = each.value.folder_name - share_name = local.file_shares[each.value.share_key].name - storage_account_name = local.storage_account.name + name = each.value.folder_name + storage_share_id = local.file_shares[each.value.share_key].id } From 87b54de57420b687becc226226014f5e3bd0a4af Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 15 Apr 2024 08:23:49 +0200 Subject: [PATCH 36/49] fix(examples): Improvements after tests (#38) Co-authored-by: Adrian Celebanski <acelebanski@paloaltonetworks.com> --- .terraform-docs.yml | 38 ++++--- examples/appgw/README.md | 17 --- examples/common_vmseries/README.md | 106 +++++++++--------- examples/common_vmseries/example.tfvars | 38 ++++++- examples/common_vmseries/main.tf | 37 ++++-- examples/common_vmseries/variables.tf | 73 +++++++----- .../common_vmseries_and_autoscale/README.md | 50 +++++---- .../example.tfvars | 36 +++++- .../common_vmseries_and_autoscale/main.tf | 19 ++++ .../variables.tf | 19 ++++ examples/dedicated_vmseries/README.md | 52 +++++---- examples/dedicated_vmseries/example.tfvars | 42 ++++++- examples/dedicated_vmseries/main.tf | 37 ++++-- examples/dedicated_vmseries/variables.tf | 19 ++++ .../README.md | 50 +++++---- .../example.tfvars | 38 ++++++- .../dedicated_vmseries_and_autoscale/main.tf | 19 ++++ .../variables.tf | 19 ++++ examples/gwlb_with_vmseries/README.md | 50 +++++---- examples/gwlb_with_vmseries/example.tfvars | 38 ++++++- examples/gwlb_with_vmseries/main.tf | 37 ++++-- examples/gwlb_with_vmseries/variables.tf | 19 ++++ examples/standalone_panorama/README.md | 19 ---- examples/standalone_vmseries/README.md | 52 +++++---- examples/standalone_vmseries/example.tfvars | 10 +- examples/standalone_vmseries/main.tf | 37 ++++-- examples/standalone_vmseries/variables.tf | 19 ++++ examples/virtual_network_gateway/README.md | 18 --- modules/appgw/README.md | 33 ------ modules/bootstrap/README.md | 18 --- modules/gwlb/README.md | 19 ---- modules/loadbalancer/README.md | 19 ---- modules/name_templater/README.md | 14 --- modules/natgw/README.md | 20 ---- modules/ngfw_metrics/README.md | 17 --- modules/panorama/README.md | 19 ---- modules/virtual_network_gateway/README.md | 25 ----- modules/vmseries/README.md | 18 --- modules/vmss/README.md | 21 ---- modules/vmss/dt_string_converter/README.md | 3 - modules/vnet/README.md | 20 ---- modules/vnet_peering/README.md | 6 - 42 files changed, 692 insertions(+), 568 deletions(-) diff --git a/.terraform-docs.yml b/.terraform-docs.yml index 7816f7e6..329b4056 100644 --- a/.terraform-docs.yml +++ b/.terraform-docs.yml @@ -23,13 +23,13 @@ content: |- {{- range .Module.Inputs }} {{- if .Required }} [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. - {{- end -}} - {{ end }} + {{- end }} + {{- end }} - {{- $optional := false }} - {{- range .Module.Inputs }}{{ if not .Required }}{{ $optional = true }}{{ end }}{{ end }} + {{ $optional := false -}} + {{ range .Module.Inputs }}{{ if not .Required }}{{ $optional = true -}}{{ end -}}{{ end -}} - {{ if $optional }} + {{ if $optional -}} ## Module's Optional Inputs Name | Type | Description @@ -38,10 +38,10 @@ content: |- {{- if not .Required }} [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. {{- end -}} - {{ end }} + {{ end -}} {{ end }} - {{ if ne (len .Module.Outputs) 0 }} + {{ if ne (len .Module.Outputs) 0 -}} ## Module's Outputs Name | Description @@ -53,21 +53,21 @@ content: |- ## Module's Nameplate - {{ if ne (len .Module.Requirements) 0 }} + {{ if ne (len .Module.Requirements) 0 -}} Requirements needed by this module: {{ range .Module.Requirements }} - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} {{- end }} {{- end }} - {{ if ne (len .Module.Providers) 0 }} + {{ if ne (len .Module.Providers) 0 -}} Providers used in this module: {{ range .Module.Providers }} - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} {{- end }} {{- end }} - {{ if ne (len .Module.ModuleCalls) 0 }} + {{ if ne (len .Module.ModuleCalls) 0 -}} Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- @@ -76,7 +76,7 @@ content: |- {{- end }} {{- end }} - {{ if ne (len .Module.Resources) 0 }} + {{ if ne (len .Module.Resources) 0 -}} Resources used in this module: {{ range .Module.Resources }} - `{{ .Type }}` ({{ .Mode }}) @@ -87,7 +87,7 @@ content: |- ### Required Inputs - {{ range .Module.Inputs }} + {{ range .Module.Inputs -}} {{ if .Required -}} #### {{ .Name }} @@ -101,13 +101,14 @@ content: |- {{ end }} <sup>[back to list](#modules-required-inputs)</sup> - {{ end }} - {{- end }} + + {{ end -}} + {{- end -}} - {{ if $optional }} + {{ if $optional -}} ### Optional Inputs - {{ range .Module.Inputs }} + {{ range .Module.Inputs -}} {{ if not .Required -}} #### {{ .Name }} @@ -123,6 +124,7 @@ content: |- Default value: `{{ .Default }}` <sup>[back to list](#modules-optional-inputs)</sup> + {{ end }} - {{- end }} - {{ end }} + {{- end -}} + {{ end -}} \ No newline at end of file diff --git a/examples/appgw/README.md b/examples/appgw/README.md index 6683ba90..0c9ad9a3 100644 --- a/examples/appgw/README.md +++ b/examples/appgw/README.md @@ -14,7 +14,6 @@ Name | Type | Description [`vnets`](#vnets) | `map` | A map defining VNETs. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. - ## Module's Optional Inputs Name | Type | Description @@ -25,27 +24,22 @@ Name | Type | Description - ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `azurerm` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | `appgw` | - | ../../modules/appgw | - Resources used in this module: - `public_ip` (managed) @@ -56,8 +50,6 @@ Resources used in this module: ### Required Inputs - - #### region The Azure region to use. @@ -66,8 +58,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - #### resource_group_name Name of the Resource Group. @@ -322,11 +312,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - ### Optional Inputs - #### tags Map of tags to assign to the created resources. @@ -337,7 +324,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### name_prefix A prefix that will be added to all created resources. @@ -373,7 +359,4 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/examples/common_vmseries/README.md b/examples/common_vmseries/README.md index ae660f4b..b918fb96 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -183,7 +183,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -191,6 +190,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -200,8 +200,6 @@ Name | Type | Description [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -220,23 +218,21 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - `local` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `natgw` | - | ../../modules/natgw | `load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | @@ -245,7 +241,6 @@ Name | Version | Source | Description `vmseries` | - | ../../modules/vmseries | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `availability_set` (managed) @@ -258,9 +253,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -277,11 +269,10 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### 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 `true`) when set to `true` will create a VNET, `false` will source @@ -351,19 +342,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -374,7 +354,7 @@ Example: ``` name_prefix = "test-" ``` - + **Note!** \ 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. @@ -390,7 +370,7 @@ Default value: `` 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. @@ -400,8 +380,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -412,15 +390,41 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### natgws -A map defining NAT Gateways. +A map defining NAT Gateways. Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). - + Following properties are supported: - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full resource name, including prefixes. @@ -506,8 +510,8 @@ Following properties are available: - `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 refer to [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) for - available properties. - + available properties. + Please note that in this example two additional properties are available: - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition in the @@ -592,7 +596,7 @@ For detailed documentation on how to configure this resource, for available prop refer to [module documentation](../../modules/appgw/README.md). **Note!** \ -The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). +The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). It represents the Rules section of an Application Gateway in Azure Portal. Below you can find a brief list of most important properties: @@ -614,11 +618,11 @@ Below you can find a brief list of most important properties: settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. -- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see +- `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. -- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects +- `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. -- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, +- `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see @@ -766,7 +770,7 @@ Following properties are supported: - `name` - (`string`, required) name of the Application Insights. - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. - + **Note!** \ Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. @@ -847,7 +851,7 @@ You can create or re-use an existing Storage Account and/or File Share. For deta will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. - + The property you should pay attention to is: - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property @@ -856,8 +860,8 @@ You can create or re-use an existing Storage Account and/or File Share. For deta For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account. - + storage account. + The properties you should pay attention to are: - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the @@ -867,9 +871,9 @@ You can create or re-use an existing Storage Account and/or File Share. For deta Subnets described in `allowed_subnet_keys`. For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). - + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. - + The properties you should pay attention to are: - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the @@ -943,7 +947,7 @@ The most basic properties are as follows: **Note!** \ The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to `true`, then you have to specify `ssh_keys` property. For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). @@ -956,7 +960,7 @@ The most basic properties are as follows: For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). -- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. +- `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. Most common properties are: - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series @@ -1016,7 +1020,7 @@ The most basic properties are as follows: - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all private networks. When set it will override the private Subnet CIDR for inbound traffic static routes. - + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the @@ -1109,13 +1113,13 @@ For details and defaults for available options please refer to the Following properties are supported: -- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When +- `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When set to `false`, an existing Resource Group is sourced. - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic properties are as follows: - - `create_virtual_network` - (`bool`, optional, defaults to `true`) when set to `true` will create a VNET, + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. @@ -1131,7 +1135,7 @@ Following properties are supported: [VNET module documentation](../../modules/vnet/README.md#route_tables). For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). - + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. The most basic properties are as follows: @@ -1148,8 +1152,8 @@ Following properties are supported: - `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 refer to [loadbalancer module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) - for available properties. - + for available properties. + Please note that in this example two additional properties are available: - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition in the @@ -1180,10 +1184,10 @@ Following properties are supported: For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#test_vms). - + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as follows: - + - `name` - (`string`, required) an Azure Bastion name. - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 113fab9c..7cc95fbe 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -125,6 +125,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + # LOAD BALANCING load_balancers = { @@ -224,7 +232,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -254,7 +262,7 @@ vmseries = { "fw-2" = { name = "firewall02" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -297,9 +305,20 @@ test_infrastructure = { "app1" = { name = "app1-nsg" rules = { + from_bastion = { + name = "app1-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.0.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app1-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" @@ -363,9 +382,20 @@ test_infrastructure = { "app2" = { name = "app2-nsg" rules = { + from_bastion = { + name = "app2-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.1.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app2-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" diff --git a/examples/common_vmseries/main.tf b/examples/common_vmseries/main.tf index 879d90cf..08fc21d2 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -75,6 +75,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + module "natgw" { source = "../../modules/natgw" @@ -354,14 +373,16 @@ module "vmseries" { bootstrap_options = try( coalesce( each.value.virtual_machine.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), + try( + join(",", [ + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + null), ), null ) diff --git a/examples/common_vmseries/variables.tf b/examples/common_vmseries/variables.tf index b0184dac..39019f5a 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/variables.tf @@ -10,7 +10,7 @@ variable "name_prefix" { ``` name_prefix = "test-" ``` - + **Note!** \ 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. @@ -23,7 +23,7 @@ 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 @@ -51,7 +51,7 @@ variable "tags" { 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 `true`) when set to `true` will create a VNET, `false` will source @@ -114,14 +114,33 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + variable "natgws" { description = <<-EOF - A map defining NAT Gateways. + A map defining NAT Gateways. Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). - + Following properties are supported: - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full resource name, including prefixes. @@ -201,8 +220,8 @@ variable "load_balancers" { - `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 refer to [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) for - available properties. - + available properties. + Please note that in this example two additional properties are available: - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition in the @@ -280,7 +299,7 @@ variable "appgws" { refer to [module documentation](../../modules/appgw/README.md). **Note!** \ - The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). + The `rules` property is meant to bind together `backend_setting`, `redirect` or `url_path_map` (all 3 are mutually exclusive). It represents the Rules section of an Application Gateway in Azure Portal. Below you can find a brief list of most important properties: @@ -302,11 +321,11 @@ variable "appgws" { settings, see [module's documentation](../../modules/appgw/README.md#backend_settings) for details. - `probes` - (`map`, optional, defaults to module default) defines backend probes used check health of backends, see [module's documentation](../../modules/appgw/README.md#probes) for details. - - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see + - `rewrites` - (`map`, optional, defaults to module default) defines rewrite rules, see [module's documentation](../../modules/appgw/README.md#rewrites) for details. - - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects + - `redirects` - (`map`, optional, mutually exclusive with `backend_settings` and `url_path_maps`) static redirects definition, see [module's documentation](../../modules/appgw/README.md#redirects) for details. - - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, + - `url_path_maps` - (`map`, optional, mutually exclusive with `backend_settings` and `redirects`) URL path maps definition, see [module's documentation](../../modules/appgw/README.md#url_path_maps) for details. - `rules` - (`map`, required) Application Gateway Rules definition, bind together a `listener` with either `backend_setting`, `redirect` or `url_path_map`, see @@ -449,7 +468,7 @@ variable "availability_sets" { - `name` - (`string`, required) name of the Application Insights. - `update_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of update domains that are used. - `fault_domain_count` - (`number`, optional, defaults to Azure default) specifies the number of fault domains that are used. - + **Note!** \ Please keep in mind that Azure defaults are not working for every region (especially the small ones, without any Availability Zones). Please verify how many update and fault domain are supported in a region before deploying this resource. @@ -515,7 +534,7 @@ variable "bootstrap_storages" { will host (created) a Storage Account. When skipped the code will fall back to `var.resource_group_name`. - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration. - + The property you should pay attention to is: - `create` - (`bool`, optional, defaults to module default) controls if the Storage Account specified in the `name` property @@ -524,8 +543,8 @@ variable "bootstrap_storages" { For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** - storage account. - + storage account. + The properties you should pay attention to are: - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the @@ -535,9 +554,9 @@ variable "bootstrap_storages" { Subnets described in `allowed_subnet_keys`. For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_network_security). - + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. - + The properties you should pay attention to are: - `create_file_shares` - (`bool`, optional, defaults to module default) controls if the File Shares defined in the @@ -604,7 +623,7 @@ variable "vmseries" { **Note!** \ The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have - to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to `true`, then you have to specify `ssh_keys` property. For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). @@ -617,7 +636,7 @@ variable "vmseries" { For details on all properties refer to [module's documentation](../../modules/vmseries/README.md#image). - - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. + - `virtual_machine` - (`map`, optional, defaults to module default) a map that groups most common VM configuration options. Most common properties are: - `size` - (`string`, optional, defaults to module default) Azure VM size (type). Consult the *VM-Series @@ -677,7 +696,7 @@ variable "vmseries" { - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all private networks. When set it will override the private Subnet CIDR for inbound traffic static routes. - + For details on all properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). - `interfaces` - (`list`, required) configuration of all network interfaces. Order of the interfaces does matter - the @@ -787,13 +806,13 @@ variable "test_infrastructure" { Following properties are supported: - - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When + - `create_resource_group` - (`bool`, optional, defaults to `true`) when set to `true`, a new Resource Group is created. When set to `false`, an existing Resource Group is sourced. - `resource_group_name` - (`string`, optional) name of the Resource Group to be created or sourced. - `vnets` - (`map`, required) a map defining VNETs and peerings for the test environment. The most basic properties are as follows: - - `create_virtual_network` - (`bool`, optional, defaults to `true`) when set to `true` will create a VNET, + - `create_virtual_network` - (`bool`, optional, defaults to `true`) 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. @@ -809,7 +828,7 @@ variable "test_infrastructure" { [VNET module documentation](../../modules/vnet/README.md#route_tables). For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#vnets). - + - `load_balancers` - (`map`, optional) a map containing configuration for all (both private and public) Load Balancers. The most basic properties are as follows: @@ -826,8 +845,8 @@ variable "test_infrastructure" { - `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 refer to [loadbalancer module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) - for available properties. - + for available properties. + Please note that in this example two additional properties are available: - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition in the @@ -858,10 +877,10 @@ variable "test_infrastructure" { For all properties and their default values see [module's documentation](../../modules/test_infrastructure/README.md#test_vms). - + - `bastions` - (`map`, required) a map containing Azure Bastion definitions. The most basic properties are as follows: - + - `name` - (`string`, required) an Azure Bastion name. - `vnet_key` - (`string`, required) a key describing a VNET defined in `vnets` property. This VNET should already have an existing subnet called `AzureBastionSubnet` (the name is hardcoded by Microsoft). diff --git a/examples/common_vmseries_and_autoscale/README.md b/examples/common_vmseries_and_autoscale/README.md index aba250e4..70970be3 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -214,7 +214,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -222,6 +221,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -229,8 +229,6 @@ Name | Type | Description [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -246,22 +244,20 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `natgw` | - | ../../modules/natgw | `load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | @@ -269,7 +265,6 @@ Name | Version | Source | Description `vmss` | - | ../../modules/vmss | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `resource_group` (managed) @@ -280,9 +275,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -299,7 +291,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -373,17 +364,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -420,8 +402,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -432,6 +412,32 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### natgws diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 55f6c6b1..f2280c87 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -125,6 +125,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + # LOAD BALANCING load_balancers = { @@ -228,7 +236,7 @@ scale_sets = { name = "common-vmss" vnet_key = "transit" image = { - version = "10.2.4" + version = "10.2.8" } authentication = { disable_password_authentication = false @@ -307,9 +315,20 @@ test_infrastructure = { "app1" = { name = "app1-nsg" rules = { + from_bastion = { + name = "app1-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.0.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app1-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" @@ -373,9 +392,20 @@ test_infrastructure = { "app2" = { name = "app2-nsg" rules = { + from_bastion = { + name = "app2-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.1.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app2-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" diff --git a/examples/common_vmseries_and_autoscale/main.tf b/examples/common_vmseries_and_autoscale/main.tf index 1b904868..edd80dfd 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -78,6 +78,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + module "natgw" { source = "../../modules/natgw" diff --git a/examples/common_vmseries_and_autoscale/variables.tf b/examples/common_vmseries_and_autoscale/variables.tf index 1e257d73..77023f8b 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/variables.tf @@ -114,6 +114,25 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + variable "natgws" { description = <<-EOF A map defining NAT Gateways. diff --git a/examples/dedicated_vmseries/README.md b/examples/dedicated_vmseries/README.md index 3e0c8874..0beeb75e 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -187,7 +187,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -195,6 +194,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -204,8 +204,6 @@ Name | Type | Description [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -224,23 +222,21 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - `local` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `natgw` | - | ../../modules/natgw | `load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | @@ -249,7 +245,6 @@ Name | Version | Source | Description `vmseries` | - | ../../modules/vmseries | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `availability_set` (managed) @@ -262,9 +257,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -281,7 +273,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -355,19 +346,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -404,8 +384,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -416,6 +394,32 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### natgws diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index d46f8176..6b287656 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -111,6 +111,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + # LOAD BALANCING load_balancers = { @@ -178,7 +186,7 @@ vmseries = { name = "inbound-firewall01" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -213,7 +221,7 @@ vmseries = { name = "inbound-firewall02" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -247,7 +255,7 @@ vmseries = { name = "obew-firewall01" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -282,7 +290,7 @@ vmseries = { name = "obew-firewall02" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -328,9 +336,20 @@ test_infrastructure = { "app1" = { name = "app1-nsg" rules = { + from_bastion = { + name = "app1-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.0.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app1-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" @@ -394,9 +413,20 @@ test_infrastructure = { "app2" = { name = "app2-nsg" rules = { + from_bastion = { + name = "app2-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.1.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app2-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" diff --git a/examples/dedicated_vmseries/main.tf b/examples/dedicated_vmseries/main.tf index 879d90cf..08fc21d2 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -75,6 +75,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + module "natgw" { source = "../../modules/natgw" @@ -354,14 +373,16 @@ module "vmseries" { bootstrap_options = try( coalesce( each.value.virtual_machine.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), + try( + join(",", [ + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + null), ), null ) diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index b0184dac..de1f8084 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/variables.tf @@ -114,6 +114,25 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + variable "natgws" { description = <<-EOF A map defining NAT Gateways. diff --git a/examples/dedicated_vmseries_and_autoscale/README.md b/examples/dedicated_vmseries_and_autoscale/README.md index 9f5b128a..a6451ee5 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -208,7 +208,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -216,6 +215,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -223,8 +223,6 @@ Name | Type | Description [`scale_sets`](#scale_sets) | `map` | A map defining Azure Virtual Machine Scale Sets based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -240,22 +238,20 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `natgw` | - | ../../modules/natgw | `load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | @@ -263,7 +259,6 @@ Name | Version | Source | Description `vmss` | - | ../../modules/vmss | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `resource_group` (managed) @@ -274,9 +269,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -293,7 +285,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -367,17 +358,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -414,8 +396,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -426,6 +406,32 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### natgws diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 814b59b1..c9edd419 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -111,6 +111,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + natgws = { "natgw" = { name = "public-natgw" @@ -182,7 +190,7 @@ scale_sets = { name = "inbound-vmss" vnet_key = "transit" image = { - version = "10.2.4" + version = "10.2.8" } authentication = { disable_password_authentication = false @@ -214,7 +222,7 @@ scale_sets = { name = "obew-vmss" vnet_key = "transit" image = { - version = "10.2.4" + version = "10.2.8" } authentication = { disable_password_authentication = false @@ -257,9 +265,20 @@ test_infrastructure = { "app1" = { name = "app1-nsg" rules = { + from_bastion = { + name = "app1-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.0.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app1-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" @@ -323,9 +342,20 @@ test_infrastructure = { "app2" = { name = "app2-nsg" rules = { + from_bastion = { + name = "app2-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.1.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } web_inbound = { name = "app2-web-allow-inbound" - priority = 100 + priority = 110 direction = "Inbound" access = "Allow" protocol = "Tcp" diff --git a/examples/dedicated_vmseries_and_autoscale/main.tf b/examples/dedicated_vmseries_and_autoscale/main.tf index affff1f2..f7b43887 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -78,6 +78,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + module "natgw" { source = "../../modules/natgw" diff --git a/examples/dedicated_vmseries_and_autoscale/variables.tf b/examples/dedicated_vmseries_and_autoscale/variables.tf index 1e257d73..77023f8b 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/variables.tf @@ -114,6 +114,25 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + variable "natgws" { description = <<-EOF A map defining NAT Gateways. diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 8d990872..ca6c7263 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -130,7 +130,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -138,6 +137,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`gateway_load_balancers`](#gateway_load_balancers) | `map` | A map with Gateway Load Balancer (GWLB) definitions. [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. @@ -145,8 +145,6 @@ Name | Type | Description [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -160,30 +158,27 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - `local` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `gwlb` | - | ../../modules/gwlb | `ngfw_metrics` | - | ../../modules/ngfw_metrics | `bootstrap` | - | ../../modules/bootstrap | `vmseries` | - | ../../modules/vmseries | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `availability_set` (managed) @@ -196,9 +191,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -215,7 +207,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -289,17 +280,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -336,8 +318,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -348,6 +328,32 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### gateway_load_balancers diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index c11d530a..51ddbc62 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -75,6 +75,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + # LOAD BALANCING gateway_load_balancers = { @@ -136,7 +144,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -166,7 +174,7 @@ vmseries = { name = "firewall02" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_DS3_v2" @@ -206,6 +214,19 @@ test_infrastructure = { network_security_groups = { "app1" = { name = "app1-nsg" + rules = { + from_bastion = { + name = "app1-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.0.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } + } } } subnets = { @@ -284,6 +305,19 @@ test_infrastructure = { network_security_groups = { "app2" = { name = "app2-nsg" + rules = { + from_bastion = { + name = "app2-mgmt-allow-bastion" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefix = "10.100.1.64/26" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "*" + } + } } } subnets = { diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf index 17944a9a..1cf9af08 100644 --- a/examples/gwlb_with_vmseries/main.tf +++ b/examples/gwlb_with_vmseries/main.tf @@ -75,6 +75,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + # Create Gateway Load Balancers module "gwlb" { @@ -236,14 +255,16 @@ module "vmseries" { bootstrap_options = try( coalesce( each.value.virtual_machine.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), + try( + join(",", [ + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + null), ), null ) diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf index e557ac8a..a6113515 100644 --- a/examples/gwlb_with_vmseries/variables.tf +++ b/examples/gwlb_with_vmseries/variables.tf @@ -114,6 +114,25 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + # LOAD BALANCING variable "gateway_load_balancers" { diff --git a/examples/standalone_panorama/README.md b/examples/standalone_panorama/README.md index 0df5fff6..b0aa8c58 100644 --- a/examples/standalone_panorama/README.md +++ b/examples/standalone_panorama/README.md @@ -128,7 +128,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -139,8 +138,6 @@ Name | Type | Description [`availability_sets`](#availability_sets) | `map` | A map defining availability sets. [`panoramas`](#panoramas) | `map` | A map defining Azure Virtual Machine based on Palo Alto Networks Panorama image. - - ## Module's Outputs Name | Description @@ -151,25 +148,21 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | `panorama` | - | ../../modules/panorama | - Resources used in this module: - `availability_set` (managed) @@ -181,9 +174,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -200,7 +190,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -274,13 +263,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -317,8 +301,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -329,7 +311,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### availability_sets A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. diff --git a/examples/standalone_vmseries/README.md b/examples/standalone_vmseries/README.md index d1d742a6..58241413 100644 --- a/examples/standalone_vmseries/README.md +++ b/examples/standalone_vmseries/README.md @@ -121,7 +121,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -129,6 +128,7 @@ Name | Type | Description [`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. [`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`vnet_peerings`](#vnet_peerings) | `map` | A map defining VNET peerings. [`natgws`](#natgws) | `map` | A map defining NAT Gateways. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. [`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. @@ -138,8 +138,6 @@ Name | Type | Description [`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. [`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. - - ## Module's Outputs Name | Description @@ -155,23 +153,21 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `random` - `azurerm` - `local` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | +`vnet_peering` | - | ../../modules/vnet_peering | `natgw` | - | ../../modules/natgw | `load_balancer` | - | ../../modules/loadbalancer | `appgw` | - | ../../modules/appgw | @@ -180,7 +176,6 @@ Name | Version | Source | Description `vmseries` | - | ../../modules/vmseries | `test_infrastructure` | - | ../../modules/test_infrastructure | - Resources used in this module: - `availability_set` (managed) @@ -193,9 +188,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -212,7 +204,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -286,19 +277,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -335,8 +315,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -347,6 +325,32 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> +#### vnet_peerings + +A map defining VNET peerings. + +Following properties are supported: +- `local_vnet_name` - (`string`, required) name of the local VNET. +- `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. +- `remote_vnet_name` - (`string`, required) name of the remote VNET. +- `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + + +Type: + +```hcl +map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +``` + + +Default value: `map[]` + +<sup>[back to list](#modules-optional-inputs)</sup> #### natgws diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index c4f0ddb9..991b4190 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -42,6 +42,14 @@ vnets = { } } +vnet_peerings = { + # "vmseries-to-panorama" = { + # local_vnet_name = "example-transit" + # remote_vnet_name = "example-panorama-vnet" + # remote_resource_group_name = "example-panorama" + # } +} + # VM-SERIES vmseries = { @@ -49,7 +57,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { bootstrap_options = "type=dhcp-client" diff --git a/examples/standalone_vmseries/main.tf b/examples/standalone_vmseries/main.tf index f2b30167..80c436fe 100644 --- a/examples/standalone_vmseries/main.tf +++ b/examples/standalone_vmseries/main.tf @@ -75,6 +75,25 @@ module "vnet" { tags = var.tags } +module "vnet_peering" { + source = "../../modules/vnet_peering" + + for_each = var.vnet_peerings + + local_peer_config = { + name = "peer-${each.value.local_vnet_name}-to-${each.value.remote_vnet_name}" + resource_group_name = coalesce(each.value.local_resource_group_name, local.resource_group.name) + vnet_name = each.value.local_vnet_name + } + remote_peer_config = { + name = "peer-${each.value.remote_vnet_name}-to-${each.value.local_vnet_name}" + resource_group_name = coalesce(each.value.remote_resource_group_name, local.resource_group.name) + vnet_name = each.value.remote_vnet_name + } + + depends_on = [module.vnet] +} + module "natgw" { source = "../../modules/natgw" @@ -354,14 +373,16 @@ module "vmseries" { bootstrap_options = try( coalesce( each.value.virtual_machine.bootstrap_options, - join(",", [ - "storage-account=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", - "access-key=${module.bootstrap[ - each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", - "file-share=${each.key}", - "share-directory=None" - ]), + try( + join(",", [ + "storage-account=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[ + each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + null), ), null ) diff --git a/examples/standalone_vmseries/variables.tf b/examples/standalone_vmseries/variables.tf index b0184dac..de1f8084 100644 --- a/examples/standalone_vmseries/variables.tf +++ b/examples/standalone_vmseries/variables.tf @@ -114,6 +114,25 @@ variable "vnets" { })) } +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. + + Following properties are supported: + - `local_vnet_name` - (`string`, required) name of the local VNET. + - `local_resource_group_name` - (`string`, optional) name of the resource group, in which local VNET exists. + - `remote_vnet_name` - (`string`, required) name of the remote VNET. + - `remote_resource_group_name` - (`string`, optional) name of the resource group, in which remote VNET exists. + EOF + default = {} + type = map(object({ + local_vnet_name = string + local_resource_group_name = optional(string) + remote_vnet_name = string + remote_resource_group_name = optional(string) + })) +} + variable "natgws" { description = <<-EOF A map defining NAT Gateways. diff --git a/examples/virtual_network_gateway/README.md b/examples/virtual_network_gateway/README.md index 6dda627c..d88e770a 100644 --- a/examples/virtual_network_gateway/README.md +++ b/examples/virtual_network_gateway/README.md @@ -13,7 +13,6 @@ Name | Type | Description [`region`](#region) | `string` | The Azure region to use. [`vnets`](#vnets) | `map` | A map defining VNETs. - ## Module's Optional Inputs Name | Type | Description @@ -23,8 +22,6 @@ Name | Type | Description [`tags`](#tags) | `map` | Map of tags to assign to the created resources. [`virtual_network_gateways`](#virtual_network_gateways) | `map` | Map of Virtual Network Gateways to create. - - ## Module's Outputs Name | Description @@ -34,24 +31,20 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - Providers used in this module: - `azurerm` - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `vnet` | - | ../../modules/vnet | `vng` | - | ../../modules/virtual_network_gateway | - Resources used in this module: - `resource_group` (managed) @@ -61,9 +54,6 @@ Resources used in this module: ### Required Inputs - - - #### resource_group_name Name of the Resource Group. @@ -80,7 +70,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -154,12 +143,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - ### Optional Inputs - #### name_prefix A prefix that will be added to all created resources. @@ -196,8 +181,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags Map of tags to assign to the created resources. @@ -208,7 +191,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### virtual_network_gateways Map of Virtual Network Gateways to create. diff --git a/modules/appgw/README.md b/modules/appgw/README.md index 2c3cb8e1..77862047 100644 --- a/modules/appgw/README.md +++ b/modules/appgw/README.md @@ -823,7 +823,6 @@ Name | Type | Description [`listeners`](#listeners) | `map` | A map of listeners for the Application Gateway. [`rules`](#rules) | `map` | A map of rules for the Application Gateway. - ## Module's Optional Inputs Name | Type | Description @@ -844,8 +843,6 @@ Name | Type | Description [`redirects`](#redirects) | `map` | A map of redirects for the Application Gateway. [`url_path_maps`](#url_path_maps) | `map` | A map of URL path maps for the Application Gateway. - - ## Module's Outputs Name | Description @@ -856,20 +853,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `application_gateway` (managed) @@ -880,7 +874,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Application Gateway. @@ -905,7 +898,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### subnet_id An ID of a subnet (must be dedicated to Application Gateway v2) that will host the Application Gateway. @@ -914,7 +906,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### public_ip A map defining listener's public IP configuration. @@ -939,13 +930,6 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - #### frontend_ip_configuration_name A frontend IP configuration name. @@ -995,12 +979,6 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - #### rules A map of rules for the Application Gateway. A rule combines backend's, listener's, rewrites' and redirects' configurations. @@ -1040,14 +1018,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -1058,7 +1030,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### zones A list of zones the Application Gateway should be available in. For non-zonal deployments this should be set to an empty list, @@ -1082,7 +1053,6 @@ Default value: `[1 2 3]` <sup>[back to list](#modules-optional-inputs)</sup> - #### domain_name_label A label for the Domain Name. Will be used to make up the FQDN. @@ -1248,8 +1218,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - #### backend_pool A map defining a backend pool, when skipped will create an empty backend. @@ -1484,5 +1452,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/bootstrap/README.md b/modules/bootstrap/README.md index 507e7f3b..ae8032dd 100644 --- a/modules/bootstrap/README.md +++ b/modules/bootstrap/README.md @@ -135,7 +135,6 @@ Name | Type | Description [`name`](#name) | `string` | Name of the Storage Account. [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. - ## Module's Optional Inputs Name | Type | Description @@ -147,8 +146,6 @@ Name | Type | Description [`file_shares_configuration`](#file_shares_configuration) | `object` | A map defining common File Share setting. [`file_shares`](#file_shares) | `map` | Definition of File Shares. - - ## Module's Outputs Name | Description @@ -159,20 +156,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `storage_account` (managed) @@ -187,7 +181,6 @@ Resources used in this module: ### Required Inputs - #### name Name of the Storage Account. @@ -209,19 +202,8 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - ### Optional Inputs - - - #### region The name of the Azure region to deploy the resources in. diff --git a/modules/gwlb/README.md b/modules/gwlb/README.md index 1408307e..8c5c4d45 100644 --- a/modules/gwlb/README.md +++ b/modules/gwlb/README.md @@ -90,7 +90,6 @@ Name | Type | Description [`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`frontend_ip`](#frontend_ip) | `object` | Frontend IP configuration of the Gateway Load Balancer. - ## Module's Optional Inputs Name | Type | Description @@ -101,8 +100,6 @@ Name | Type | Description [`backends`](#backends) | `map` | Map with backend configurations for the Gateway Load Balancer. [`lb_rule`](#lb_rule) | `object` | Load balancing rule configuration. - - ## Module's Outputs Name | Description @@ -112,20 +109,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `lb` (managed) @@ -137,7 +131,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Load Balancer. @@ -162,8 +155,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - #### frontend_ip Frontend IP configuration of the Gateway Load Balancer. @@ -190,17 +181,8 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -225,7 +207,6 @@ Default value: `[1 2 3]` <sup>[back to list](#modules-optional-inputs)</sup> - #### health_probe Health probe configuration for the Gateway Load Balancer backends. diff --git a/modules/loadbalancer/README.md b/modules/loadbalancer/README.md index ec521cbd..5da20729 100644 --- a/modules/loadbalancer/README.md +++ b/modules/loadbalancer/README.md @@ -93,7 +93,6 @@ Name | Type | Description [`backend_name`](#backend_name) | `string` | The name of the backend pool to create. [`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 @@ -103,8 +102,6 @@ Name | Type | Description [`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. - - ## Module's Outputs Name | Description @@ -117,20 +114,17 @@ private IP address otherwise. ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `lb` (managed) @@ -146,7 +140,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Load Balancer. @@ -171,8 +164,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - #### backend_name The name of the backend pool to create. All frontends of the Load Balancer always use the same backend. @@ -341,16 +332,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -384,8 +367,6 @@ Default value: `[1 2 3]` <sup>[back to list](#modules-optional-inputs)</sup> - - #### health_probes Backend's health probe definition. diff --git a/modules/name_templater/README.md b/modules/name_templater/README.md index ce4ea094..7bc032a5 100644 --- a/modules/name_templater/README.md +++ b/modules/name_templater/README.md @@ -59,15 +59,12 @@ Name | Type | Description [`name_prefix`](#name_prefix) | `string` | Prefix used in names for the resources. [`name_template`](#name_template) | `object` | A name template definition. - ## Module's Optional Inputs Name | Type | Description --- | --- | --- [`abbreviations`](#abbreviations) | `map` | Map of abbreviations used for resources (placed in place of "__default__"). - - ## Module's Outputs Name | Description @@ -76,20 +73,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `random`, version: ~> 3.5 - Providers used in this module: - `random`, version: ~> 3.5 - Resources used in this module: - `pet` (managed) @@ -98,7 +92,6 @@ Resources used in this module: ### Required Inputs - #### resource_type A type of resource for which the name template will be created. This should follow the abbreviations resource naming standard. @@ -174,15 +167,8 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - - - ### Optional Inputs - - - - #### abbreviations Map of abbreviations used for resources (placed in place of "__default__"). diff --git a/modules/natgw/README.md b/modules/natgw/README.md index 2ab2f2a4..7d19e6b7 100644 --- a/modules/natgw/README.md +++ b/modules/natgw/README.md @@ -48,7 +48,6 @@ Name | Type | Description [`region`](#region) | `string` | Azure region. [`subnet_ids`](#subnet_ids) | `map` | A map of subnet IDs what will be bound with this NAT Gateway. - ## Module's Optional Inputs Name | Type | Description @@ -60,8 +59,6 @@ Name | Type | Description [`public_ip`](#public_ip) | `object` | A map defining a Public IP resource. [`public_ip_prefix`](#public_ip_prefix) | `object` | A map defining a Public IP Prefix resource. - - ## Module's Outputs Name | Description @@ -71,20 +68,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `nat_gateway` (managed) @@ -101,7 +95,6 @@ Resources used in this module: ### Required Inputs - #### name Name of a NAT Gateway. @@ -126,7 +119,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### subnet_ids A map of subnet IDs what will be bound with this NAT Gateway. @@ -138,19 +130,8 @@ Type: map(string) <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - ### Optional Inputs - - - - #### tags A map of tags that will be assigned to resources created by this module. Only for newly created resources. @@ -161,7 +142,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### create_natgw Triggers creation of a NAT Gateway when set to `true`. diff --git a/modules/ngfw_metrics/README.md b/modules/ngfw_metrics/README.md index e2e0287a..3c956e04 100644 --- a/modules/ngfw_metrics/README.md +++ b/modules/ngfw_metrics/README.md @@ -64,7 +64,6 @@ Name | Type | Description [`region`](#region) | `string` | The name of the Azure region to deploy the resources in. [`application_insights`](#application_insights) | `map` | A map defining Application Insights instances. - ## Module's Optional Inputs Name | Type | Description @@ -73,8 +72,6 @@ Name | Type | Description [`create_workspace`](#create_workspace) | `bool` | Controls creation or sourcing of a Log Analytics Workspace. [`log_analytics_workspace`](#log_analytics_workspace) | `object` | Configuration of the log analytics workspace. - - ## Module's Outputs Name | Description @@ -84,20 +81,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `application_insights` (managed) @@ -108,7 +102,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Log Analytics Workspace. @@ -133,9 +126,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - - #### application_insights A map defining Application Insights instances. @@ -166,14 +156,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -223,5 +207,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/panorama/README.md b/modules/panorama/README.md index 318ac310..0d402b92 100644 --- a/modules/panorama/README.md +++ b/modules/panorama/README.md @@ -30,7 +30,6 @@ Name | Type | Description [`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. [`interfaces`](#interfaces) | `list` | List of the network interface specifications. - ## Module's Optional Inputs Name | Type | Description @@ -38,8 +37,6 @@ Name | Type | Description [`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`logging_disks`](#logging_disks) | `map` | A map of objects describing the additional disks configuration. - - ## Module's Outputs Name | Description @@ -50,20 +47,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `linux_virtual_machine` (managed) @@ -77,7 +71,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Virtual Machine. @@ -102,7 +95,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### authentication A map defining authentication settings (including username and password). @@ -299,15 +291,8 @@ list(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -318,10 +303,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - - - #### logging_disks A map of objects describing the additional disks configuration. diff --git a/modules/virtual_network_gateway/README.md b/modules/virtual_network_gateway/README.md index b5a513c3..6e04933f 100644 --- a/modules/virtual_network_gateway/README.md +++ b/modules/virtual_network_gateway/README.md @@ -324,7 +324,6 @@ Name | Type | Description [`instance_settings`](#instance_settings) | `object` | A map containing the basic Virtual Network Gateway instance settings. [`ip_configurations`](#ip_configurations) | `object` | A map defining the Public IPs used by the Virtual Network Gateway. - ## Module's Optional Inputs Name | Type | Description @@ -339,8 +338,6 @@ Name | Type | Description [`local_network_gateways`](#local_network_gateways) | `map` | Map of local network gateways and their connections. [`vpn_clients`](#vpn_clients) | `map` | VPN client configurations (IPSec point-to-site connections). - - ## Module's Outputs Name | Description @@ -350,20 +347,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `local_network_gateway` (managed) @@ -376,7 +370,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Virtual Network Gateway. @@ -401,7 +394,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### subnet_id An ID of a Subnet in which the Virtual Network Gateway will be created. @@ -413,8 +405,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - #### instance_settings A map containing the basic Virtual Network Gateway instance settings. @@ -527,20 +517,8 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -551,7 +529,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### zones After provider version 3.x you need to specify in which availability zone(s) you want to place a Public IP address. @@ -576,8 +553,6 @@ Default value: `&{}` <sup>[back to list](#modules-optional-inputs)</sup> - - #### private_ip_address_enabled Controls whether the private IP is enabled on the Virtual Netowkr Gateway. diff --git a/modules/vmseries/README.md b/modules/vmseries/README.md index 483c32ab..7d770f66 100644 --- a/modules/vmseries/README.md +++ b/modules/vmseries/README.md @@ -44,15 +44,12 @@ Name | Type | Description [`virtual_machine`](#virtual_machine) | `object` | Firewall parameters configuration. [`interfaces`](#interfaces) | `list` | List of the network interface specifications. - ## Module's Optional Inputs Name | Type | Description --- | --- | --- [`tags`](#tags) | `map` | The map of tags to assign to all created resources. - - ## Module's Outputs Name | Description @@ -65,20 +62,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `linux_virtual_machine` (managed) @@ -91,7 +85,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Virtual Machine. @@ -116,7 +109,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### authentication A map defining authentication settings (including username and password). @@ -336,14 +328,8 @@ list(object({ <sup>[back to list](#modules-required-inputs)</sup> - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -354,8 +340,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - - - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vmss/README.md b/modules/vmss/README.md index 1f853021..3162974c 100644 --- a/modules/vmss/README.md +++ b/modules/vmss/README.md @@ -110,7 +110,6 @@ Name | Type | Description [`image`](#image) | `object` | Basic Azure VM configuration. [`interfaces`](#interfaces) | `list` | List of the network interfaces specifications. - ## Module's Optional Inputs Name | Type | Description @@ -120,8 +119,6 @@ Name | Type | Description [`autoscaling_configuration`](#autoscaling_configuration) | `object` | Autoscaling configuration common to all policies. [`autoscaling_profiles`](#autoscaling_profiles) | `list` | A list defining autoscaling profiles. - - ## Module's Outputs Name | Description @@ -132,24 +129,20 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Modules used in this module: Name | Version | Source | Description --- | --- | --- | --- `ptd_time` | - | ./dt_string_converter | - Resources used in this module: - `linux_virtual_machine_scale_set` (managed) @@ -159,7 +152,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Virtual Machine Scale Set. @@ -184,7 +176,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### authentication A map defining authentication settings (including username and password). @@ -258,7 +249,6 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - #### interfaces List of the network interfaces specifications. @@ -321,16 +311,8 @@ list(object({ <sup>[back to list](#modules-required-inputs)</sup> - - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. @@ -341,8 +323,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - #### virtual_machine_scale_set Scale set parameters configuration. @@ -422,7 +402,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### autoscaling_configuration Autoscaling configuration common to all policies. diff --git a/modules/vmss/dt_string_converter/README.md b/modules/vmss/dt_string_converter/README.md index 1d77b27e..cf118578 100644 --- a/modules/vmss/dt_string_converter/README.md +++ b/modules/vmss/dt_string_converter/README.md @@ -15,7 +15,6 @@ Name | Type | Description - ## Module's Outputs Name | Description @@ -36,7 +35,6 @@ Name | Description ### Required Inputs - #### time The time value in minutes to be converted to string representation. @@ -45,5 +43,4 @@ Type: number <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file diff --git a/modules/vnet/README.md b/modules/vnet/README.md index 2fdf8504..366c2437 100644 --- a/modules/vnet/README.md +++ b/modules/vnet/README.md @@ -130,7 +130,6 @@ Name | Type | Description [`resource_group_name`](#resource_group_name) | `string` | The name of the Resource Group to use. [`region`](#region) | `string` | The name of the Azure region to deploy the resources in. - ## Module's Optional Inputs Name | Type | Description @@ -143,8 +142,6 @@ Name | Type | Description [`create_subnets`](#create_subnets) | `bool` | Controls subnet creation. [`subnets`](#subnets) | `map` | Map of objects describing subnets to manage. - - ## Module's Outputs Name | Description @@ -158,20 +155,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `network_security_group` (managed) @@ -189,7 +183,6 @@ Resources used in this module: ### Required Inputs - #### name The name of the Azure Virtual Network. @@ -214,21 +207,8 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - - - - - - - - - ### Optional Inputs - - - - #### tags The map of tags to assign to all created resources. diff --git a/modules/vnet_peering/README.md b/modules/vnet_peering/README.md index b27cf9ae..6dee9cad 100644 --- a/modules/vnet_peering/README.md +++ b/modules/vnet_peering/README.md @@ -30,7 +30,6 @@ Name | Type | Description - ## Module's Outputs Name | Description @@ -42,20 +41,17 @@ Name | Description ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 - `azurerm`, version: ~> 3.98 - Providers used in this module: - `azurerm`, version: ~> 3.98 - Resources used in this module: - `virtual_network_peering` (managed) @@ -67,7 +63,6 @@ Resources used in this module: ### Required Inputs - #### local_peer_config A map that contains the local peer configuration. @@ -136,5 +131,4 @@ object({ <sup>[back to list](#modules-required-inputs)</sup> - <!-- END_TF_DOCS --> \ No newline at end of file From 17bb2dd391e2cdbfee15d70755735d3af8408d43 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 15 Apr 2024 08:32:41 +0200 Subject: [PATCH 37/49] Add modules/vmss/dt_string_converter/main_test.go --- modules/vmss/dt_string_converter/main_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 modules/vmss/dt_string_converter/main_test.go diff --git a/modules/vmss/dt_string_converter/main_test.go b/modules/vmss/dt_string_converter/main_test.go new file mode 100644 index 00000000..53abe7ea --- /dev/null +++ b/modules/vmss/dt_string_converter/main_test.go @@ -0,0 +1,11 @@ +package vmss + +import ( + "testing" + + "github.com/PaloAltoNetworks/terraform-modules-swfw-tests-skeleton/pkg/testskeleton" +) + +func TestValidate(t *testing.T) { + testskeleton.ValidateCode(t, nil) +} From e525f31cf4b0e8c5b987e7bd15f21e1468519765 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 15 Apr 2024 08:38:09 +0200 Subject: [PATCH 38/49] Remove Terraform versions 1.2 1.3 1.4 from PR CI workflow --- .github/workflows/pr_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_ci.yml b/.github/workflows/pr_ci.yml index f6fb1fc4..49064dd2 100644 --- a/.github/workflows/pr_ci.yml +++ b/.github/workflows/pr_ci.yml @@ -27,7 +27,7 @@ jobs: if: github.actor != 'dependabot[bot]' with: cloud: azure - tf_version: 1.2 1.3 1.4 1.5 1.6 1.7 + tf_version: 1.5 1.6 1.7 validate_max_parallel: 20 test_max_parallel: 10 terratest_action: Plan # keep in mind that this has to start with capital letter From d26bb8bf12eb13bdc0f099346e1919f3fef1c482 Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 15 Apr 2024 08:45:41 +0200 Subject: [PATCH 39/49] Comment Application Gateways with SSL (they required CA files and certs created) --- examples/appgw/example.tfvars | 892 +++++++++++++++++----------------- 1 file changed, 446 insertions(+), 446 deletions(-) diff --git a/examples/appgw/example.tfvars b/examples/appgw/example.tfvars index 20751f9c..b4928d59 100644 --- a/examples/appgw/example.tfvars +++ b/examples/appgw/example.tfvars @@ -269,450 +269,450 @@ appgws = { # 3. Create PFX file with key and certificate: # openssl pkcs12 -inkey test1.key -in test1.crt -export -out test1.pfx # openssl pkcs12 -inkey test2.key -in test2.crt -export -out test2.pfx - "public-ssl-custom" = { - name = "appgw-ssl-custom" - vnet_key = "transit" - subnet_key = "appgw" - zones = ["1", "2", "3"] - public_ip = { - name = "pip-ssl-custom" - } - global_ssl_policy = { - type = "Custom" - min_protocol_version = "TLSv1_0" - cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", - "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384"] - } - ssl_profiles = { - profile1 = { - name = "appgw-ssl-profile1" - ssl_policy_min_protocol_version = "TLSv1_1" - ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", - "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] - } - profile2 = { - name = "appgw-ssl-profile2" - ssl_policy_min_protocol_version = "TLSv1_2" - ssl_policy_cipher_suites = ["TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384"] - } - } - frontend_ip_configuration_name = "public_ipconfig" - listeners = { - http = { - name = "http-listener" - port = 80 - } - https1 = { - name = "https1-listener" - port = 443 - protocol = "Https" - ssl_profile_name = "appgw-ssl-profile1" - ssl_certificate_path = "./files/test1.pfx" - ssl_certificate_pass = "" - host_names = ["test1.appgw.local"] - } - https2 = { - name = "https2-listener" - port = 443 - protocol = "Https" - ssl_profile_name = "appgw-ssl-profile2" - ssl_certificate_path = "./files/test2.pfx" - ssl_certificate_pass = "" - host_names = ["test2.appgw.local"] - } - redirect_listener = { - name = "redirect-listener-listener" - port = 521 - } - redirect_url = { - name = "redirect-url-listener" - port = 522 - } - path_based_backend = { - name = "path-backend-listener" - port = 641 - } - path_based_redirect_listener = { - name = "path-redirect-listener-listener" - port = 642 - } - path_based_redirect_url = { - name = "path-redirect-rul-listener" - port = 643 - } - } - backend_pool = { - name = "vmseries-pool" - } - backend_settings = { - http = { - name = "http-settings" - port = 80 - protocol = "Http" - timeout = 60 - use_cookie_based_affinity = true - probe = "http" - } - https1 = { - name = "https1-settings" - port = 481 - protocol = "Https" - timeout = 60 - use_cookie_based_affinity = true - hostname_from_backend = false - hostname = "test1.appgw.local" - root_certs = { - test = { - name = "https-application-test1" - path = "./files/ca-cert1.pem" - } - } - probe = "https1" - } - https2 = { - name = "https2-settings" - port = 482 - protocol = "Https" - timeout = 60 - use_cookie_based_affinity = true - hostname_from_backend = false - hostname = "test2.appgw.local" - root_certs = { - test = { - name = "https-application-test2" - path = "./files/ca-cert2.pem" - } - } - probe = "https2" - } - } - probes = { - http = { - name = "http-probe" - path = "/" - protocol = "Http" - timeout = 10 - host = "127.0.0.1" - } - https1 = { - name = "https-probe1" - path = "/" - protocol = "Https" - timeout = 10 - } - https2 = { - name = "https-probe2" - path = "/" - protocol = "Https" - timeout = 10 - } - } - rewrites = { - http = { - name = "http-set" - rules = { - "xff-strip-port" = { - name = "http-xff-strip-port" - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } - https1 = { - name = "https1-set" - rules = { - "xff-strip-port" = { - name = "https1-xff-strip-port" - sequence = 100 - conditions = { - "http_resp_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } - } - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - "X-Forwarded-Proto" = "https" - } - } - } - } - https2 = { - name = "https2-set" - rules = { - "xff-strip-port" = { - name = "https2-xff-strip-port" - sequence = 100 - conditions = { - "http_resp_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } - } - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - "X-Forwarded-Proto" = "https" - } - } - } - } - } - redirects = { - redirect_listener = { - name = "listener-redirect" - type = "Permanent" - target_listener_key = "http" - include_path = true - include_query_string = true - } - redirect_url = { - name = "url-redirect" - type = "Temporary" - target_url = "http://example.com" - include_path = true - include_query_string = true - } - } - url_path_maps = { - path_based_backend = { - name = "backend-map" - backend_key = "http" - path_rules = { - http = { - paths = ["/plaintext"] - backend_key = "http" - } - https = { - paths = ["/secure"] - backend_key = "https1" - } - } - } - path_based_redirect_listener = { - name = "redirect-listener-map" - backend_key = "http" - path_rules = { - http = { - paths = ["/redirect"] - redirect_key = "redirect_listener" - } - } - } - path_based_redirect_url = { - name = "redirect-url-map" - backend_key = "http" - path_rules = { - http = { - paths = ["/redirect"] - redirect_key = "redirect_url" - } - } - } - } - rules = { - http = { - name = "http-rule" - priority = 1 - backend_key = "http" - listener_key = "http" - rewrite_key = "http" - } - https1 = { - name = "https1-rule" - priority = 2 - backend_key = "https1" - listener_key = "https1" - rewrite_key = "https1" - } - https2 = { - name = "https2-rule" - priority = 3 - backend_key = "https2" - listener_key = "https2" - rewrite_key = "https2" - } - redirect_listener = { - name = "redirect-listener-rule" - priority = 4 - listener_key = "redirect_listener" - redirect_key = "redirect_listener" - } - redirect_url = { - name = "redirect-url-rule" - priority = 5 - listener_key = "redirect_url" - redirect_key = "redirect_url" - } - path_based_backend = { - name = "path-based-backend-rule" - priority = 6 - listener_key = "path_based_backend" - url_path_map_key = "path_based_backend" - } - path_based_redirect_listener = { - name = "path-redirect-listener-rule" - priority = 7 - listener_key = "path_based_redirect_listener" - url_path_map_key = "path_based_redirect_listener" - } - path_based_redirect_url = { - name = "path-redirect-rul-rule" - priority = 8 - listener_key = "path_based_redirect_url" - url_path_map_key = "path_based_redirect_url" - } - } - } - "public-ssl-predefined" = { - name = "appgw-ssl-predefined" - vnet_key = "transit" - subnet_key = "appgw" - public_ip = { - name = "pip-ssl-predefined" - } - global_ssl_policy = { - type = "Predefined" - name = "AppGwSslPolicy20170401" - } - ssl_profiles = { - profile1 = { - name = "appgw-ssl-profile1" - ssl_policy_name = "AppGwSslPolicy20170401S" - } - } - listeners = { - https1 = { - name = "https1-listener" - port = 443 - protocol = "Https" - ssl_profile_name = "appgw-ssl-profile1" - ssl_certificate_path = "./files/test1.pfx" - ssl_certificate_pass = "" - host_names = ["test1.appgw.local"] - } - https2 = { - name = "https2-listener" - port = 443 - protocol = "Https" - ssl_certificate_path = "./files/test2.pfx" - ssl_certificate_pass = "" - host_names = ["test2.appgw.local"] - } - } - backend_pool = { - name = "vmseries-pool-custom" - } - backend_settings = { - https1 = { - name = "https1-settings" - port = 481 - protocol = "Https" - timeout = 60 - use_cookie_based_affinity = true - hostname_from_backend = false - hostname = "test1.appgw.local" - root_certs = { - test = { - name = "https-application-test1" - path = "./files/ca-cert1.pem" - } - } - } - https2 = { - name = "https2-settings" - port = 482 - protocol = "Https" - timeout = 60 - use_cookie_based_affinity = true - hostname_from_backend = false - hostname = "test2.appgw.local" - root_certs = { - test = { - name = "https-application-test2" - path = "./files/ca-cert2.pem" - } - } - } - } - rewrites = { - https1 = { - name = "https1-set" - rules = { - "xff-strip-port" = { - name = "https1-xff-strip-port" - sequence = 100 - conditions = { - "http_resp_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } - } - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - "X-Forwarded-Proto" = "https" - } - } - } - } - https2 = { - name = "https2-set" - rules = { - "xff-strip-port" = { - name = "https2-xff-strip-port" - sequence = 100 - conditions = { - "http_resp_X-Forwarded-Proto" = { - pattern = "https" - ignore_case = true - negate = true - } - } - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - "X-Forwarded-Proto" = "https" - } - } - } - } - } - rules = { - https1 = { - name = "https1-rule" - priority = 2 - backend_key = "https1" - listener_key = "https1" - rewrite_key = "https1" - } - https2 = { - name = "https2-rule" - priority = 3 - backend_key = "https2" - listener_key = "https2" - rewrite_key = "https2" - } - } - } + # "public-ssl-custom" = { + # name = "appgw-ssl-custom" + # vnet_key = "transit" + # subnet_key = "appgw" + # zones = ["1", "2", "3"] + # public_ip = { + # name = "pip-ssl-custom" + # } + # global_ssl_policy = { + # type = "Custom" + # min_protocol_version = "TLSv1_0" + # cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + # "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + # "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + # "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + # "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + # "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + # "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + # "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + # "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA256", + # "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA256", + # "TLS_RSA_WITH_AES_256_GCM_SHA384"] + # } + # ssl_profiles = { + # profile1 = { + # name = "appgw-ssl-profile1" + # ssl_policy_min_protocol_version = "TLSv1_1" + # ssl_policy_cipher_suites = ["TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + # "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", + # "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + # "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + # "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + # "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + # "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + # "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + # "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"] + # } + # profile2 = { + # name = "appgw-ssl-profile2" + # ssl_policy_min_protocol_version = "TLSv1_2" + # ssl_policy_cipher_suites = ["TLS_RSA_WITH_3DES_EDE_CBC_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", + # "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA", + # "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384"] + # } + # } + # frontend_ip_configuration_name = "public_ipconfig" + # listeners = { + # http = { + # name = "http-listener" + # port = 80 + # } + # https1 = { + # name = "https1-listener" + # port = 443 + # protocol = "Https" + # ssl_profile_name = "appgw-ssl-profile1" + # ssl_certificate_path = "./files/test1.pfx" + # ssl_certificate_pass = "" + # host_names = ["test1.appgw.local"] + # } + # https2 = { + # name = "https2-listener" + # port = 443 + # protocol = "Https" + # ssl_profile_name = "appgw-ssl-profile2" + # ssl_certificate_path = "./files/test2.pfx" + # ssl_certificate_pass = "" + # host_names = ["test2.appgw.local"] + # } + # redirect_listener = { + # name = "redirect-listener-listener" + # port = 521 + # } + # redirect_url = { + # name = "redirect-url-listener" + # port = 522 + # } + # path_based_backend = { + # name = "path-backend-listener" + # port = 641 + # } + # path_based_redirect_listener = { + # name = "path-redirect-listener-listener" + # port = 642 + # } + # path_based_redirect_url = { + # name = "path-redirect-rul-listener" + # port = 643 + # } + # } + # backend_pool = { + # name = "vmseries-pool" + # } + # backend_settings = { + # http = { + # name = "http-settings" + # port = 80 + # protocol = "Http" + # timeout = 60 + # use_cookie_based_affinity = true + # probe = "http" + # } + # https1 = { + # name = "https1-settings" + # port = 481 + # protocol = "Https" + # timeout = 60 + # use_cookie_based_affinity = true + # hostname_from_backend = false + # hostname = "test1.appgw.local" + # root_certs = { + # test = { + # name = "https-application-test1" + # path = "./files/ca-cert1.pem" + # } + # } + # probe = "https1" + # } + # https2 = { + # name = "https2-settings" + # port = 482 + # protocol = "Https" + # timeout = 60 + # use_cookie_based_affinity = true + # hostname_from_backend = false + # hostname = "test2.appgw.local" + # root_certs = { + # test = { + # name = "https-application-test2" + # path = "./files/ca-cert2.pem" + # } + # } + # probe = "https2" + # } + # } + # probes = { + # http = { + # name = "http-probe" + # path = "/" + # protocol = "Http" + # timeout = 10 + # host = "127.0.0.1" + # } + # https1 = { + # name = "https-probe1" + # path = "/" + # protocol = "Https" + # timeout = 10 + # } + # https2 = { + # name = "https-probe2" + # path = "/" + # protocol = "Https" + # timeout = 10 + # } + # } + # rewrites = { + # http = { + # name = "http-set" + # rules = { + # "xff-strip-port" = { + # name = "http-xff-strip-port" + # sequence = 100 + # request_headers = { + # "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + # } + # } + # } + # } + # https1 = { + # name = "https1-set" + # rules = { + # "xff-strip-port" = { + # name = "https1-xff-strip-port" + # sequence = 100 + # conditions = { + # "http_resp_X-Forwarded-Proto" = { + # pattern = "https" + # ignore_case = true + # negate = true + # } + # } + # request_headers = { + # "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + # "X-Forwarded-Proto" = "https" + # } + # } + # } + # } + # https2 = { + # name = "https2-set" + # rules = { + # "xff-strip-port" = { + # name = "https2-xff-strip-port" + # sequence = 100 + # conditions = { + # "http_resp_X-Forwarded-Proto" = { + # pattern = "https" + # ignore_case = true + # negate = true + # } + # } + # request_headers = { + # "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + # "X-Forwarded-Proto" = "https" + # } + # } + # } + # } + # } + # redirects = { + # redirect_listener = { + # name = "listener-redirect" + # type = "Permanent" + # target_listener_key = "http" + # include_path = true + # include_query_string = true + # } + # redirect_url = { + # name = "url-redirect" + # type = "Temporary" + # target_url = "http://example.com" + # include_path = true + # include_query_string = true + # } + # } + # url_path_maps = { + # path_based_backend = { + # name = "backend-map" + # backend_key = "http" + # path_rules = { + # http = { + # paths = ["/plaintext"] + # backend_key = "http" + # } + # https = { + # paths = ["/secure"] + # backend_key = "https1" + # } + # } + # } + # path_based_redirect_listener = { + # name = "redirect-listener-map" + # backend_key = "http" + # path_rules = { + # http = { + # paths = ["/redirect"] + # redirect_key = "redirect_listener" + # } + # } + # } + # path_based_redirect_url = { + # name = "redirect-url-map" + # backend_key = "http" + # path_rules = { + # http = { + # paths = ["/redirect"] + # redirect_key = "redirect_url" + # } + # } + # } + # } + # rules = { + # http = { + # name = "http-rule" + # priority = 1 + # backend_key = "http" + # listener_key = "http" + # rewrite_key = "http" + # } + # https1 = { + # name = "https1-rule" + # priority = 2 + # backend_key = "https1" + # listener_key = "https1" + # rewrite_key = "https1" + # } + # https2 = { + # name = "https2-rule" + # priority = 3 + # backend_key = "https2" + # listener_key = "https2" + # rewrite_key = "https2" + # } + # redirect_listener = { + # name = "redirect-listener-rule" + # priority = 4 + # listener_key = "redirect_listener" + # redirect_key = "redirect_listener" + # } + # redirect_url = { + # name = "redirect-url-rule" + # priority = 5 + # listener_key = "redirect_url" + # redirect_key = "redirect_url" + # } + # path_based_backend = { + # name = "path-based-backend-rule" + # priority = 6 + # listener_key = "path_based_backend" + # url_path_map_key = "path_based_backend" + # } + # path_based_redirect_listener = { + # name = "path-redirect-listener-rule" + # priority = 7 + # listener_key = "path_based_redirect_listener" + # url_path_map_key = "path_based_redirect_listener" + # } + # path_based_redirect_url = { + # name = "path-redirect-rul-rule" + # priority = 8 + # listener_key = "path_based_redirect_url" + # url_path_map_key = "path_based_redirect_url" + # } + # } + # } + # "public-ssl-predefined" = { + # name = "appgw-ssl-predefined" + # vnet_key = "transit" + # subnet_key = "appgw" + # public_ip = { + # name = "pip-ssl-predefined" + # } + # global_ssl_policy = { + # type = "Predefined" + # name = "AppGwSslPolicy20170401" + # } + # ssl_profiles = { + # profile1 = { + # name = "appgw-ssl-profile1" + # ssl_policy_name = "AppGwSslPolicy20170401S" + # } + # } + # listeners = { + # https1 = { + # name = "https1-listener" + # port = 443 + # protocol = "Https" + # ssl_profile_name = "appgw-ssl-profile1" + # ssl_certificate_path = "./files/test1.pfx" + # ssl_certificate_pass = "" + # host_names = ["test1.appgw.local"] + # } + # https2 = { + # name = "https2-listener" + # port = 443 + # protocol = "Https" + # ssl_certificate_path = "./files/test2.pfx" + # ssl_certificate_pass = "" + # host_names = ["test2.appgw.local"] + # } + # } + # backend_pool = { + # name = "vmseries-pool-custom" + # } + # backend_settings = { + # https1 = { + # name = "https1-settings" + # port = 481 + # protocol = "Https" + # timeout = 60 + # use_cookie_based_affinity = true + # hostname_from_backend = false + # hostname = "test1.appgw.local" + # root_certs = { + # test = { + # name = "https-application-test1" + # path = "./files/ca-cert1.pem" + # } + # } + # } + # https2 = { + # name = "https2-settings" + # port = 482 + # protocol = "Https" + # timeout = 60 + # use_cookie_based_affinity = true + # hostname_from_backend = false + # hostname = "test2.appgw.local" + # root_certs = { + # test = { + # name = "https-application-test2" + # path = "./files/ca-cert2.pem" + # } + # } + # } + # } + # rewrites = { + # https1 = { + # name = "https1-set" + # rules = { + # "xff-strip-port" = { + # name = "https1-xff-strip-port" + # sequence = 100 + # conditions = { + # "http_resp_X-Forwarded-Proto" = { + # pattern = "https" + # ignore_case = true + # negate = true + # } + # } + # request_headers = { + # "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + # "X-Forwarded-Proto" = "https" + # } + # } + # } + # } + # https2 = { + # name = "https2-set" + # rules = { + # "xff-strip-port" = { + # name = "https2-xff-strip-port" + # sequence = 100 + # conditions = { + # "http_resp_X-Forwarded-Proto" = { + # pattern = "https" + # ignore_case = true + # negate = true + # } + # } + # request_headers = { + # "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + # "X-Forwarded-Proto" = "https" + # } + # } + # } + # } + # } + # rules = { + # https1 = { + # name = "https1-rule" + # priority = 2 + # backend_key = "https1" + # listener_key = "https1" + # rewrite_key = "https1" + # } + # https2 = { + # name = "https2-rule" + # priority = 3 + # backend_key = "https2" + # listener_key = "https2" + # rewrite_key = "https2" + # } + # } + # } } From f81194d8e11d4d5c6db46cb3a3c81576a7401ea9 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Sat, 20 Apr 2024 22:50:48 +0200 Subject: [PATCH 40/49] Bump PAN-OS version to 10.2.9-h1 for VM-Series --- examples/common_vmseries/example.tfvars | 4 ++-- examples/common_vmseries_and_autoscale/example.tfvars | 2 +- examples/dedicated_vmseries/example.tfvars | 8 ++++---- examples/dedicated_vmseries_and_autoscale/example.tfvars | 4 ++-- examples/gwlb_with_vmseries/example.tfvars | 4 ++-- examples/standalone_vmseries/example.tfvars | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 7cc95fbe..f94a547f 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -232,7 +232,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" @@ -262,7 +262,7 @@ vmseries = { "fw-2" = { name = "firewall02" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index f2280c87..9990d7cf 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -236,7 +236,7 @@ scale_sets = { name = "common-vmss" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } authentication = { disable_password_authentication = false diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 6b287656..d3637994 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -186,7 +186,7 @@ vmseries = { name = "inbound-firewall01" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" @@ -221,7 +221,7 @@ vmseries = { name = "inbound-firewall02" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" @@ -255,7 +255,7 @@ vmseries = { name = "obew-firewall01" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" @@ -290,7 +290,7 @@ vmseries = { name = "obew-firewall02" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index c9edd419..2f1e67f8 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -190,7 +190,7 @@ scale_sets = { name = "inbound-vmss" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } authentication = { disable_password_authentication = false @@ -222,7 +222,7 @@ scale_sets = { name = "obew-vmss" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } authentication = { disable_password_authentication = false diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 51ddbc62..26827fdd 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -144,7 +144,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" @@ -174,7 +174,7 @@ vmseries = { name = "firewall02" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { size = "Standard_DS3_v2" diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index 991b4190..f0ef939f 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -57,7 +57,7 @@ vmseries = { name = "firewall01" vnet_key = "transit" image = { - version = "10.2.8" + version = "10.2.901" } virtual_machine = { bootstrap_options = "type=dhcp-client" From 092fb611525e945ac4e3cf709f1b3a83c006ad95 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Sat, 20 Apr 2024 23:03:27 +0200 Subject: [PATCH 41/49] Bump PAN-OS version to 10.2.8 for Panorama --- examples/standalone_panorama/example.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 2f338520..50521294 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -54,7 +54,7 @@ panoramas = { #ssh_keys = ["~/.ssh/id_rsa.pub"] } image = { - version = "10.2.3" + version = "10.2.8" } virtual_machine = { size = "Standard_D5_v2" From 01f8ca9c77ff76496391902846fec2311e1143a2 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 22 Apr 2024 00:55:42 +0200 Subject: [PATCH 42/49] Update config version in the XML templates --- examples/dedicated_vmseries/templates/bootstrap_common.tmpl | 2 +- examples/dedicated_vmseries/templates/bootstrap_inbound.tmpl | 2 +- examples/dedicated_vmseries/templates/bootstrap_obew.tmpl | 2 +- examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/dedicated_vmseries/templates/bootstrap_common.tmpl b/examples/dedicated_vmseries/templates/bootstrap_common.tmpl index e2776fe5..f2195ba6 100644 --- a/examples/dedicated_vmseries/templates/bootstrap_common.tmpl +++ b/examples/dedicated_vmseries/templates/bootstrap_common.tmpl @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> +<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.9"> <mgt-config> <users> <entry name="panadmin"> diff --git a/examples/dedicated_vmseries/templates/bootstrap_inbound.tmpl b/examples/dedicated_vmseries/templates/bootstrap_inbound.tmpl index 84beeb3c..ccd4feb2 100644 --- a/examples/dedicated_vmseries/templates/bootstrap_inbound.tmpl +++ b/examples/dedicated_vmseries/templates/bootstrap_inbound.tmpl @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> +<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.9"> <mgt-config> <users> <entry name="panadmin"> diff --git a/examples/dedicated_vmseries/templates/bootstrap_obew.tmpl b/examples/dedicated_vmseries/templates/bootstrap_obew.tmpl index e95775c1..c9e55f25 100644 --- a/examples/dedicated_vmseries/templates/bootstrap_obew.tmpl +++ b/examples/dedicated_vmseries/templates/bootstrap_obew.tmpl @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> +<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.9"> <mgt-config> <users> <entry name="panadmin"> diff --git a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl index ffaf7ed3..78a6dab8 100644 --- a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl +++ b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.3"> +<config version="10.2.0" urldb="paloaltonetworks" detail-version="10.2.9"> <mgt-config> <users> <entry name="panadmin"> From b37a83bc83e155ddaaad46ef75269ffd19ec1602 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 22 Apr 2024 01:28:19 +0200 Subject: [PATCH 43/49] Fix GWLB example by adding outputs and tuning NSGs --- examples/gwlb_with_vmseries/README.md | 3 ++ examples/gwlb_with_vmseries/example.tfvars | 32 +++++++++++++++------- examples/gwlb_with_vmseries/outputs.tf | 20 ++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index ca6c7263..ed6c9e82 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -154,6 +154,9 @@ Name | Description `metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. `vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. `bootstrap_storage_urls` | +`test_vms_usernames` | Initial administrative username to use for test VMs. +`test_vms_passwords` | Initial administrative password to use for test VMs. +`test_vms_ips` | IP Addresses of the test VMs. `app_lb_frontend_ips` | IP Addresses of the load balancers. ## Module's Nameplate diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 26827fdd..3bb05abd 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -226,6 +226,17 @@ test_infrastructure = { destination_address_prefix = "*" destination_port_range = "*" } + web_inbound = { + name = "app1-web-allow-inbound" + priority = 110 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure + source_port_range = "*" + destination_address_prefix = "10.100.0.0/25" + destination_port_ranges = ["80", "443"] + } } } } @@ -245,11 +256,6 @@ test_infrastructure = { load_balancers = { "app1" = { name = "app1-lb" - nsg_auto_rules_settings = { - nsg_vnet_key = "app1" - nsg_key = "app1" - source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB - } frontend_ips = { "app1" = { name = "app1-frontend" @@ -317,6 +323,17 @@ test_infrastructure = { destination_address_prefix = "*" destination_port_range = "*" } + web_inbound = { + name = "app2-web-allow-inbound" + priority = 110 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access test infrastructure + source_port_range = "*" + destination_address_prefix = "10.100.1.0/25" + destination_port_ranges = ["80", "443"] + } } } } @@ -336,11 +353,6 @@ test_infrastructure = { load_balancers = { "app2" = { name = "app2-lb" - nsg_auto_rules_settings = { - nsg_vnet_key = "app2" - nsg_key = "app2" - source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB - } frontend_ips = { "app2" = { name = "app2-frontend" diff --git a/examples/gwlb_with_vmseries/outputs.tf b/examples/gwlb_with_vmseries/outputs.tf index f448e723..11d4d9d0 100644 --- a/examples/gwlb_with_vmseries/outputs.tf +++ b/examples/gwlb_with_vmseries/outputs.tf @@ -25,6 +25,26 @@ output "bootstrap_storage_urls" { sensitive = true } +output "test_vms_usernames" { + description = "Initial administrative username to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.username + } : null +} + +output "test_vms_passwords" { + description = "Initial administrative password to use for test VMs." + value = length(var.test_infrastructure) > 0 ? { + for k, v in local.test_vm_authentication : k => v.password + } : null + sensitive = true +} + +output "test_vms_ips" { + description = "IP Addresses of the test VMs." + value = length(var.test_infrastructure) > 0 ? { for k, v in module.test_infrastructure : k => v.vm_private_ips } : null +} + output "app_lb_frontend_ips" { description = "IP Addresses of the load balancers." value = length({ for k, v in var.test_infrastructure : k => v if v.load_balancers != null }) > 0 ? { From ac32304abdba441a94caf0f703ef19089c89d078 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 22 Apr 2024 01:38:32 +0200 Subject: [PATCH 44/49] Minor tweaks to Panorama example --- examples/standalone_panorama/example.tfvars | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 50521294..2dab0ac0 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -20,14 +20,14 @@ vnets = { name = "panorama-nsg" rules = { mgmt_inbound = { - name = "vmseries-management-allow-inbound" + name = "panorama-management-allow-inbound" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_address_prefixes = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to manage the appliances source_port_range = "*" - destination_address_prefix = "10.1.0.0/24" + destination_address_prefix = "10.1.0.0/28" destination_port_ranges = ["22", "443"] } } From 79680357f6559affbc1e870cacb3499732c7ecf9 Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 22 Apr 2024 07:57:33 +0200 Subject: [PATCH 45/49] Modify calculated intranet subnet in the XML template --- examples/dedicated_vmseries/example.tfvars | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index d3637994..181b9cf8 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -197,6 +197,7 @@ vmseries = { bootstrap_xml_template = "templates/bootstrap_inbound.tmpl" private_snet_key = "private" public_snet_key = "public" + intranet_cidr = "10.0.0.0/8" } } interfaces = [ @@ -232,6 +233,7 @@ vmseries = { bootstrap_xml_template = "templates/bootstrap_inbound.tmpl" private_snet_key = "private" public_snet_key = "public" + intranet_cidr = "10.0.0.0/8" } } interfaces = [ @@ -266,6 +268,7 @@ vmseries = { bootstrap_xml_template = "templates/bootstrap_obew.tmpl" private_snet_key = "private" public_snet_key = "public" + intranet_cidr = "10.0.0.0/8" } } interfaces = [ @@ -301,6 +304,7 @@ vmseries = { bootstrap_xml_template = "templates/bootstrap_obew.tmpl" private_snet_key = "private" public_snet_key = "public" + intranet_cidr = "10.0.0.0/8" } } interfaces = [ From 3b22e32ee63a427d19c2b9eae55363361e9d7c2a Mon Sep 17 00:00:00 2001 From: Sebastian Czech <sczech@paloaltonetworks.com> Date: Mon, 22 Apr 2024 15:10:32 +0200 Subject: [PATCH 46/49] refactor: Move `appgw` and `virtual_network_gateway` examples to `tests` directory (#42) --- {examples => tests}/appgw/.header.md | 0 {examples => tests}/appgw/README.md | 0 {examples => tests}/appgw/example.tfvars | 0 {examples => tests}/appgw/main.tf | 0 {examples => tests}/appgw/main_test.go | 0 {examples => tests}/appgw/outputs.tf | 0 {examples => tests}/appgw/variables.tf | 0 {examples => tests}/appgw/versions.tf | 0 {examples => tests}/virtual_network_gateway/.header.md | 0 {examples => tests}/virtual_network_gateway/README.md | 0 {examples => tests}/virtual_network_gateway/example.tfvars | 0 {examples => tests}/virtual_network_gateway/main.tf | 0 {examples => tests}/virtual_network_gateway/main_test.go | 0 {examples => tests}/virtual_network_gateway/outputs.tf | 0 {examples => tests}/virtual_network_gateway/variables.tf | 0 {examples => tests}/virtual_network_gateway/versions.tf | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename {examples => tests}/appgw/.header.md (100%) rename {examples => tests}/appgw/README.md (100%) rename {examples => tests}/appgw/example.tfvars (100%) rename {examples => tests}/appgw/main.tf (100%) rename {examples => tests}/appgw/main_test.go (100%) rename {examples => tests}/appgw/outputs.tf (100%) rename {examples => tests}/appgw/variables.tf (100%) rename {examples => tests}/appgw/versions.tf (100%) rename {examples => tests}/virtual_network_gateway/.header.md (100%) rename {examples => tests}/virtual_network_gateway/README.md (100%) rename {examples => tests}/virtual_network_gateway/example.tfvars (100%) rename {examples => tests}/virtual_network_gateway/main.tf (100%) rename {examples => tests}/virtual_network_gateway/main_test.go (100%) rename {examples => tests}/virtual_network_gateway/outputs.tf (100%) rename {examples => tests}/virtual_network_gateway/variables.tf (100%) rename {examples => tests}/virtual_network_gateway/versions.tf (100%) diff --git a/examples/appgw/.header.md b/tests/appgw/.header.md similarity index 100% rename from examples/appgw/.header.md rename to tests/appgw/.header.md diff --git a/examples/appgw/README.md b/tests/appgw/README.md similarity index 100% rename from examples/appgw/README.md rename to tests/appgw/README.md diff --git a/examples/appgw/example.tfvars b/tests/appgw/example.tfvars similarity index 100% rename from examples/appgw/example.tfvars rename to tests/appgw/example.tfvars diff --git a/examples/appgw/main.tf b/tests/appgw/main.tf similarity index 100% rename from examples/appgw/main.tf rename to tests/appgw/main.tf diff --git a/examples/appgw/main_test.go b/tests/appgw/main_test.go similarity index 100% rename from examples/appgw/main_test.go rename to tests/appgw/main_test.go diff --git a/examples/appgw/outputs.tf b/tests/appgw/outputs.tf similarity index 100% rename from examples/appgw/outputs.tf rename to tests/appgw/outputs.tf diff --git a/examples/appgw/variables.tf b/tests/appgw/variables.tf similarity index 100% rename from examples/appgw/variables.tf rename to tests/appgw/variables.tf diff --git a/examples/appgw/versions.tf b/tests/appgw/versions.tf similarity index 100% rename from examples/appgw/versions.tf rename to tests/appgw/versions.tf diff --git a/examples/virtual_network_gateway/.header.md b/tests/virtual_network_gateway/.header.md similarity index 100% rename from examples/virtual_network_gateway/.header.md rename to tests/virtual_network_gateway/.header.md diff --git a/examples/virtual_network_gateway/README.md b/tests/virtual_network_gateway/README.md similarity index 100% rename from examples/virtual_network_gateway/README.md rename to tests/virtual_network_gateway/README.md diff --git a/examples/virtual_network_gateway/example.tfvars b/tests/virtual_network_gateway/example.tfvars similarity index 100% rename from examples/virtual_network_gateway/example.tfvars rename to tests/virtual_network_gateway/example.tfvars diff --git a/examples/virtual_network_gateway/main.tf b/tests/virtual_network_gateway/main.tf similarity index 100% rename from examples/virtual_network_gateway/main.tf rename to tests/virtual_network_gateway/main.tf diff --git a/examples/virtual_network_gateway/main_test.go b/tests/virtual_network_gateway/main_test.go similarity index 100% rename from examples/virtual_network_gateway/main_test.go rename to tests/virtual_network_gateway/main_test.go diff --git a/examples/virtual_network_gateway/outputs.tf b/tests/virtual_network_gateway/outputs.tf similarity index 100% rename from examples/virtual_network_gateway/outputs.tf rename to tests/virtual_network_gateway/outputs.tf diff --git a/examples/virtual_network_gateway/variables.tf b/tests/virtual_network_gateway/variables.tf similarity index 100% rename from examples/virtual_network_gateway/variables.tf rename to tests/virtual_network_gateway/variables.tf diff --git a/examples/virtual_network_gateway/versions.tf b/tests/virtual_network_gateway/versions.tf similarity index 100% rename from examples/virtual_network_gateway/versions.tf rename to tests/virtual_network_gateway/versions.tf From 005369cc975c81f39a938919a275181feccaa42e Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Mon, 22 Apr 2024 15:19:17 +0200 Subject: [PATCH 47/49] Add xdr_exclusion tag to fix idempotency checks --- examples/common_vmseries/example.tfvars | 5 +++-- examples/common_vmseries_and_autoscale/example.tfvars | 5 +++-- examples/dedicated_vmseries/example.tfvars | 5 +++-- examples/dedicated_vmseries_and_autoscale/example.tfvars | 5 +++-- examples/gwlb_with_vmseries/example.tfvars | 5 +++-- examples/standalone_panorama/example.tfvars | 5 +++-- examples/standalone_vmseries/example.tfvars | 5 +++-- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index f94a547f..d96d72d4 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "transit-vnet-common" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/common_vmseries_and_autoscale/example.tfvars b/examples/common_vmseries_and_autoscale/example.tfvars index 9990d7cf..6ec23a8c 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "autoscale-common" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index 181b9cf8..14685e59 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "transit-vnet-dedicated" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/dedicated_vmseries_and_autoscale/example.tfvars b/examples/dedicated_vmseries_and_autoscale/example.tfvars index 2f1e67f8..b39601da 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "autoscale-dedicated" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 3bb05abd..d4b26b29 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "gwlb" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/standalone_panorama/example.tfvars b/examples/standalone_panorama/example.tfvars index 2dab0ac0..535dc52a 100644 --- a/examples/standalone_panorama/example.tfvars +++ b/examples/standalone_panorama/example.tfvars @@ -5,8 +5,9 @@ resource_group_name = "panorama" name_prefix = "example-" create_resource_group = true tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK diff --git a/examples/standalone_vmseries/example.tfvars b/examples/standalone_vmseries/example.tfvars index f0ef939f..5033dc12 100644 --- a/examples/standalone_vmseries/example.tfvars +++ b/examples/standalone_vmseries/example.tfvars @@ -4,8 +4,9 @@ region = "North Europe" resource_group_name = "vmseries-standalone" name_prefix = "example-" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } # NETWORK From 8ef53fccc3b6435394d60871fc5d01363901318b Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <135693994+acelebanski@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:28:54 +0200 Subject: [PATCH 48/49] refactor: Fix README generation for internal submodules (#43) --- .pre-commit-config.yaml | 5 + .terraform-docs-internal.yml | 130 +++++++++++++++++++++++++ modules/test_infrastructure/.README.md | 24 +---- 3 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 .terraform-docs-internal.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c1a9075..62ea1b16 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,6 +4,11 @@ repos: - args: - --args=--config=.terraform-docs.yml id: terraform_docs + - args: + - --args=--config=.terraform-docs-internal.yml + - --hook-config=--path-to-file=.README.md + files: ^modules/test_infrastructure/ + id: terraform_docs - args: - --args=--only=terraform_deprecated_interpolation - --args=--only=terraform_deprecated_index diff --git a/.terraform-docs-internal.yml b/.terraform-docs-internal.yml new file mode 100644 index 00000000..273f7315 --- /dev/null +++ b/.terraform-docs-internal.yml @@ -0,0 +1,130 @@ +formatter: "markdown document" # this is required +version: "" +header-from: ".header.md" + +output: + file: .README.md + mode: replace + +sort: + enabled: false + +settings: + indent: 3 + lockfile: false + +content: |- + {{ .Header }} + + ## Module's Required Inputs + + Name | Type | Description + --- | --- | --- + {{- range .Module.Inputs }} + {{- if .Required }} + [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. + {{- end }} + {{- end }} + + {{ $optional := false -}} + {{ range .Module.Inputs }}{{ if not .Required }}{{ $optional = true -}}{{ end -}}{{ end -}} + + {{ if $optional -}} + ## Module's Optional Inputs + + Name | Type | Description + --- | --- | --- + {{- range .Module.Inputs }} + {{- if not .Required }} + [`{{ .Name }}`](#{{ .Name }}) | `{{ (split "(" .Type.Raw)._0 }}` | {{ (split "." .Description.Raw)._0 }}. + {{- end -}} + {{ end -}} + {{ end }} + + {{ if ne (len .Module.Outputs) 0 -}} + ## Module's Outputs + + Name | Description + --- | --- + {{- range .Module.Outputs }} + `{{ .Name }}` | {{ .Description.Raw }} + {{- end }} + {{- end }} + + ## Module's Nameplate + + {{ if ne (len .Module.Requirements) 0 -}} + Requirements needed by this module: + {{ range .Module.Requirements }} + - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} + {{- end }} + {{- end }} + + {{ if ne (len .Module.Providers) 0 -}} + Providers used in this module: + {{ range .Module.Providers }} + - `{{ .Name }}`{{ if .Version }}, version: {{ .Version }}{{ end }} + {{- end }} + {{- end }} + + {{ if ne (len .Module.ModuleCalls) 0 -}} + Modules used in this module: + Name | Version | Source | Description + --- | --- | --- | --- + {{- range .Module.ModuleCalls }} + `{{ .Name }}` | {{ if .Version }}{{ .Version }}{{ else }}-{{ end }} | {{ .Source }} | {{ .Description }} + {{- end }} + {{- end }} + + {{ if ne (len .Module.Resources) 0 -}} + Resources used in this module: + {{ range .Module.Resources }} + - `{{ .Type }}` ({{ .Mode }}) + {{- end }} + {{- end }} + + ## Inputs/Outpus details + + ### Required Inputs + + {{ range .Module.Inputs -}} + {{ if .Required -}} + #### {{ .Name }} + + {{ .Description }} + + Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} + + ```hcl + {{ .Type }} + ``` + {{ end }} + + <sup>[back to list](#modules-required-inputs)</sup> + + {{ end -}} + {{- end -}} + + {{ if $optional -}} + ### Optional Inputs + + {{ range .Module.Inputs -}} + {{ if not .Required -}} + #### {{ .Name }} + + {{ .Description }} + + Type: {{ if lt (len (split "\n" .Type.Raw)) 2 }}{{ .Type }}{{ else }} + + ```hcl + {{ .Type }} + ``` + {{ end }} + + Default value: `{{ .Default }}` + + <sup>[back to list](#modules-optional-inputs)</sup> + + {{ end }} + {{- end -}} + {{ end -}} \ No newline at end of file diff --git a/modules/test_infrastructure/.README.md b/modules/test_infrastructure/.README.md index aa0c50dc..7646a9a4 100644 --- a/modules/test_infrastructure/.README.md +++ b/modules/test_infrastructure/.README.md @@ -20,7 +20,6 @@ Name | Type | Description [`spoke_vms`](#spoke_vms) | `map` | A map defining spoke VMs for testing. [`bastions`](#bastions) | `map` | A map containing Azure Bastion definition. - ## Module's Optional Inputs Name | Type | Description @@ -29,8 +28,6 @@ Name | Type | Description [`tags`](#tags) | `map` | The map of tags to assign to all created resources. [`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (both private and public) Load Balancers. - - ## Module's Outputs Name | Description @@ -42,17 +39,14 @@ private IP address otherwise. ## Module's Nameplate - Requirements needed by this module: - `terraform`, version: >= 1.5, < 2.0 -- `azurerm`, version: ~> 3.80 - +- `azurerm`, version: ~> 3.98 Providers used in this module: -- `azurerm`, version: ~> 3.80 - +- `azurerm`, version: ~> 3.98 Modules used in this module: Name | Version | Source | Description @@ -61,7 +55,6 @@ Name | Version | Source | Description `vnet_peering` | - | ../vnet_peering | https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/vnet_peering `load_balancer` | - | ../loadbalancer | https://registry.terraform.io/modules/PaloAltoNetworks/swfw-modules/azurerm/latest/submodules/loadbalancer - Resources used in this module: - `bastion_host` (managed) @@ -76,8 +69,6 @@ Resources used in this module: ### Required Inputs - - #### resource_group_name The name of the Resource Group to use. @@ -94,7 +85,6 @@ Type: string <sup>[back to list](#modules-required-inputs)</sup> - #### vnets A map defining VNETs. @@ -171,7 +161,6 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - #### authentication A map defining authentication details for spoke VMs. @@ -278,11 +267,8 @@ map(object({ <sup>[back to list](#modules-required-inputs)</sup> - - ### 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 @@ -296,8 +282,6 @@ Default value: `true` <sup>[back to list](#modules-optional-inputs)</sup> - - #### tags The map of tags to assign to all created resources. @@ -308,7 +292,6 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - #### load_balancers A map containing configuration for all (both private and public) Load Balancers. @@ -408,7 +391,4 @@ Default value: `map[]` <sup>[back to list](#modules-optional-inputs)</sup> - - - <!-- END_TF_DOCS --> \ No newline at end of file From c5b3f2eaeb023ff4696c361787d41a075454a76f Mon Sep 17 00:00:00 2001 From: Adrian Celebanski <acelebanski@paloaltonetworks.com> Date: Thu, 25 Apr 2024 12:26:01 +0200 Subject: [PATCH 49/49] Change hub sync workflow trigger to published --- .github/workflows/hub_sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hub_sync.yml b/.github/workflows/hub_sync.yml index e20c4a4c..d8e16a89 100644 --- a/.github/workflows/hub_sync.yml +++ b/.github/workflows/hub_sync.yml @@ -6,7 +6,7 @@ permissions: on: workflow_dispatch: release: - types: [released] + types: [published] jobs: hub_sync: