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/.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: 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 diff --git a/.gitignore b/.gitignore index e2fcf46c..13cd1eab 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,12 @@ .vscode .idea + +# Ceritifcates +*.pem +*.crt +*.pfx +*.key # Palo auth codes authcodes # Crash log files @@ -49,3 +55,5 @@ terraform.tfvars.json # **/ *bootstrap.xml +bootstrap_package/ +examples/appgw/files/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c88ed742..62ea1b16 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,9 +2,12 @@ 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=--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 @@ -14,14 +17,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/.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 }} + + [back to list](#modules-required-inputs) + + {{ 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 }}` + + [back to list](#modules-optional-inputs) + + {{ end }} + {{- end -}} + {{ end -}} \ No newline at end of file diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 00000000..329b4056 --- /dev/null +++ b/.terraform-docs.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 }} + + [back to list](#modules-required-inputs) + + {{ 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 }}` + + [back to list](#modules-optional-inputs) + + {{ end }} + {{- end -}} + {{ end -}} \ No newline at end of file diff --git a/examples/common_vmseries/.header.md b/examples/common_vmseries/.header.md new file mode 100644 index 00000000..bb6de35f --- /dev/null +++ b/examples/common_vmseries/.header.md @@ -0,0 +1,175 @@ +--- +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-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, + 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://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/9e330a37-3679-419a-8aa3-aa963cb4faf2) + +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 +- _(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 [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`]() +- 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 = + 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 249f801f..b918fb96 100644 --- a/examples/common_vmseries/README.md +++ b/examples/common_vmseries/README.md @@ -1,134 +1,171 @@ + --- -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-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, 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. - -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/8e8da6e0-afba-4bb5-b2c7-a95c7250dab3) +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://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/9e330a37-3679-419a-8aa3-aa963cb4faf2) 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 +- _(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 [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`]() -* 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`]() +- 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 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 = - 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 = + 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 @@ -138,80 +175,1159 @@ To remove the deployed infrastructure run: terraform destroy ``` -## Reference - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| [random](#provider\_random) | n/a | -| [http](#provider\_http) | n/a | -| [azurerm](#provider\_azurerm) | n/a | -| [local](#provider\_local) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| [ai](#module\_ai) | ../../modules/application_insights | n/a | -| [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| [vmseries](#module\_vmseries) | ../../modules/vmseries | n/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 | -|------|-------------|------|---------|:--------:| -| [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| [name\_prefix](#input\_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. | `string` | `""` | no | -| [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`.
When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| [vnets](#input\_vnets) | 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. | `any` | n/a | yes | -| [natgws](#input\_natgws) | 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.

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.

Example:
`
natgws = {
"natgw" = {
name = "public-natgw"
vnet_key = "transit-vnet"
subnet_keys = ["public"]
zone = 1
}
}
| `any` | `{}` | no | -| [load\_balancers](#input\_load\_balancers) | 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"
}
}
}
}
}
| `map` | `{}` | no | -| [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 | -| [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 | -| [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| [availability\_sets](#input\_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. | `any` | `{}` | no | -| [application\_insights](#input\_application\_insights) | 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 [modules documentation](../../modules/application\_insights/README.md)):

- `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' = {
....
}
}

application_insights = {
metrics_retention_in_days = 365
}
| `map(string)` | `null` | no | -| [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.

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). | `any` | `{}` | no | -| [vmseries](#input\_vmseries) | 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)

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"
}
]
}
}
| `any` | n/a | yes | -| [appgws](#input\_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 and the `rules` property 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 | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [username](#output\_username) | Initial administrative username to use for VM-Series. | -| [password](#output\_password) | Initial administrative password to use for VM-Series. | -| [natgw\_public\_ips](#output\_natgw\_public\_ips) | Nat Gateways Public IP resources. | -| [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -| [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for the VMSeries management interface. | -| [bootstrap\_storage\_urls](#output\_bootstrap\_storage\_urls) | n/a | - +## Module's Required Inputs + +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. + +## Module's Optional Inputs + +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. +[`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. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. + +## 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` | +`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 + +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 | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`test_infrastructure` | - | ../../modules/test_infrastructure | + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### region + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `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. +- `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) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + +### Optional Inputs + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. +Name of the newly specified RG is controlled by `resource_group_name`. + +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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: +- `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: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + 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) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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` - (`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: + +```hcl +map(object({ + name = string + update_domain_count = optional(number) + fault_domain_count = optional(number) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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 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: + +```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: `&{}` + +[back to list](#modules-optional-inputs) + +#### 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. + + 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. + + 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 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 +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) + blob_retention = optional(number) + }), {}) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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`. +- `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/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 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 + 1st 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 + vnet_key = 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({ + 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) + 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)) + }) + 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) + application_gateway_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + + \ No newline at end of file diff --git a/examples/common_vmseries/example.tfvars b/examples/common_vmseries/example.tfvars index 5d1371da..d96d72d4 100644 --- a/examples/common_vmseries/example.tfvars +++ b/examples/common_vmseries/example.tfvars @@ -1,14 +1,16 @@ -# --- GENERAL --- # -location = "North Europe" +# GENERAL + +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 -# --- VNET PART --- # vnets = { "transit" = { name = "transit" @@ -17,12 +19,13 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-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_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"] @@ -38,41 +41,58 @@ 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" + } } } "private" = { 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" + } } } "public" = { 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" } @@ -83,20 +103,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" @@ -106,20 +126,32 @@ 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 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 - avzones = ["1", "2", "3"] + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB + } 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 } @@ -128,15 +160,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { - vnet_key = "transit" + name = "private-vmseries" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } @@ -146,86 +179,274 @@ load_balancers = { } } +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 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" + vnet_key = "transit" + image = { + version = "10.2.901" + } + virtual_machine = { + 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" - subnet_key = "public" - load_balancer_key = "public" - create_pip = true + name = "vm01-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + application_gateway_key = "public" } ] } "fw-2" = { - name = "firewall02" - bootstrap_options = "type=dhcp-client" - vnet_key = "transit" - avzone = 2 + name = "firewall02" + image = { + version = "10.2.901" + } + virtual_machine = { + size = "Standard_DS3_v2" + zone = 2 + bootstrap_options = "type=dhcp-client" + } + vnet_key = "transit" 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" - subnet_key = "public" - load_balancer_key = "public" - create_pip = true + name = "vm02-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + application_gateway_key = "public" } ] } } +# TEST INFRASTRUCTURE -# # --- 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 +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } } - rewrite_sets = { - "xff-strip-port" = { - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" + 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 = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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 22042e08..08fc21d2 100644 --- a/examples/common_vmseries/main.tf +++ b/examples/common_vmseries/main.tf @@ -1,6 +1,10 @@ -# Generate a random password. +# Generate a random password + +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { - count = var.vmseries_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 @@ -11,25 +15,30 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) + 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)) + } + ) + } } -# 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 - url = "https://ifconfig.me/ip" -} +# Create or source a Resource Group -# 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.location + 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 @@ -39,156 +48,236 @@ 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" 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) - location = var.location + 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 = 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 = 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" 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) - location = var.location + 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) + region = var.region 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] } +# Create Load Balancers, both internal and external -# 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 + region = var.region resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones + backend_name = each.value.backend_name - 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[each.value.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags 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... + } +} -# create the actual VMSeries VMs and resources -module "ai" { - source = "../../modules/application_insights" +module "appgw" { + source = "../../modules/appgw" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + for_each = var.appgws - name = "${var.name_prefix}${each.key}" + 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 + 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 + +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) + ) + region = var.region + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } - 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) + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } 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 if can(v.bootstrap_storage.template_bootstrap_xml) } + for_each = { + for k, v in var.vmseries : + k => merge(v.virtual_machine, { vnet_key = v.vnet_key }) + 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.ai[try(var.application_insights.name, "${each.value.name}-ai")].metrics_instrumentation_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] ) @@ -199,66 +288,68 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + 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_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 -} + for_each = var.bootstrap_storages -module "bootstrap_share" { - source = "../../modules/bootstrap" + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + region = var.region - 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.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_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] + ] } ) - - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} + 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 - ] } - - +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set 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) + location = var.region + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count tags = var.tags } @@ -268,80 +359,108 @@ module "vmseries" { for_each = var.vmseries - location = var.location + name = "${var.name_prefix}${each.value.name}" + region = var.region 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, + 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 + ) + } ) 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.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 ] } -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] +# Create test infrastructure - 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) +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)) + } + ) + } +} - 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)] +module "test_infrastructure" { + source = "../../modules/test_infrastructure" - rules = each.value.rules + for_each = var.test_infrastructure - 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, {}) + 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_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}" }) + } + 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.vmseries] + depends_on = [module.vnet] } diff --git a/examples/common_vmseries/outputs.tf b/examples/common_vmseries/outputs.tf index d652953d..03613e39 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 } @@ -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 } @@ -29,11 +29,38 @@ 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 } + +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 54f10314..39019f5a 100644 --- a/examples/common_vmseries/variables.tf +++ b/examples/common_vmseries/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: ``` 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,352 +35,983 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool +variable "region" { + 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) - - `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`, 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) + })), {}) + })) +} - - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. - - `network_security_groups` : map of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + 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 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. + 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. + - `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: ``` 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({ + 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) + 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) + })) + })) } +# LOAD BALANCING - -### 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 (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 "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. - -### 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 + 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 + default = {} + nullable = false + 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, "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) + })) + })) } -variable "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = 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 = {} - type = any + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number) + fault_domain_count = optional(number) + })) } -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 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 = 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" { +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. + 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: - Following properties are supported (except for name, all are optional): + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. - - `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. + **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. - 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). + - `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. + 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. + + 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 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 = {} - 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) + blob_retention = optional(number) + }), {}) + 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) - - 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" - } - ] - } - } - ``` + 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). + + **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`, 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/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 + 1st 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 + vnet_key = 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({ + 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) + 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)) + }) + 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) + application_gateway_key = optional(string) + })) + })) + 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 = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF + } + 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 + ]) + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF + } } -# Application Gateway -variable "appgws" { +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { description = <<-EOF - A map defining all Application Gateways in the current deployment. + A map defining test infrastructure including test VMs and Azure Bastion hosts. - 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 details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. 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 + - `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/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 new file mode 100644 index 00000000..5c756293 --- /dev/null +++ b/examples/common_vmseries_and_autoscale/.header.md @@ -0,0 +1,206 @@ +--- +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-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, + 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. + +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/10289e10-6652-423b-94cd-0dc0b35f9997) + +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 +- _(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 +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`]() +- 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 = + password = + 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 bf8dbd29..70970be3 100644 --- a/examples/common_vmseries_and_autoscale/README.md +++ b/examples/common_vmseries_and_autoscale/README.md @@ -1,212 +1,1227 @@ + --- -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 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). +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-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, 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. - -![Common-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/7d363d6a-b394-4851-99b9-03ce8abf379a) +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. +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/10289e10-6652-423b-94cd-0dc0b35f9997) 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. +- 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 +- _(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 +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`]() -* 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`]() +- 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 = - password = - username = "panadmin" + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = + password = + 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 - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| [random](#provider\_random) | n/a | -| [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| [ai](#module\_ai) | ../../modules/application_insights | n/a | -| [appgw](#module\_appgw) | ../../modules/appgw | n/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 | -|------|-------------|------|---------|:--------:| -| [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| [name\_prefix](#input\_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. | `string` | `""` | no | -| [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`.
When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| [vnets](#input\_vnets) | 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. | `any` | n/a | yes | -| [natgws](#input\_natgws) | 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.

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.

Example:
`
natgws = {
"natgw" = {
name = "public-natgw"
vnet_key = "transit-vnet"
subnet_keys = ["public"]
zone = 1
}
}
| `any` | `{}` | no | -| [load\_balancers](#input\_load\_balancers) | 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"
}
}
}
}
}
| `map` | `{}` | no | -| [application\_insights](#input\_application\_insights) | 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 [modules documentation](../../modules/application\_insights/README.md)):

- `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' = {
....
}
}

application_insights = {
metrics_retention_in_days = 365
}
| `map(string)` | `null` | no | -| [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 | -| [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 | -| [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.

For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)

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:
{
"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"
}
]
}
| `any` | `{}` | no | -| [appgws](#input\_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 and the `rules` property 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 | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [username](#output\_username) | Initial administrative username to use for VM-Series. | -| [password](#output\_password) | Initial administrative password to use for VM-Series. | -| [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | - +## Module's Required Inputs + +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. + +## Module's Optional Inputs + +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. +[`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. + +## 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. +`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 + +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 | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`vmss` | - | ../../modules/vmss | +`test_infrastructure` | - | ../../modules/test_infrastructure | + +Resources used in this module: + +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### region + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `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. +- `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) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + +### Optional Inputs + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. +Name of the newly specified RG is controlled by `resource_group_name`. + +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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: +- `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: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + 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) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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 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: + +```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: `&{}` + +[back to list](#modules-optional-inputs) + +#### 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`. +- `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 + 1st 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). + + +Type: + +```hcl +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) + allow_extension_operations = 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), []) + })) + 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) + }) + })), []) + })), []) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + + \ 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 cdf0a7a4..6ec23a8c 100644 --- a/examples/common_vmseries_and_autoscale/example.tfvars +++ b/examples/common_vmseries_and_autoscale/example.tfvars @@ -1,14 +1,16 @@ -# --- GENERAL --- # -location = "North Europe" +# GENERAL + +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" } -enable_zones = true -# --- VNET PART --- # +# NETWORK + vnets = { "transit" = { name = "transit" @@ -17,12 +19,13 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-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_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"] @@ -38,14 +41,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 +61,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 +87,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" } @@ -91,20 +103,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" @@ -114,20 +126,32 @@ 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 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 - avzones = ["1", "2", "3"] + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB + } 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,16 +160,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] - + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { - vnet_key = "transit" + name = "private-vmseries" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } @@ -156,20 +180,32 @@ load_balancers = { } appgws = { - "public" = { - name = "public-appgw" + public = { + name = "appgw" vnet_key = "transit" subnet_key = "appgw" - zones = ["1", "2", "3"] - capacity = 2 - rules = { - "minimum" = { - priority = 1 - listener = { - port = 80 - } - rewrite_sets = { + 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}" @@ -178,28 +214,46 @@ appgws = { } } } + rules = { + "http" = { + name = "http" + listener_key = "http" + backend_key = "http" + rewrite_key = "xff" + priority = 1 + } + } } } +# VM-SERIES +ngfw_metrics = { + name = "ngwf-log-analytics-wrksp" +} -# --- 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" - +scale_sets = { + common = { + name = "common-vmss" + vnet_key = "transit" + image = { + version = "10.2.901" + } + authentication = { + disable_password_authentication = false + } + virtual_machine_scale_set = { + bootstrap_options = "type=dhcp-client" + zones = ["1", "2", "3"] + } + autoscaling_configuration = { + default_count = 2 + } 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" @@ -211,30 +265,199 @@ vmss = { subnet_key = "public" load_balancer_key = "public" application_gateway_key = "public" - create_pip = true + create_public_ip = true } ] + autoscaling_profiles = [ + { + name = "default_profile" + default_count = 2 + }, + { + name = "weekday_profile" + default_count = 2 + minimum_count = 2 + maximum_count = 4 + 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 + } + }, + ] + }, + ] + } +} + +# TEST INFRASTRUCTURE - 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 +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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 manage the appliances + 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 = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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 aaa77147..edd80dfd 100644 --- a/examples/common_vmseries_and_autoscale/main.tf +++ b/examples/common_vmseries_and_autoscale/main.tf @@ -1,6 +1,13 @@ -# Generate a random password. +# Generate a random password + +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/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 + ]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 @@ -11,19 +18,30 @@ 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. +# 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 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + 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 @@ -33,216 +51,273 @@ 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" 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) - location = var.location + 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 = 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 = 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" 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) - location = var.location + 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) + region = var.region 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] } +# Create Load Balancers, both internal and external - -# 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 + region = var.region 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, + 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 ) - # 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, - 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[each.value.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags depends_on = [module.vnet] } +# Create Application Gateways -# Create the scale sets and related resources. -module "ai" { - source = "../../modules/application_insights" +module "appgw" { + source = "../../modules/appgw" - for_each = { for k, v in var.vmss : k => "${v.name}-ai" if can(v.autoscale_metrics) } + for_each = var.appgws - name = "${var.name_prefix}${each.value}" + 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] - 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) + 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 + 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 + tags = var.tags + depends_on = [module.vnet] } -module "appgw" { - source = "../../modules/appgw" +# Create VM-Series VM Scale Sets and closely associated resources - for_each = var.appgws +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - 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] + count = var.ngfw_metrics != null ? 1 : 0 - 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) + create_workspace = var.ngfw_metrics.create_workspace - rules = each.value.rules + 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) + ) + region = var.region - 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, {}) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } - tags = var.tags - depends_on = [module.vnet] + 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" - 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) + region = var.region + + 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) + 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 + 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 - application_insights_id = can(each.value.autoscale_metrics) ? module.ai[each.key].application_insights_id : null + tags = var.tags + depends_on = [module.vnet, module.load_balancer, module.appgw] +} - 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) +# Create test infrastructure - autoscale_metrics = try(each.value.autoscale_metrics, {}) +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)) + } + ) + } +} - 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) +module "test_infrastructure" { + source = "../../modules/test_infrastructure" - 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) + for_each = var.test_infrastructure - tags = var.tags + 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_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}" }) + } + 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")}" + }) } - depends_on = [ - module.ai, - module.vnet, - module.appgw - ] + 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 688a40da..ecd90478 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 } @@ -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 e3845c7a..77023f8b 100644 --- a/examples/common_vmseries_and_autoscale/variables.tf +++ b/examples/common_vmseries_and_autoscale/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: ``` 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,325 +35,847 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool +variable "region" { + 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) - - `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`, 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) + })), {}) + })) +} - - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. - - `network_security_groups` : map of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + 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. - - 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. + - `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: ``` 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({ + 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) + 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) + })) + })) } +# LOAD BALANCING - -### 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 (both 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: + Following properties are available: - ``` - "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" - } - } - } - } - } - ``` + - `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 "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 default = {} + nullable = false + 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, "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) + })) + })) } +# VM-SERIES -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 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 = 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 "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. -### 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 -} + The basic Scale Set configuration properties are as follows: -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 -} + - `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. -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - 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_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - 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 "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} + - do not specify anything else, a random password will be generated for you. + - specify at least one of `password` or `ssh_keys` properties. -variable "vmss" { - description = <<-EOF - A map defining all Virtual Machine Scale Sets. + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). - For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md) + - `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: - 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: + - `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. - ``` - { - "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" - } - ] - } - ``` + 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 + 1st 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 = {} - type = any + 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) + allow_extension_operations = 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), []) + })) + 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) + }) + })), []) + })), []) + })) } +# TEST INFRASTRUCTURE - -# Application Gateway -variable "appgws" { +variable "test_infrastructure" { description = <<-EOF - A map defining all Application Gateways in the current deployment. + A map defining test infrastructure including test VMs and Azure Bastion hosts. - 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 details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. 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 + - `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/versions.tf b/examples/common_vmseries_and_autoscale/versions.tf index 1f99597c..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 new file mode 100644 index 00000000..f4edff80 --- /dev/null +++ b/examples/dedicated_vmseries/.header.md @@ -0,0 +1,179 @@ +--- +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-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, + 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://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a5054270-514e-4c90-9601-133c6dc2ca66) + +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 +- _(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 + [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) +- [supported](#requirements) version of [`Terraform`]() +- 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 = + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + password = + 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 b38f14bf..0beeb75e 100644 --- a/examples/dedicated_vmseries/README.md +++ b/examples/dedicated_vmseries/README.md @@ -1,218 +1,1337 @@ + --- -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-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, 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. - -![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/3644469f-5f0f-44f9-8990-010c8bcf1cec) +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://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a5054270-514e-4c90-9601-133c6dc2ca66) 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 +- _(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 [AzureRM provider documentation for details](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) -* [supported](#requirements) version of [`Terraform`]() -* 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`]() +- 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. +**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 = - lb_frontend_ips = { - "private" = { - "ha-ports" = "1.2.3.4" - } - "public" = { - "palo-lb-app1-pip" = "1.2.3.4" - } - } - password = - 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 = + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + password = + 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 - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| [random](#provider\_random) | n/a | -| [http](#provider\_http) | n/a | -| [azurerm](#provider\_azurerm) | n/a | -| [local](#provider\_local) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| [ai](#module\_ai) | ../../modules/application_insights | n/a | -| [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| [vmseries](#module\_vmseries) | ../../modules/vmseries | n/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 | -|------|-------------|------|---------|:--------:| -| [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| [name\_prefix](#input\_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. | `string` | `""` | no | -| [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`.
When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| [vnets](#input\_vnets) | 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. | `any` | n/a | yes | -| [natgws](#input\_natgws) | 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.

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.

Example:
`
natgws = {
"natgw" = {
name = "public-natgw"
vnet_key = "transit-vnet"
subnet_keys = ["public"]
zone = 1
}
}
| `any` | `{}` | no | -| [load\_balancers](#input\_load\_balancers) | 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"
}
}
}
}
}
| `map` | `{}` | no | -| [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 | -| [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 | -| [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| [availability\_sets](#input\_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. | `any` | `{}` | no | -| [application\_insights](#input\_application\_insights) | 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 [modules documentation](../../modules/application\_insights/README.md)):

- `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' = {
....
}
}

application_insights = {
metrics_retention_in_days = 365
}
| `map(string)` | `null` | no | -| [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.

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 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.


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). | `any` | `{}` | no | -| [vmseries](#input\_vmseries) | 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)

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"
}
]
}
}
| `any` | n/a | yes | -| [appgws](#input\_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 and the `rules` property 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 | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [username](#output\_username) | Initial administrative username to use for VM-Series. | -| [password](#output\_password) | Initial administrative password to use for VM-Series. | -| [natgw\_public\_ips](#output\_natgw\_public\_ips) | Nat Gateways Public IP resources. | -| [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | -| [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for the VMSeries management interface. | -| [bootstrap\_storage\_urls](#output\_bootstrap\_storage\_urls) | n/a | - +## Module's Required Inputs + +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. + +## Module's Optional Inputs + +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. +[`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. +[`test_infrastructure`](#test_infrastructure) | `map` | A map defining test infrastructure including test VMs and Azure Bastion hosts. + +## 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` | +`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 + +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 | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`test_infrastructure` | - | ../../modules/test_infrastructure | + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### region + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `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. +- `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) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + +### Optional Inputs + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. +Name of the newly specified RG is controlled by `resource_group_name`. + +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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: +- `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: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + 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) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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` - (`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: + +```hcl +map(object({ + name = string + update_domain_count = optional(number) + fault_domain_count = optional(number) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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 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: + +```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: `&{}` + +[back to list](#modules-optional-inputs) + +#### 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. + + 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. + + 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 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 +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) + blob_retention = optional(number) + }), {}) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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`. +- `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/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 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 + 1st 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 + vnet_key = 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({ + 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) + 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)) + }) + 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) + application_gateway_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + + \ No newline at end of file diff --git a/examples/dedicated_vmseries/example.tfvars b/examples/dedicated_vmseries/example.tfvars index e408b1d2..14685e59 100644 --- a/examples/dedicated_vmseries/example.tfvars +++ b/examples/dedicated_vmseries/example.tfvars @@ -1,14 +1,16 @@ -# --- GENERAL --- # -location = "North Europe" +# GENERAL + +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 -# --- VNET PART --- # vnets = { "transit" = { name = "transit" @@ -17,12 +19,13 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-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_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"] @@ -38,10 +41,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 +56,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 +77,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" } @@ -83,39 +93,51 @@ 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" } } } } +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 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 - avzones = ["1", "2", "3"] + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB + } 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 } @@ -124,15 +146,16 @@ load_balancers = { } } "private" = { - name = "private-lb" - avzones = ["1", "2", "3"] + name = "private-lb" + vnet_key = "transit" frontend_ips = { "ha-ports" = { - vnet_key = "transit" + name = "private-vmseries" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } @@ -142,138 +165,324 @@ load_balancers = { } } +# VM-SERIES +ngfw_metrics = { + name = "metrics" +} -# --- VMSERIES PART --- # - -bootstrap_storage = { - bootstrap = { - name = "xmplbootstrapdedicated" - 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 = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } - 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 } } -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" vnet_key = "transit" - avzone = 1 + image = { + version = "10.2.901" + } + virtual_machine = { + 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" + intranet_cidr = "10.0.0.0/8" + } + } 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" vnet_key = "transit" - avzone = 2 + image = { + version = "10.2.901" + } + virtual_machine = { + 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" + intranet_cidr = "10.0.0.0/8" + } + } 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" vnet_key = "transit" - avzone = 1 + image = { + version = "10.2.901" + } + virtual_machine = { + 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" + intranet_cidr = "10.0.0.0/8" + } + } 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" vnet_key = "transit" - avzone = 2 + image = { + version = "10.2.901" + } + virtual_machine = { + 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" + intranet_cidr = "10.0.0.0/8" + } + } 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 } ] } } + +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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 = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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 f9a7a7f7..08fc21d2 100644 --- a/examples/dedicated_vmseries/main.tf +++ b/examples/dedicated_vmseries/main.tf @@ -1,6 +1,10 @@ -# Generate a random password. +# Generate a random password + +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password resource "random_password" "this" { - count = var.vmseries_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 @@ -11,25 +15,30 @@ resource "random_password" "this" { } locals { - vmseries_password = coalesce(var.vmseries_password, try(random_password.this[0].result, null)) + 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)) + } + ) + } } -# 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" -} +# Create or source a Resource Group -# 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.location + 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 @@ -39,156 +48,236 @@ 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" 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) - location = var.location + 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 = 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 = 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" 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) - location = var.location + 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) + region = var.region 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] } +# Create Load Balancers, both internal and external -# 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 + region = var.region resource_group_name = local.resource_group.name - enable_zones = var.enable_zones - avzones = try(each.value.avzones, null) + zones = each.value.zones + backend_name = each.value.backend_name - 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[each.value.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags 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... + } +} -# create the actual VMSeries VMs and resources -module "ai" { - source = "../../modules/application_insights" +module "appgw" { + source = "../../modules/appgw" - for_each = toset( - var.application_insights != null ? flatten( - try([var.application_insights.name], [for _, v in var.vmseries : "${v.name}-ai"]) - ) : [] - ) + for_each = var.appgws - name = "${var.name_prefix}${each.key}" + 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 + 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 + +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) + ) + region = var.region + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } - 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) + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } 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 if can(v.bootstrap_storage.template_bootstrap_xml) } + for_each = { + for k, v in var.vmseries : + k => merge(v.virtual_machine, { vnet_key = v.vnet_key }) + 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.ai[try(var.application_insights.name, "${each.value.name}-ai")].metrics_instrumentation_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] ) @@ -199,66 +288,68 @@ resource "local_file" "bootstrap_xml" { ) depends_on = [ - module.ai, + 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_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 -} + for_each = var.bootstrap_storages -module "bootstrap_share" { - source = "../../modules/bootstrap" + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + region = var.region - 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.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_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] + ] } ) - - files_md5 = can(each.value.bootstrap_storage.template_bootstrap_xml) ? { - "files/${each.value.name}-bootstrap.xml" = local_file.bootstrap_xml[each.key].content_md5 - } : {} + 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 - ] } - - +# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set 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) + location = var.region + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count tags = var.tags } @@ -268,80 +359,108 @@ module "vmseries" { for_each = var.vmseries - location = var.location + name = "${var.name_prefix}${each.value.name}" + region = var.region 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, + 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 + ) + } ) 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.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 ] } -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] +# Create test infrastructure - 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) +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)) + } + ) + } +} - 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)] +module "test_infrastructure" { + source = "../../modules/test_infrastructure" - rules = each.value.rules + for_each = var.test_infrastructure - 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, {}) + 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_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}" }) + } + 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.vmseries] + depends_on = [module.vnet] } diff --git a/examples/dedicated_vmseries/main_test.go b/examples/dedicated_vmseries/main_test.go index f4f9bd09..c0880b1d 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 d652953d..03613e39 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 } @@ -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 } @@ -29,11 +29,38 @@ 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 } + +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/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 @@ - + 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 @@ - + 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 @@ - + diff --git a/examples/dedicated_vmseries/variables.tf b/examples/dedicated_vmseries/variables.tf index a47878bf..de1f8084 100644 --- a/examples/dedicated_vmseries/variables.tf +++ b/examples/dedicated_vmseries/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: ``` 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,353 +35,983 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool +variable "region" { + 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) - - `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`, 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) + })), {}) + })) +} - - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. - - `network_security_groups` : map of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + 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. - - 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. + - `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: ``` 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({ + 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) + 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) + })) + })) } +# LOAD BALANCING - -### 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 (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 "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. - -### 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 + 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 + default = {} + nullable = false + 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, "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) + })) + })) } -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 -} +# 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 = {} - type = any + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number) + fault_domain_count = optional(number) + })) } -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 + Following properties are available: - 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 - } - ``` + - `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 = 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" { +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. + 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. + + 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. - Following properties are supported (except for name, all are optional): + For detailed documentation see [module's documentation](../../modules/bootstrap/README.md#storage_account). - - `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 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_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + 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`. - 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). + 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 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 = {} - 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) + blob_retention = optional(number) + }), {}) + 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) - - 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" - } - ] - } - } - ``` + 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). + + **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`, 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/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 + 1st 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 + vnet_key = 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({ + 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) + 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)) + }) + 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) + application_gateway_key = optional(string) + })) + })) + 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 = <<-EOF + Either `bootstrap_options` or `bootstrap_package` property can be set. + EOF + } + 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 + ]) + error_message = <<-EOF + The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set. + EOF + } } -# Application Gateway -variable "appgws" { +# TEST INFRASTRUCTURE + +variable "test_infrastructure" { description = <<-EOF - A map defining all Application Gateways in the current deployment. + A map defining test infrastructure including test VMs and Azure Bastion hosts. - 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 details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. 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 + - `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/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 new file mode 100644 index 00000000..63ba1637 --- /dev/null +++ b/examples/dedicated_vmseries_and_autoscale/.header.md @@ -0,0 +1,200 @@ +--- +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-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, + 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. + +![Detailed Topology Diagram](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a3104f98-4981-47ce-b96e-84e026268d1d) + +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 +- _(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. +- 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 + +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`]() +- 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 = + password = + 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 ed74fbfc..a6451ee5 100644 --- a/examples/dedicated_vmseries_and_autoscale/README.md +++ b/examples/dedicated_vmseries_and_autoscale/README.md @@ -1,210 +1,1221 @@ + --- -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 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). +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-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, 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. - -![Dedicated-VMSeries-with-autoscaling](https://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/2110772/757005dc-3e24-4b39-8a69-7b3fbf9819cb) +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://github.com/PaloAltoNetworks/terraform-azurerm-swfw-modules/assets/135693994/a3104f98-4981-47ce-b96e-84e026268d1d) 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). +- 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 +- _(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. +- 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 -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`]() -* 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`]() +- 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 = - password = - username = "panadmin" + lb_frontend_ips = { + "private" = { + "ha-ports" = "1.2.3.4" + } + "public" = { + "palo-lb-app1-pip" = "1.2.3.4" + } + } + metrics_instrumentation_keys = + password = + 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 - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.2, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| [random](#provider\_random) | n/a | -| [azurerm](#provider\_azurerm) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| [natgw](#module\_natgw) | ../../modules/natgw | n/a | -| [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/a | -| [ai](#module\_ai) | ../../modules/application_insights | n/a | -| [appgw](#module\_appgw) | ../../modules/appgw | n/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 | -|------|-------------|------|---------|:--------:| -| [tags](#input\_tags) | Map of tags to assign to the created resources. | `map(string)` | `{}` | no | -| [location](#input\_location) | The Azure region to use. | `string` | n/a | yes | -| [name\_prefix](#input\_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. | `string` | `""` | no | -| [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`.
When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group. | `string` | n/a | yes | -| [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| [vnets](#input\_vnets) | 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. | `any` | n/a | yes | -| [natgws](#input\_natgws) | 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.

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.

Example:
`
natgws = {
"natgw" = {
name = "public-natgw"
vnet_key = "transit-vnet"
subnet_keys = ["public"]
zone = 1
}
}
| `any` | `{}` | no | -| [load\_balancers](#input\_load\_balancers) | 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"
}
}
}
}
}
| `map` | `{}` | no | -| [application\_insights](#input\_application\_insights) | 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 [modules documentation](../../modules/application\_insights/README.md)):

- `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' = {
....
}
}

application_insights = {
metrics_retention_in_days = 365
}
| `map(string)` | `null` | no | -| [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 | -| [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 | -| [vmseries\_sku](#input\_vmseries\_sku) | VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks` | `string` | `"byol"` | no | -| [vmseries\_username](#input\_vmseries\_username) | Initial administrative username to use for all systems. | `string` | `"panadmin"` | no | -| [vmseries\_password](#input\_vmseries\_password) | Initial administrative password to use for all systems. Set to null for an auto-generated password. | `string` | `null` | no | -| [vmss](#input\_vmss) | A map defining all Virtual Machine Scale Sets.

For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md)

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:
{
"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"
}
]
}
| `any` | `{}` | no | -| [appgws](#input\_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 and the `rules` property 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 | `map` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [username](#output\_username) | Initial administrative username to use for VM-Series. | -| [password](#output\_password) | Initial administrative password to use for VM-Series. | -| [metrics\_instrumentation\_keys](#output\_metrics\_instrumentation\_keys) | The Instrumentation Key of the created instance(s) of Azure Application Insights. | -| [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP Addresses of the load balancers. | - +## Module's Required Inputs + +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. + +## Module's Optional Inputs + +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. +[`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. + +## 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. +`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 + +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 | +`ngfw_metrics` | - | ../../modules/ngfw_metrics | +`vmss` | - | ../../modules/vmss | +`test_infrastructure` | - | ../../modules/test_infrastructure | + +Resources used in this module: + +- `resource_group` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### region + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `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. +- `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) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + +### Optional Inputs + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. +Name of the newly specified RG is controlled by `resource_group_name`. + +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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: +- `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: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + 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) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### 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 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: + +```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: `&{}` + +[back to list](#modules-optional-inputs) + +#### 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`. +- `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 + 1st 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). + + +Type: + +```hcl +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) + allow_extension_operations = 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), []) + })) + 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) + }) + })), []) + })), []) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + + \ 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 a476b17e..b39601da 100644 --- a/examples/dedicated_vmseries_and_autoscale/example.tfvars +++ b/examples/dedicated_vmseries_and_autoscale/example.tfvars @@ -1,14 +1,16 @@ -# --- GENERAL --- # -location = "North Europe" +# GENERAL + +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" } -enable_zones = false -# --- VNET PART --- # +# NETWORK + vnets = { "transit" = { name = "transit" @@ -17,12 +19,13 @@ vnets = { "management" = { name = "mgmt-nsg" rules = { - vmseries_mgmt_allow_inbound = { + mgmt_inbound = { + name = "vmseries-management-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_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"] @@ -38,49 +41,48 @@ 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" = { - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "private" = { 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" = { - address_prefix = "10.0.0.48/28" - next_hop_type = "None" - } } } "public" = { 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" } @@ -91,54 +93,65 @@ 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" - } - "appgw" = { - name = "appgw-snet" - address_prefixes = ["10.0.0.48/28"] + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" } } } } +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" - 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 + } } } +# LOAD BALANCING - -# --- 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" + zones = null + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access LB + } 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 } @@ -147,14 +160,17 @@ load_balancers = { } } "private" = { - name = "private-lb" + name = "private-lb" + zones = null + vnet_key = "transit" frontend_ips = { "ha-ports" = { - vnet_key = "transit" + name = "private-vmseries" subnet_key = "private" private_ip_address = "10.0.0.30" in_rules = { HA_PORTS = { + name = "HA-ports" port = 0 protocol = "All" } @@ -164,44 +180,29 @@ load_balancers = { } } -appgws = { - "public" = { - name = "public-appgw" - vnet_key = "transit" - subnet_key = "appgw" - capacity = 2 - rules = { - "minimum" = { - priority = 1 - listener = { - port = 80 - } - rewrite_sets = { - "xff-strip-port" = { - sequence = 100 - request_headers = { - "X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}" - } - } - } - } - } - } -} - - - -# --- VMSERIES PART --- # -application_insights = {} +# VM-SERIES -vmseries_version = "10.2.3" -vmseries_vm_size = "Standard_DS3_v2" -vmss = { - "inbound" = { - name = "inbound-vmss" - vnet_key = "transit" - bootstrap_options = "type=dhcp-client" +ngfw_metrics = { + name = "ngwf-log-analytics-wrksp" +} +scale_sets = { + inbound = { + name = "inbound-vmss" + vnet_key = "transit" + image = { + version = "10.2.901" + } + authentication = { + disable_password_authentication = false + } + virtual_machine_scale_set = { + bootstrap_options = "type=dhcp-client" + zones = null + } + autoscaling_configuration = { + default_count = 2 + } interfaces = [ { name = "management" @@ -212,40 +213,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" + vnet_key = "transit" + image = { + version = "10.2.901" } - 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 = { + 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" @@ -261,27 +250,164 @@ vmss = { subnet_key = "public" } ] + } +} - autoscale_config = { - count_default = 2 - count_minimum = 1 - count_maximum = 3 +# TEST INFRASTRUCTURE + +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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"] + } + } + } } - autoscale_metrics = { - "DataPlaneCPUUtilizationPct" = { - scaleout_threshold = 70 - scalein_threshold = 20 + spoke_vms = { + "app1_vm" = { + name = "app1-vm" + vnet_key = "app1" + subnet_key = "vms" } } - scaleout_config = { - statistic = "Average" - time_aggregation = "Average" - window_minutes = 10 - cooldown_minutes = 30 + bastions = { + "app1_bastion" = { + name = "app1-bastion" + vnet_key = "app1" + subnet_key = "bastion" + } } - scalein_config = { - window_minutes = 10 - cooldown_minutes = 300 + } + "app2_testenv" = { + vnets = { + "app2" = { + name = "app2-vnet" + address_space = ["10.100.1.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + 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 aaa77147..f7b43887 100644 --- a/examples/dedicated_vmseries_and_autoscale/main.tf +++ b/examples/dedicated_vmseries_and_autoscale/main.tf @@ -1,6 +1,13 @@ -# Generate a random password. +# Generate a random password + +# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/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 + ]) ? ( + anytrue([for _, v in var.test_infrastructure : v.authentication.password == null]) ? 2 : 1 + ) : 0 length = 16 min_lower = 16 - 4 @@ -11,19 +18,30 @@ 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. +# 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 name = "${var.name_prefix}${var.resource_group_name}" - location = var.location + 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 @@ -33,216 +51,273 @@ 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" 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) - location = var.location + 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 = 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 = 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" 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) - location = var.location + 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) + region = var.region 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] } +# Create Load Balancers, both internal and external - -# 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 + region = var.region 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, + 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 ) - # 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, - 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[each.value.vnet_key].subnet_ids[v.subnet_key], null) + } + ) } tags = var.tags depends_on = [module.vnet] } +# Create Application Gateways -# Create the scale sets and related resources. -module "ai" { - source = "../../modules/application_insights" +module "appgw" { + source = "../../modules/appgw" - for_each = { for k, v in var.vmss : k => "${v.name}-ai" if can(v.autoscale_metrics) } + for_each = var.appgws - name = "${var.name_prefix}${each.value}" + 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] - 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) + 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 + 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 + tags = var.tags + depends_on = [module.vnet] } -module "appgw" { - source = "../../modules/appgw" +# Create VM-Series VM Scale Sets and closely associated resources - for_each = var.appgws +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" - 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] + count = var.ngfw_metrics != null ? 1 : 0 - 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) + create_workspace = var.ngfw_metrics.create_workspace - rules = each.value.rules + 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) + ) + region = var.region - 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, {}) + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } - tags = var.tags - depends_on = [module.vnet] + 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" - 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) + region = var.region + + 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) + 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 + 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 - application_insights_id = can(each.value.autoscale_metrics) ? module.ai[each.key].application_insights_id : null + tags = var.tags + depends_on = [module.vnet, module.load_balancer, module.appgw] +} - 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) +# Create test infrastructure - autoscale_metrics = try(each.value.autoscale_metrics, {}) +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)) + } + ) + } +} - 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) +module "test_infrastructure" { + source = "../../modules/test_infrastructure" - 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) + for_each = var.test_infrastructure - tags = var.tags + 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_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}" }) + } + 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")}" + }) } - depends_on = [ - module.ai, - module.vnet, - module.appgw - ] + 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 688a40da..ecd90478 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 } @@ -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 e3845c7a..77023f8b 100644 --- a/examples/dedicated_vmseries_and_autoscale/variables.tf +++ b/examples/dedicated_vmseries_and_autoscale/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: ``` 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,325 +35,847 @@ variable "resource_group_name" { type = string } -variable "enable_zones" { - description = "If `true`, enable zone support for resources." - default = true - type = bool +variable "region" { + 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) - - `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`, 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) + })), {}) + })) +} - - `create_subnets` : (default: `true`) if true, create the Subnets inside the Virtual Network, otherwise use pre-existing subnets - - `subnets` : map of Subnets to create +variable "vnet_peerings" { + description = <<-EOF + A map defining VNET peerings. - - `network_security_groups` : map of Network Security Groups to create - - `route_tables` : map of Route Tables to create. + 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. - - 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. + - `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: ``` 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({ + 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) + 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) + })) + })) } +# LOAD BALANCING - -### 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 (both 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: + Following properties are available: - ``` - "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" - } - } - } - } - } - ``` + - `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 "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 default = {} + nullable = false + 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, "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) + })) + })) } +# VM-SERIES -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 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 = 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 "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. -### 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 -} + The basic Scale Set configuration properties are as follows: -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 -} + - `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. -variable "vmseries_sku" { - description = "VM-Series SKU - list available with `az vm image list -o table --all --publisher paloaltonetworks`" - default = "byol" - 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_username" { - description = "Initial administrative username to use for all systems." - default = "panadmin" - 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 "vmseries_password" { - description = "Initial administrative password to use for all systems. Set to null for an auto-generated password." - default = null - type = string -} + - do not specify anything else, a random password will be generated for you. + - specify at least one of `password` or `ssh_keys` properties. -variable "vmss" { - description = <<-EOF - A map defining all Virtual Machine Scale Sets. + For all properties and their default values refer to [module's documentation](../../modules/vmss/README.md#authentication). - For detailed documentation on how to configure this resource, for available properties, especially for the defaults refer to [module documentation](../../modules/vmss/README.md) + - `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: - 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: + - `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. - ``` - { - "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" - } - ] - } - ``` + 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 + 1st 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 = {} - type = any + 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) + allow_extension_operations = 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), []) + })) + 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) + }) + })), []) + })), []) + })) } +# TEST INFRASTRUCTURE - -# Application Gateway -variable "appgws" { +variable "test_infrastructure" { description = <<-EOF - A map defining all Application Gateways in the current deployment. + A map defining test infrastructure including test VMs and Azure Bastion hosts. - 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 details and defaults for available options please refer to the + [`test_infrastructure`](../../modules/test_infrastructure/README.md) module. 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 + - `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/versions.tf b/examples/dedicated_vmseries_and_autoscale/versions.tf index 1f99597c..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/gwlb_with_vmseries/.header.md b/examples/gwlb_with_vmseries/.header.md new file mode 100644 index 00000000..ce21c24a --- /dev/null +++ b/examples/gwlb_with_vmseries/.header.md @@ -0,0 +1,122 @@ +--- +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`]() +- 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. +* 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 index bee9d4a3..ed6c9e82 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -1,6 +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). +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`]() +- 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 @@ -8,19 +55,27 @@ The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffi * 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. +* 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 +```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: - terraform apply +```bash +terraform apply +``` * At this stage you have to wait a few minutes for the firewalls to bootstrap. @@ -30,15 +85,19 @@ Firewalls in this example are configured with password authentication. To retrie * for username: - terraform output username +```bash +terraform output username +``` * for password: - terraform output password +```bash +terraform output password +``` The management public IP addresses are available in the `vmseries_mgmt_ips` output: -```sh +```bash terraform output vmseries_mgmt_ips ``` @@ -47,91 +106,884 @@ 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. +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). +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 ``` - -### Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.0, < 2.0 | - -### Providers - -| Name | Version | -|------|---------| -| [http](#provider\_http) | n/a | -| [azurerm](#provider\_azurerm) | n/a | -| [local](#provider\_local) | n/a | -| [random](#provider\_random) | n/a | - -### Modules - -| Name | Source | Version | -|------|--------|---------| -| [vnet](#module\_vnet) | ../../modules/vnet | n/a | -| [gwlb](#module\_gwlb) | ../../modules/gwlb | n/a | -| [ai](#module\_ai) | ../../modules/application_insights | n/a | -| [bootstrap](#module\_bootstrap) | ../../modules/bootstrap | n/a | -| [bootstrap\_share](#module\_bootstrap\_share) | ../../modules/bootstrap | n/a | -| [vmseries](#module\_vmseries) | ../../modules/vmseries | n/a | -| [load\_balancer](#module\_load\_balancer) | ../../modules/loadbalancer | n/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 | -|------|-------------|------|---------|:--------:| -| [name\_prefix](#input\_name\_prefix) | Prefix for resource names. | `string` | `""` | no | -| [location](#input\_location) | Location where the resources will be deployed. | `string` | n/a | yes | -| [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`.
When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. | `bool` | `true` | no | -| [resource\_group\_name](#input\_resource\_group\_name) | Name of the Resource Group to create or use. | `string` | n/a | yes | -| [enable\_zones](#input\_enable\_zones) | If `true`, enable zone support for resources. | `bool` | `true` | no | -| [tags](#input\_tags) | Map of tags to assign to all of the created resources. | `map(string)` | `{}` | no | -| [vnets](#input\_vnets) | 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. | `any` | n/a | yes | -| [gateway\_load\_balancers](#input\_gateway\_load\_balancers) | 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. | `any` | `{}` | no | -| [application\_insights](#input\_application\_insights) | 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
}
| `map(string)` | `null` | no | -| [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.
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. | `any` | `{}` | no | -| [vmseries\_common](#input\_vmseries\_common) | 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). | `any` | n/a | yes | -| [vmseries](#input\_vmseries) | 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` | `map(any)` | n/a | yes | -| [availability\_sets](#input\_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` - (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. | `any` | `{}` | no | -| [load\_balancers](#input\_load\_balancers) | 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"
}
}
}
}
}
| `map` | `{}` | no | -| [appvms\_common](#input\_appvms\_common) | 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. | `any` | n/a | yes | -| [appvms](#input\_appvms) | 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`. | `any` | `{}` | no | - -### Outputs - -| Name | Description | -|------|-------------| -| [username](#output\_username) | Initial administrative username to use for VM-Series. | -| [password](#output\_password) | Initial administrative password to use for VM-Series. | -| [vmseries\_mgmt\_ips](#output\_vmseries\_mgmt\_ips) | IP addresses for VM-Series management. | -| [gwlb\_frontend\_ip\_configuration\_ids](#output\_gwlb\_frontend\_ip\_configuration\_ids) | Configuration IDs of Gateway Load Balancers' frontends. | -| [appvms\_username](#output\_appvms\_username) | Initial administrative username to use for application VMs. | -| [appvms\_password](#output\_appvms\_password) | Initial administrative password to use for application VMs. | -| [lb\_frontend\_ips](#output\_lb\_frontend\_ips) | IP addresses of the Load Balancers serving applications. | - \ No newline at end of file +## Module's Required Inputs + +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. + +## Module's Optional Inputs + +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. +[`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. + +## 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. +`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 + +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) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + +#### region + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `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. +- `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) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + +### Optional Inputs + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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: `` + +[back to list](#modules-optional-inputs) + +#### create_resource_group + +When set to `true` it will cause a Resource Group creation. +Name of the newly specified RG is controlled by `resource_group_name`. + +When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + + +Type: bool + +Default value: `true` + +[back to list](#modules-optional-inputs) + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + +#### gateway_load_balancers + +A map with Gateway Load Balancer (GWLB) definitions. + +Following settings are available: +- `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: + +```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[]` + +[back to list](#modules-optional-inputs) + +#### 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` - (`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: + +```hcl +map(object({ + name = string + update_domain_count = optional(number) + fault_domain_count = optional(number) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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 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: + +```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: `&{}` + +[back to list](#modules-optional-inputs) + +#### 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. + + 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. + + 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 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 +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) + blob_retention = optional(number) + }), {}) + 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[]` + +[back to list](#modules-optional-inputs) + +#### 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`. +- `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/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 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. + + - `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 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 + 1st 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 + vnet_key = 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({ + 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) + 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)) + }) + 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) + application_gateway_key = optional(string) + })) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### 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[]` + +[back to list](#modules-optional-inputs) + + \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index ed3ae724..d4b26b29 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,114 +1,109 @@ -# Common +# GENERAL + +region = "North Europe" +resource_group_name = "gwlb" name_prefix = "example-" -location = "North Europe" -resource_group_name = "vmseries-gwlb" tags = { - "CreatedBy" = "Palo Alto Networks" - "CreatedWith" = "Terraform" + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" + "xdr-exclusion" = "yes" } -# VNets +# NETWORK + 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" - } - } + "transit" = { + name = "transit" + address_space = ["10.0.0.0/25"] network_security_groups = { - mgmt = { - name = "vmseries-mgmt" + "management" = { + name = "mgmt-nsg" rules = { - vmseries-mgmt-allow-inbound = { + 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_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 = "*" + destination_address_prefix = "10.0.0.0/28" destination_port_ranges = ["22", "443"] } } } - data = { - name = "vmseries-data" + "data" = { + name = "data-nsg" } } route_tables = { - mgmt = { - name = "vmseries-mgmt" + "management" = { + name = "mgmt-rt" routes = { - data-blackhole = { - address_prefix = "10.0.1.16/28" + "data_blackhole" = { + name = "data-blackhole-udr" + address_prefix = "10.0.0.16/28" next_hop_type = "None" } } } - data = { - name = "vmseries-data" + "data" = { + name = "data-rt" routes = { - mgmt-blackhole = { - address_prefix = "10.0.1.0/28" + "mgmt_blackhole" = { + name = "mgmt-blackhole-udr" + address_prefix = "10.0.0.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" + "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 } - } - network_security_groups = { - web = { - name = "app1-web" - rules = { - 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"] - } - } + "data" = { + name = "data-snet" + address_prefixes = ["10.0.0.16/28"] + network_security_group_key = "data" + route_table_key = "data" } } } } +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 = { gwlb = { - name = "vmseries-gwlb" - vnet_key = "security" - subnet_key = "data" + name = "vmseries-gwlb" + + frontend_ip = { + vnet_key = "transit" + subnet_key = "data" + } health_probe = { - port = 80 + name = "custom-health-probe" + port = 80 + protocol = "Tcp" } backends = { - ext-int = { + backend = { + name = "custom-backend" tunnel_interfaces = { internal = { identifier = 800 @@ -125,132 +120,284 @@ gateway_load_balancers = { } } } + + lb_rule = { + name = "custom-lb-rule" + } } } -# VMseries +# VM-SERIES + bootstrap_storages = { - bootstrap = { - name = "vmseriesgwlbboostrap" - storage_acl = true - storage_allow_vnet_subnets = { - management = { - vnet_key = "security" - subnet_key = "mgmt" - } + "bootstrap" = { + name = "examplegwlbbootstrap" + storage_network_security = { + vnet_key = "transit" + allowed_subnet_keys = ["management"] + allowed_public_ips = ["1.1.1.1/32"] # TODO: Whitelist public IP addresses that will be used to access storage account } } } 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" + "fw-1" = { + name = "firewall01" + vnet_key = "transit" + image = { + version = "10.2.901" + } + virtual_machine = { + 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 = "mgmt" - subnet_key = "mgmt" + name = "vm01-mgmt" + subnet_key = "management" create_public_ip = true }, { - name = "data" - subnet_key = "data" - enable_backend_pool = true - gwlb_key = "gwlb" - gwlb_backend_key = "ext-int" + name = "vm01-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" } ] } - 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" + "fw-2" = { + name = "firewall02" + vnet_key = "transit" + image = { + version = "10.2.901" + } + virtual_machine = { + 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 = "mgmt" - subnet_key = "mgmt" + name = "vm02-mgmt" + subnet_key = "management" create_public_ip = true }, { - name = "data" - subnet_key = "data" - enable_backend_pool = true - gwlb_key = "gwlb" - gwlb_backend_key = "ext-int" + name = "vm02-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" } ] } } -vmseries_common = { - username = "panadmin" - ssh_keys = [] # Update here if required +# TEST INFRASTRUCTURE - 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 = { - create_public_ip = true - gwlb_key = "gwlb" - in_rules = { - http = { - floating_ip = false - port = 80 - protocol = "Tcp" +test_infrastructure = { + "app1_testenv" = { + vnets = { + "app1" = { + name = "app1-vnet" + address_space = ["10.100.0.0/25"] + hub_vnet_name = "transit" # Name prefix is added to the beginning of this string + 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 = "*" + } + 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"] + } + } + } + } + subnets = { + "vms" = { + name = "vms-snet" + address_prefixes = ["10.100.0.0/26"] + network_security_group_key = "app1" } - https = { - floating_ip = false - port = 443 - protocol = "Tcp" + "bastion" = { + name = "AzureBastionSubnet" + address_prefixes = ["10.100.0.64/26"] } } - out_rules = { - outbound = { - protocol = "Tcp" + } + } + load_balancers = { + "app1" = { + name = "app1-lb" + 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" + } + } } -} - -appvms_common = { - username = "appadmin" - custom_data = <