From 00f5d75523f0e0f129d9dfd1b4d7a191aaf40cae Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 23 Jan 2024 15:44:40 +0100 Subject: [PATCH 01/14] Start GWLB example refactor --- examples/gwlb_with_vmseries/.header.md | 78 ++ examples/gwlb_with_vmseries/README.md | 1032 +++++++++++++++++ examples/gwlb_with_vmseries/example.tfvars | 219 ++++ .../files/init-cfg.sample.txt | 2 + examples/gwlb_with_vmseries/main.tf | 415 +++++++ examples/gwlb_with_vmseries/main_test.go | 81 ++ examples/gwlb_with_vmseries/outputs.tf | 39 + .../templates/bootstrap-gwlb.tftpl | 520 +++++++++ examples/gwlb_with_vmseries/variables.tf | 789 +++++++++++++ examples/gwlb_with_vmseries/versions.tf | 22 + 10 files changed, 3197 insertions(+) create mode 100644 examples/gwlb_with_vmseries/.header.md create mode 100644 examples/gwlb_with_vmseries/README.md create mode 100644 examples/gwlb_with_vmseries/example.tfvars create mode 100644 examples/gwlb_with_vmseries/files/init-cfg.sample.txt create mode 100644 examples/gwlb_with_vmseries/main.tf create mode 100644 examples/gwlb_with_vmseries/main_test.go create mode 100644 examples/gwlb_with_vmseries/outputs.tf create mode 100644 examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl create mode 100644 examples/gwlb_with_vmseries/variables.tf create mode 100644 examples/gwlb_with_vmseries/versions.tf diff --git a/examples/gwlb_with_vmseries/.header.md b/examples/gwlb_with_vmseries/.header.md new file mode 100644 index 00000000..c69ec1d9 --- /dev/null +++ b/examples/gwlb_with_vmseries/.header.md @@ -0,0 +1,78 @@ +# VM-Series Azure Gateway Load Balancer example + +The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing +Azure Gateway Load Balancer in service chain model as described in the following +[document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). + +## Usage + +### Deployment Steps + +* Checkout the code locally. +* Copy `example.tfvars` to `terraform.tfvars` and adjust it to your needs. +* Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this +[documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) +for details). +* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* Initialize the Terraform module: + +```bash +terraform init +``` + +* (optional) Plan you infrastructure to see what will be actually deployed: + +```bash +terraform plan +``` + +* Deploy the infrastructure: + +```bash +terraform apply +``` + +* At this stage you have to wait a few minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +* for username: + +```bash +terraform output username +``` + +* for password: + +```bash +terraform output password +``` + +The management public IP addresses are available in the `vmseries_mgmt_ips` output: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +* CLI - ssh client is required +* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +With default example configuration, the devices already contain `DAY0` configuration, so all network interfaces should be +configured and Azure Gateway Load Balancer should already report that the devices are healthy. + +You can now proceed with licensing the devices and configuring your first rules. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for +`DAY1` configuration (security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md new file mode 100644 index 00000000..6f9a55ea --- /dev/null +++ b/examples/gwlb_with_vmseries/README.md @@ -0,0 +1,1032 @@ + +# VM-Series Azure Gateway Load Balancer example + +The exmaple allows to deploy VM-Series firewalls for inbound and outbound traffic inspection utilizing +Azure Gateway Load Balancer in service chain model as described in the following +[document](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/set-up-the-vm-series-firewall-on-azure/deploy-the-vm-series-firewall-with-the-azure-gwlb). + +## Usage + +### Deployment Steps + +* Checkout the code locally. +* Copy `example.tfvars` to `terraform.tfvars` and adjust it to your needs. +* Copy `files/init-cfg.txt.sample` to `files/init-cfg.txt` and fill it in with required bootstrap parameters (see this +[documentation](https://docs.paloaltonetworks.com/vm-series/10-2/vm-series-deployment/bootstrap-the-vm-series-firewall/create-the-init-cfgtxt-file/init-cfgtxt-file-components) +for details). +* (optional) Authenticate to AzureRM, switch to the Subscription of your choice if necessary. +* Initialize the Terraform module: + +```bash +terraform init +``` + +* (optional) Plan you infrastructure to see what will be actually deployed: + +```bash +terraform plan +``` + +* Deploy the infrastructure: + +```bash +terraform apply +``` + +* At this stage you have to wait a few minutes for the firewalls to bootstrap. + +### Post deploy + +Firewalls in this example are configured with password authentication. To retrieve the initial credentials run: + +* for username: + +```bash +terraform output username +``` + +* for password: + +```bash +terraform output password +``` + +The management public IP addresses are available in the `vmseries_mgmt_ips` output: + +```bash +terraform output vmseries_mgmt_ips +``` + +You can now login to the devices using either: + +* CLI - ssh client is required +* Web UI (https) - any modern web browser, note that initially the traffic is encrypted with a self-signed certificate. + +With default example configuration, the devices already contain `DAY0` configuration, so all network interfaces should be +configured and Azure Gateway Load Balancer should already report that the devices are healthy. + +You can now proceed with licensing the devices and configuring your first rules. + +Please also refer to [this repository](https://github.com/PaloAltoNetworks/iron-skillet) for +`DAY1` configuration (security hardening). + +### Cleanup + +To remove the deployed infrastructure run: + +```bash +terraform destroy +``` + +## Module's Required Inputs + +Name | Type | Description +--- | --- | --- +[`location`](#location) | `string` | The Azure region to use. +[`resource_group_name`](#resource_group_name) | `string` | Name of the Resource Group. +[`vnets`](#vnets) | `map` | A map defining VNETs. + + +## Module's Optional Inputs + +Name | Type | Description +--- | --- | --- +[`tags`](#tags) | `map` | Map of tags to assign to the created resources. +[`name_prefix`](#name_prefix) | `string` | A prefix that will be added to all created resources. +[`create_resource_group`](#create_resource_group) | `bool` | When set to `true` it will cause a Resource Group creation. +[`enable_zones`](#enable_zones) | `bool` | If `true`, enable zone support for resources. +[`natgws`](#natgws) | `map` | A map defining NAT Gateways. +[`load_balancers`](#load_balancers) | `map` | A map containing configuration for all (private and public) Load Balancers. +[`gateway_load_balancers`](#gateway_load_balancers) | `any` | Map with Gateway Load Balancer definitions. +[`availability_sets`](#availability_sets) | `map` | A map defining availability sets. +[`ngfw_metrics`](#ngfw_metrics) | `object` | A map controlling metrics-relates resources. +[`bootstrap_storages`](#bootstrap_storages) | `map` | A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. +[`vmseries`](#vmseries) | `map` | A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image. +[`appgws`](#appgws) | `map` | A map defining all Application Gateways in the current deployment. +[`appvms`](#appvms) | `any` | Configuration for sample application VMs. + + + +## Module's Outputs + +Name | Description +--- | --- +`usernames` | Initial administrative username to use for VM-Series. +`passwords` | Initial administrative password to use for VM-Series. +`natgw_public_ips` | Nat Gateways Public IP resources. +`metrics_instrumentation_keys` | The Instrumentation Key of the created instance(s) of Azure Application Insights. +`lb_frontend_ips` | IP Addresses of the load balancers. +`vmseries_mgmt_ips` | IP addresses for the VM-Series management interface. +`bootstrap_storage_urls` | + +## Module's Nameplate + + +Requirements needed by this module: + +- `terraform`, version: >= 1.5, < 2.0 + + +Providers used in this module: + +- `random` +- `azurerm` +- `local` + + +Modules used in this module: +Name | Version | Source | Description +--- | --- | --- | --- +`vnet` | - | ../../modules/vnet | Manage the network required for the topology. +`natgw` | - | ../../modules/natgw | +`load_balancer` | - | ../../modules/loadbalancer | create load balancers, both internal and external +`gwlb` | - | ../../modules/gwlb | create Gateway Load Balancers +`ngfw_metrics` | - | ../../modules/ngfw_metrics | create the actual VM-Series VMs and resources +`bootstrap` | - | ../../modules/bootstrap | +`vmseries` | - | ../../modules/vmseries | +`appgw` | - | ../../modules/appgw | +`appvm` | - | ../../modules/virtual_machine | + + +Resources used in this module: + +- `availability_set` (managed) +- `resource_group` (managed) +- `file` (managed) +- `password` (managed) +- `resource_group` (data) + +## Inputs/Outpus details + +### Required Inputs + + + +#### location + +The Azure region to use. + +Type: string + +[back to list](#modules-required-inputs) + + + +#### resource_group_name + +Name of the Resource Group. + +Type: string + +[back to list](#modules-required-inputs) + + +#### vnets + +A map defining VNETs. + +For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + +- `create_virtual_network` - (`bool`, optional, defaults to `true`) when set to `true` will create a VNET, + `false` will source an existing VNET. +- `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be + a full resource name, including prefixes. +- `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly + created VNET +- `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which + the VNET will reside or is sourced from +- `create_subnets` - (`bool`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, + otherwise use source existing subnets +- `subnets` - (`map`, optional) map of Subnets to create or source, for details see + [VNET module documentation](../../modules/vnet/README.md#subnets) +- `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see + [VNET module documentation](../../modules/vnet/README.md#network_security_groups) +- `route_tables` - (`map`, optional) map of Route Tables to create, for details see + [VNET module documentation](../../modules/vnet/README.md#route_tables) + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = string + rules = optional(map(object({ + name = string + priority = number + direction = string + access = string + protocol = string + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + })), {}) + })), {}) + route_tables = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_ip_address = optional(string) + })) + })), {}) + create_subnets = optional(bool, true) + subnets = optional(map(object({ + name = string + address_prefixes = optional(list(string), []) + network_security_group_key = optional(string) + route_table_key = optional(string) + enable_storage_service_endpoint = optional(bool, false) + })), {}) + })) +``` + + +[back to list](#modules-required-inputs) + + + + + + + + + + + + +### Optional Inputs + + +#### tags + +Map of tags to assign to the created resources. + +Type: map(string) + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + + +#### name_prefix + +A prefix that will be added to all created resources. +There is no default delimiter applied between the prefix and the resource name. +Please include the delimiter in the actual prefix. + +Example: +``` +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) + + +#### enable_zones + +If `true`, enable zone support for resources. + +Type: bool + +Default value: `true` + +[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: +- `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. +- `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. +- `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). +- `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. +- `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. +- `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. +- `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. +- `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. +- `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + +Example: +``` +natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } +} +``` + + +Type: + +```hcl +map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### load_balancers + +A map containing configuration for all (private and public) Load Balancers. + +This is a brief description of available properties. For a detailed one please refer to +[module documentation](../../modules/loadbalancer/README.md). + +Following properties are available: + +- `name` - (`string`, required) a name of the Load Balancer +- `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details +- `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties +- `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` +- `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` + + +Type: + +```hcl +map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### gateway_load_balancers + +Map with Gateway Load Balancer definitions. Following settings are supported: + +TODO: adjust type and description + +Please consult [module documentation](../../modules/gwlb/README.md) for details. + + +Type: any + +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` - name of the Application Insights. +- `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). +- `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + +Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). +Please verify how many update and fault domain are supported in a region before deploying this resource. + + +Type: + +```hcl +map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +``` + + +Default value: `map[]` + +[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 defaults) the SKU of the Log Analytics Workspace. +- `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + + +Type: + +```hcl +object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +``` + + +Default value: `&{}` + +[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, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. +- `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. +- `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. +- `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + + +Type: + +```hcl +map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +``` + + +Default value: `map[]` + +[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`. +- `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + +- `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + +- `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + +- `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1st interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + + +Type: + +```hcl +map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) +``` + + +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). + +Following properties are supported: +- `name` - (`string`, required) name of the Application Gateway. +- `public_ip` - (`string`, required) public IP address. +- `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. +- `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. +- `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. +- `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) +- `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported +- `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway +- `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. +- `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name +- `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool +- `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) +- `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) +- `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) +- `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) +- `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) +- `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) +- `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` +- `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` +- `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` +- `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` +- `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + + +Type: + +```hcl +map(object({ + name = string + public_ip = object({ + name = string + resource_group_name = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +``` + + +Default value: `map[]` + +[back to list](#modules-optional-inputs) + +#### appvms + +Configuration for sample application VMs. Available settings: + +TODO: adjust type and description + + + +Type: any + +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 new file mode 100644 index 00000000..86769e85 --- /dev/null +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -0,0 +1,219 @@ +# --- GENERAL --- # +location = "North Europe" +resource_group_name = "transit-vnet-common" +name_prefix = "example-" +tags = { + "CreatedBy" = "Palo Alto Networks" + "CreatedWith" = "Terraform" +} + +# --- VNET PART --- # +vnets = { + "transit" = { + name = "transit" + address_space = ["10.0.0.0/25"] + network_security_groups = { + "management" = { + name = "mgmt-nsg" + rules = { + mgmt_inbound = { + name = "vmseries-management-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "10.0.0.0/28" + destination_port_ranges = ["22", "443"] + } + } + } + "public" = { + name = "public-nsg" + } + } + route_tables = { + "management" = { + name = "mgmt-rt" + routes = { + "private_blackhole" = { + name = "private-blackhole-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" + } + } + } + "private" = { + name = "private-rt" + routes = { + "default" = { + 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" + } + } + } + "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" + } + } + } + } + subnets = { + "management" = { + name = "mgmt-snet" + address_prefixes = ["10.0.0.0/28"] + network_security_group_key = "management" + route_table_key = "management" + enable_storage_service_endpoint = true + } + "private" = { + name = "private-snet" + address_prefixes = ["10.0.0.16/28"] + route_table_key = "private" + } + "public" = { + name = "public-snet" + address_prefixes = ["10.0.0.32/28"] + network_security_group_key = "public" + route_table_key = "public" + } + } + } +} + + +# --- LOAD BALANCING PART --- # +load_balancers = { + "public" = { + name = "public-lb" + nsg_auto_rules_settings = { + nsg_vnet_key = "transit" + nsg_key = "public" + source_ips = ["0.0.0.0/0"] + } + frontend_ips = { + "app1" = { + name = "app1" + public_ip_name = "public-lb-app1-pip" + create_public_ip = true + in_rules = { + "balanceHttp" = { + name = "HTTP" + protocol = "Tcp" + port = 80 + } + } + } + } + } + "private" = { + name = "private-lb" + frontend_ips = { + "ha-ports" = { + name = "private-vmseries" + vnet_key = "transit" + subnet_key = "private" + private_ip_address = "10.0.0.30" + in_rules = { + HA_PORTS = { + name = "HA-ports" + port = 0 + protocol = "All" + } + } + } + } + } +} + +# --- VMSERIES PART --- # +vmseries = { + "fw-1" = { + name = "firewall01" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_options = "type=dhcp-client" + } + interfaces = [ + { + name = "vm01-mgmt" + subnet_key = "management" + create_public_ip = true + }, + { + name = "vm01-private" + subnet_key = "private" + load_balancer_key = "private" + }, + { + name = "vm01-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + } + ] + } + "fw-2" = { + name = "firewall02" + image = { + version = "10.2.3" + } + virtual_machine = { + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_options = "type=dhcp-client" + } + interfaces = [ + { + name = "vm02-mgmt" + subnet_key = "management" + create_public_ip = true + }, + { + name = "vm02-private" + subnet_key = "private" + load_balancer_key = "private" + }, + { + name = "vm02-public" + subnet_key = "public" + create_public_ip = true + load_balancer_key = "public" + } + ] + } +} diff --git a/examples/gwlb_with_vmseries/files/init-cfg.sample.txt b/examples/gwlb_with_vmseries/files/init-cfg.sample.txt new file mode 100644 index 00000000..c4373843 --- /dev/null +++ b/examples/gwlb_with_vmseries/files/init-cfg.sample.txt @@ -0,0 +1,2 @@ +type=dhcp-client +plugin-op-commands=azure-gwlb-inspect:enable \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main.tf b/examples/gwlb_with_vmseries/main.tf new file mode 100644 index 00000000..3c65f707 --- /dev/null +++ b/examples/gwlb_with_vmseries/main.tf @@ -0,0 +1,415 @@ +# Generate a random password. +resource "random_password" "this" { + count = anytrue([ + for _, v in var.vmseries : v.authentication.password == null + ]) ? 1 : 0 + + length = 16 + min_lower = 16 - 4 + min_numeric = 1 + min_special = 1 + min_upper = 1 + override_special = "_%@" +} + +locals { + authentication = { + for k, v in var.vmseries : k => + merge( + v.authentication, + { + ssh_keys = [for ssh_key in v.authentication.ssh_keys : file(ssh_key)] + password = coalesce(v.authentication.password, try(random_password.this[0].result, null)) + } + ) + } +} + +# Create or source the Resource Group. +resource "azurerm_resource_group" "this" { + count = var.create_resource_group ? 1 : 0 + name = "${var.name_prefix}${var.resource_group_name}" + location = var.location + + tags = var.tags +} + +data "azurerm_resource_group" "this" { + count = var.create_resource_group ? 0 : 1 + name = var.resource_group_name +} + +locals { + resource_group = var.create_resource_group ? azurerm_resource_group.this[0] : data.azurerm_resource_group.this[0] +} + +# Manage the network required for the topology. +module "vnet" { + source = "../../modules/vnet" + + for_each = var.vnets + + name = each.value.create_virtual_network ? "${var.name_prefix}${each.value.name}" : each.value.name + create_virtual_network = each.value.create_virtual_network + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + address_space = each.value.address_space + + create_subnets = each.value.create_subnets + subnets = each.value.subnets + + network_security_groups = { for k, v in each.value.network_security_groups : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + route_tables = { for k, v in each.value.route_tables : k => merge(v, { name = "${var.name_prefix}${v.name}" }) + } + + tags = var.tags +} + + +module "natgw" { + source = "../../modules/natgw" + + for_each = var.natgws + + create_natgw = each.value.create_natgw + name = each.value.create_natgw ? "${var.name_prefix}${each.value.name}" : each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + zone = try(each.value.zone, null) + idle_timeout = each.value.idle_timeout + subnet_ids = { for v in each.value.subnet_keys : v => module.vnet[each.value.vnet_key].subnet_ids[v] } + + public_ip = try(merge(each.value.public_ip, { name = "${each.value.public_ip.create ? var.name_prefix : ""}${each.value.public_ip.name}" }), null) + public_ip_prefix = try(merge(each.value.public_ip_prefix, { name = "${each.value.public_ip_prefix.create ? var.name_prefix : ""}${each.value.public_ip_prefix.name}" }), null) + + tags = var.tags + depends_on = [module.vnet] +} + + +# create load balancers, both internal and external +module "load_balancer" { + source = "../../modules/loadbalancer" + + for_each = var.load_balancers + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + zones = each.value.zones + + health_probes = each.value.health_probes + + nsg_auto_rules_settings = try( + { + nsg_name = try( + "${var.name_prefix}${var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].network_security_groups[each.value.nsg_auto_rules_settings.nsg_key].name}", + each.value.nsg_auto_rules_settings.nsg_name + ) + nsg_resource_group_name = try( + var.vnets[each.value.nsg_auto_rules_settings.nsg_vnet_key].resource_group_name, + each.value.nsg_auto_rules_settings.nsg_resource_group_name, + null + ) + source_ips = each.value.nsg_auto_rules_settings.source_ips + base_priority = each.value.nsg_auto_rules_settings.base_priority + }, + null + ) + + frontend_ips = { + for k, v in each.value.frontend_ips : k => merge( + v, + { + public_ip_name = v.create_public_ip ? "${var.name_prefix}${v.public_ip_name}" : "${v.public_ip_name}", + subnet_id = try(module.vnet[v.vnet_key].subnet_ids[v.subnet_key], null) + gwlb_fip_id = try(module.gwlb[v.gwlb_key].frontend_ip_config_id, null) + } + ) + } + + tags = var.tags + depends_on = [module.vnet] +} + +# create Gateway Load Balancers +module "gwlb" { + for_each = var.gateway_load_balancers + source = "../../modules/gwlb" + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = try(each.value.resource_group_name, local.resource_group.name) + location = var.location + + backends = try(each.value.backends, null) + health_probe = try(each.value.health_probe, null) + lb_rule = try(each.value.lb_rule, null) + + zones = var.enable_zones ? try(each.value.zones, null) : null + frontend_ip = { + name = try(each.value.frontend_ip.name, "${var.name_prefix}${each.value.name}") + private_ip_address_version = try(each.value.frontend_ip.private_ip_address_version, null) + private_ip_address = try(each.value.frontend_ip.private_ip_address, null) + subnet_id = module.vnet[each.value.frontend_ip.vnet_key].subnet_ids[each.value.frontend_ip.subnet_key] + } + + tags = var.tags +} + + +# create the actual VM-Series VMs and resources +module "ngfw_metrics" { + source = "../../modules/ngfw_metrics" + + count = var.ngfw_metrics != null ? 1 : 0 + + create_workspace = var.ngfw_metrics.create_workspace + + name = "${var.ngfw_metrics.create_workspace ? var.name_prefix : ""}${var.ngfw_metrics.name}" + resource_group_name = var.ngfw_metrics.create_workspace ? local.resource_group.name : coalesce(var.ngfw_metrics.resource_group_name, local.resource_group.name) + location = var.location + + log_analytics_workspace = { + sku = var.ngfw_metrics.sku + metrics_retention_in_days = var.ngfw_metrics.metrics_retention_in_days + } + + application_insights = { for k, v in var.vmseries : k => { name = "${var.name_prefix}${v.name}-ai" } } + + tags = var.tags +} + +resource "local_file" "bootstrap_xml" { + for_each = { + for k, v in var.vmseries : + k => v.virtual_machine + if try(v.virtual_machine.bootstrap_package.bootstrap_xml_template != null, false) + } + + filename = "files/${each.key}-bootstrap.xml" + content = templatefile( + each.value.bootstrap_package.bootstrap_xml_template, + { + private_azure_router_ip = cidrhost( + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.private_snet_key], + 1 + ) + + public_azure_router_ip = cidrhost( + module.vnet[each.value.vnet_key].subnet_cidrs[each.value.bootstrap_package.public_snet_key], + 1 + ) + + ai_instr_key = try( + module.ngfw_metrics[0].metrics_instrumentation_keys[each.key], + null + ) + + ai_update_interval = 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] + ) + + mgmt_profile_appgw_cidr = flatten([ + for _, v in var.appgws : var.vnets[v.vnet_key].subnets[v.subnet_key].address_prefixes + ]) + } + ) + + depends_on = [ + module.ngfw_metrics, + module.vnet + ] +} + +locals { + bootstrap_file_shares_flat = flatten([ + for k, v in var.vmseries : + merge(v.virtual_machine.bootstrap_package, { vm_key = k }) + if v.virtual_machine.bootstrap_package != null + ]) + + bootstrap_file_shares = { for k, v in var.bootstrap_storages : k => { + for file_share in local.bootstrap_file_shares_flat : file_share.vm_key => { + name = file_share.vm_key + bootstrap_package_path = file_share.bootstrap_package_path + bootstrap_files = merge( + file_share.static_files, + file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = "config/bootstrap.xml" + } + ) + bootstrap_files_md5 = file_share.bootstrap_xml_template == null ? {} : { + "files/${file_share.vm_key}-bootstrap.xml" = local_file.bootstrap_xml[file_share.vm_key].content_md5 + } + } if file_share.bootstrap_storage_key == k } + } +} + +module "bootstrap" { + source = "../../modules/bootstrap" + + for_each = var.bootstrap_storages + + storage_account = each.value.storage_account + name = each.value.name + resource_group_name = coalesce(each.value.resource_group_name, local.resource_group.name) + location = var.location + + storage_network_security = merge( + each.value.storage_network_security, + each.value.storage_network_security.vnet_key == null ? {} : { + allowed_subnet_ids = [ + for v in each.value.storage_network_security.allowed_subnet_keys : + module.vnet[each.value.storage_network_security.vnet_key].subnet_ids[v] + ] } + ) + file_shares_configuration = each.value.file_shares_configuration + file_shares = local.bootstrap_file_shares[each.key] + + tags = var.tags +} + +resource "azurerm_availability_set" "this" { + for_each = var.availability_sets + + name = "${var.name_prefix}${each.value.name}" + resource_group_name = local.resource_group.name + location = var.location + platform_update_domain_count = each.value.update_domain_count + platform_fault_domain_count = each.value.fault_domain_count + + tags = var.tags +} + +module "vmseries" { + source = "../../modules/vmseries" + + for_each = var.vmseries + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + + authentication = local.authentication[each.key] + image = each.value.image + virtual_machine = merge( + each.value.virtual_machine, + { + disk_name = "${var.name_prefix}${coalesce(each.value.virtual_machine.disk_name, "${each.value.name}-osdisk")}" + avset_id = try(azurerm_availability_set.this[each.value.virtual_machine.avset_key].id, null) + bootstrap_options = try( + coalesce( + each.value.virtual_machine.bootstrap_options, + join(",", [ + "storage-account=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_name}", + "access-key=${module.bootstrap[each.value.virtual_machine.bootstrap_package.bootstrap_storage_key].storage_account_primary_access_key}", + "file-share=${each.key}", + "share-directory=None" + ]), + ), + null + ) + } + ) + + interfaces = [for v in each.value.interfaces : { + name = "${var.name_prefix}${v.name}" + subnet_id = module.vnet[each.value.virtual_machine.vnet_key].subnet_ids[v.subnet_key] + create_public_ip = v.create_public_ip + public_ip_name = v.create_public_ip ? "${var.name_prefix}${coalesce(v.public_ip_name, "${v.name}-pip")}" : v.public_ip_name + public_ip_resource_group_name = v.public_ip_resource_group_name + private_ip_address = v.private_ip_address + attach_to_lb_backend_pool = v.load_balancer_key != null || v.gwlb_key != null + lb_backend_pool_id = try( + module.load_balancer[v.load_balancer_key].backend_pool_id, + try( + module.gwlb[v.gwlb_key].backend_pool_ids[v.gwlb_backend_key], + null + ) + ) + }] + + tags = var.tags + depends_on = [ + module.vnet, + azurerm_availability_set.this, + module.load_balancer, + module.bootstrap, + ] +} + +module "appgw" { + source = "../../modules/appgw" + + for_each = var.appgws + + name = each.value.name + public_ip = each.value.public_ip + resource_group_name = local.resource_group.name + location = var.location + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + + managed_identities = each.value.managed_identities + capacity = each.value.capacity + waf = each.value.waf + enable_http2 = each.value.enable_http2 + zones = each.value.zones + + frontend_ip_configuration_name = each.value.frontend_ip_configuration_name + listeners = each.value.listeners + backend_pool = { + name = "vmseries" + vmseries_ips = [ + for k, v in var.vmseries : module.vmseries[k].interfaces[ + "${var.name_prefix}${v.name}-${each.value.vmseries_public_nic_name}" # TODO: fix this so that we do not need to use vmseries_public_nic_name + ].private_ip_address if try(v.add_to_appgw_backend, false) + ] + } + backends = each.value.backends + probes = each.value.probes + rewrites = each.value.rewrites + rules = each.value.rules + redirects = each.value.redirects + url_path_maps = each.value.url_path_maps + + ssl_global = each.value.ssl_global + ssl_profiles = each.value.ssl_profiles + + tags = var.tags + depends_on = [module.vnet] +} + +module "appvm" { + for_each = var.appvms + source = "../../modules/virtual_machine" + + name = "${var.name_prefix}${each.value.name}" + location = var.location + resource_group_name = local.resource_group.name + avzone = each.value.avzone + + interfaces = [ + { + name = "${var.name_prefix}${each.value.name}" + subnet_id = module.vnet[each.value.vnet_key].subnet_ids[each.value.subnet_key] + enable_backend_pool = true + lb_backend_pool_id = module.load_balancer[each.value.load_balancer_key].backend_pool_id + }, + ] + + username = try(each.value.username, null) + password = try(each.value.password, null) + ssh_keys = try(each.value.ssh_keys, []) + custom_data = try(each.value.custom_data, null) + + vm_size = try(each.value.vm_size, "Standard_B1ls") + managed_disk_type = try(each.value.disk_type, "Standard_LRS") + accelerated_networking = try(each.value.accelerated_networking, false) + + tags = var.tags +} \ No newline at end of file diff --git a/examples/gwlb_with_vmseries/main_test.go b/examples/gwlb_with_vmseries/main_test.go new file mode 100644 index 00000000..22bfe9ee --- /dev/null +++ b/examples/gwlb_with_vmseries/main_test.go @@ -0,0 +1,81 @@ +package gwlb_with_vmseries + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +// TODO: update Terratest + +func CreateTerraformOptions(t *testing.T) *terraform.Options { + // prepare random prefix + randomNames, _ := testskeleton.GenerateTerraformVarsInfo("azure") + storageDefinition := fmt.Sprintf("{ bootstrap = { name = \"%s\", public_snet_key = \"public\", private_snet_key = \"private\", intranet_cidr = \"10.0.1.0/24\"} }", randomNames.AzureStorageAccountName) + + // copy the init-cfg.sample.txt file to init-cfg.txt for test purposes + _, err := os.Stat("files/init-cfg.txt") + if err != nil { + buff, err := os.ReadFile("files/init-cfg.sample.txt") + if err != nil { + log.Fatal(err) + } + err = os.WriteFile("files/init-cfg.txt", buff, 0644) + if err != nil { + log.Fatal(err) + } + } + + // define options for Terraform + terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: ".", + VarFiles: []string{"example.tfvars"}, + Vars: map[string]interface{}{ + "name_prefix": randomNames.NamePrefix, + "resource_group_name": randomNames.AzureResourceGroupName, + "bootstrap_storages": storageDefinition, + }, + Logger: logger.Default, + Lock: true, + Upgrade: true, + SetVarsAfterVarFiles: true, + }) + + return terraformOptions +} + +func TestValidate(t *testing.T) { + testskeleton.ValidateCode(t, nil) +} + +func TestPlan(t *testing.T) { + // define options for Terraform + terraformOptions := CreateTerraformOptions(t) + // prepare list of items to check + assertList := []testskeleton.AssertExpression{} + // plan test infrastructure and verify outputs + testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected") +} + +func TestApply(t *testing.T) { + // define options for Terraform + terraformOptions := CreateTerraformOptions(t) + // prepare list of items to check + assertList := []testskeleton.AssertExpression{} + // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment + testskeleton.DeployInfraCheckOutputs(t, terraformOptions, assertList) +} + +func TestIdempotence(t *testing.T) { + // define options for Terraform + terraformOptions := CreateTerraformOptions(t) + // prepare list of items to check + assertList := []testskeleton.AssertExpression{} + // deploy test infrastructure and verify outputs and check if there are no planned changes after deployment + testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList) +} diff --git a/examples/gwlb_with_vmseries/outputs.tf b/examples/gwlb_with_vmseries/outputs.tf new file mode 100644 index 00000000..f946a80b --- /dev/null +++ b/examples/gwlb_with_vmseries/outputs.tf @@ -0,0 +1,39 @@ +output "usernames" { + description = "Initial administrative username to use for VM-Series." + value = { for k, v in local.authentication : k => v.username } +} + +output "passwords" { + description = "Initial administrative password to use for VM-Series." + value = { for k, v in local.authentication : k => v.password } + sensitive = true +} + +output "natgw_public_ips" { + description = "Nat Gateways Public IP resources." + value = length(var.natgws) > 0 ? { for k, v in module.natgw : k => { + pip = v.natgw_pip + pip_prefix = v.natgw_pip_prefix + } } : null +} + +output "metrics_instrumentation_keys" { + description = "The Instrumentation Key of the created instance(s) of Azure Application Insights." + value = try(module.ngfw_metrics[0].metrics_instrumentation_keys, null) + sensitive = true +} + +output "lb_frontend_ips" { + description = "IP Addresses of the load balancers." + value = length(var.load_balancers) > 0 ? { for k, v in module.load_balancer : k => v.frontend_ip_configs } : null +} + +output "vmseries_mgmt_ips" { + description = "IP addresses for the VM-Series management interface." + value = { for k, v in module.vmseries : k => v.mgmt_ip_address } +} + +output "bootstrap_storage_urls" { + value = length(var.bootstrap_storages) > 0 ? { for k, v in module.bootstrap : k => v.file_share_urls } : null + sensitive = true +} diff --git a/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl new file mode 100644 index 00000000..ffaf7ed3 --- /dev/null +++ b/examples/gwlb_with_vmseries/templates/bootstrap-gwlb.tftpl @@ -0,0 +1,520 @@ + + + + + + * + + + yes + + + + + + yes + 8 + + + + + + + + + + + + yes + 5 + + + yes + 5 + + + yes + 5 + + + yes + 10 + + + yes + 5 + + + + yes + + + + 10 + 10 + + 100 + 50 + + + + 10 + 10 + + 100 + 50 + + + + + + 100 + yes + + + + + + + + + + + + no + + + + no + + + no + + + no + + gwlb-healthcheck + + no + + + + + + + no + + + + + no + + + no + + + no + + 1 + + + + + + no + + + + + no + + + no + + + no + + 2 + + + + + + + + + + 3 + 5 + wait-recover + + + + + yes + + + + + + + + + + + + aes-128-cbc + 3des + + + sha1 + + + group2 + + + 8 + + + + + aes-128-cbc + + + sha256 + + + group19 + + + 8 + + + + + aes-256-cbc + + + sha384 + + + group20 + + + 8 + + + + + + + + aes-128-cbc + 3des + + + sha1 + + + group2 + + 1 + + + + + + aes-128-gcm + + + none + + + group19 + + 1 + + + + + + aes-256-gcm + + + none + + + group20 + + 1 + + + + + + + aes-128-cbc + + + sha1 + + + + + + + + + + + + + real-time + + + high + + + high + + + medium + + + medium + + + low + + + low + + + low + + + + + + + + + + + + no + + + 1.25 + 0.5 + 900 + 300 + 900 + yes + + + + + yes + + + + + no + + + no + + + no + + + + ethernet1/1 + ethernet1/1.1 + ethernet1/1.2 + + + + + + + + + + + + no + any + 2 + + + ${data_gateway_ip} + + + None + + ethernet1/1 + 10 + 168.63.129.16/32 + + + + + + + + + + + + + + + yes + no + no + no + + + updates.paloaltonetworks.com + + + + + wednesday + 01:02 + download-only + + + + + US/Pacific + + yes + yes + + + + 0.us.pool.ntp.org + + + + + + 1.us.pool.ntp.org + + + + + + + + + yes + + + FQDN + + panadmin + + + yes + no + no + no + + + + + +%{ if ai_instr_key != null ~} + + + + yes + ${ai_instr_key} + ${ai_update_interval} + + + +%{ endif ~} + + + + + + + + + + ethernet1/1.2 + + + + + + + ethernet1/1 + ethernet1/1.1 + + + + + + + + + + + + + any + + + any + + + any + + + any + + + any + + + any + + + any + + + service-http + service-https + + + any + + + any + + allow + + + + + + + + ethernet1/1 + ethernet1/1.1 + ethernet1/1.2 + + + + + + + + diff --git a/examples/gwlb_with_vmseries/variables.tf b/examples/gwlb_with_vmseries/variables.tf new file mode 100644 index 00000000..96f4d143 --- /dev/null +++ b/examples/gwlb_with_vmseries/variables.tf @@ -0,0 +1,789 @@ +### GENERAL +variable "tags" { + description = "Map of tags to assign to the created resources." + default = {} + type = map(string) +} + +variable "location" { + description = "The Azure region to use." + type = string +} + +variable "name_prefix" { + description = <<-EOF + A prefix that will be added to all created resources. + There is no default delimiter applied between the prefix and the resource name. + Please include the delimiter in the actual prefix. + + Example: + ``` + name_prefix = "test-" + ``` + + **Note!** \ + This prefix is not applied to existing resources. If you plan to reuse i.e. a VNET please specify it's full name, + even if it is also prefixed with the same value as the one in this property. + EOF + default = "" + type = string +} + +variable "create_resource_group" { + description = <<-EOF + When set to `true` it will cause a Resource Group creation. + Name of the newly specified RG is controlled by `resource_group_name`. + + When set to `false` the `resource_group_name` parameter is used to specify a name of an existing Resource Group. + EOF + default = true + type = bool +} + +variable "resource_group_name" { + description = "Name of the Resource Group." + type = string +} + +variable "enable_zones" { + description = "If `true`, enable zone support for resources." + default = true + type = bool +} + + + +### VNET +variable "vnets" { + description = <<-EOF + A map defining VNETs. + + For detailed documentation on each property refer to [module documentation](../../modules/vnet/README.md) + + - `create_virtual_network` - (`bool`, optional, defaults to `true`) when set to `true` will create a VNET, + `false` will source an existing VNET. + - `name` - (`string`, required) a name of a VNET. In case `create_virtual_network = false` this should be + a full resource name, including prefixes. + - `address_space` - (`list(string)`, required when `create_virtual_network = false`) a list of CIDRs for a newly + created VNET + - `resource_group_name` - (`string`, optional, defaults to current RG) a name of an existing Resource Group in which + the VNET will reside or is sourced from + - `create_subnets` - (`bool`, optional, defaults to `true`) if `true`, create Subnets inside the Virtual Network, + otherwise use source existing subnets + - `subnets` - (`map`, optional) map of Subnets to create or source, for details see + [VNET module documentation](../../modules/vnet/README.md#subnets) + - `network_security_groups` - (`map`, optional) map of Network Security Groups to create, for details see + [VNET module documentation](../../modules/vnet/README.md#network_security_groups) + - `route_tables` - (`map`, optional) map of Route Tables to create, for details see + [VNET module documentation](../../modules/vnet/README.md#route_tables) + EOF + + type = map(object({ + name = string + resource_group_name = optional(string) + create_virtual_network = optional(bool, true) + address_space = optional(list(string)) + network_security_groups = optional(map(object({ + name = string + rules = optional(map(object({ + name = string + priority = number + direction = string + access = string + protocol = string + source_port_range = optional(string) + source_port_ranges = optional(list(string)) + destination_port_range = optional(string) + destination_port_ranges = optional(list(string)) + source_address_prefix = optional(string) + source_address_prefixes = optional(list(string)) + destination_address_prefix = optional(string) + destination_address_prefixes = optional(list(string)) + })), {}) + })), {}) + route_tables = optional(map(object({ + name = string + disable_bgp_route_propagation = optional(bool) + routes = map(object({ + name = string + address_prefix = string + next_hop_type = string + next_hop_ip_address = optional(string) + })) + })), {}) + create_subnets = optional(bool, true) + subnets = optional(map(object({ + name = string + address_prefixes = optional(list(string), []) + network_security_group_key = optional(string) + route_table_key = optional(string) + enable_storage_service_endpoint = optional(bool, false) + })), {}) + })) +} + +variable "natgws" { + description = <<-EOF + A map defining NAT Gateways. + + Please note that a NAT Gateway is a zonal resource, this means it's always placed in a zone (even when you do not specify one + explicitly). Please refer to Microsoft documentation for notes on NAT Gateway's zonal resiliency. + For detailed documentation on each property refer to [module documentation](../../modules/natgw/README.md). + + Following properties are supported: + - `create_natgw` - (`bool`, optional, defaults to `true`) create (`true`) or source an existing NAT Gateway (`false`), + created or sourced: the NAT Gateway will be assigned to a subnet created by the `vnet` module. + - `name` - (`string`, required) a name of a NAT Gateway. In case `create_natgw = false` this should be a full + resource name, including prefixes. + - `resource_group_name - (`string`, optional) name of a Resource Group hosting the NAT Gateway (newly created or the existing + one). + - `zone` - (`string`, optional) an Availability Zone in which the NAT Gateway will be placed, when skipped + AzureRM will pick a zone. + - `idle_timeout` - (`number`, optional, defults to 4) connection IDLE timeout in minutes, for newly created resources. + - `vnet_key` - (`string`, required) a name (key value) of a VNET defined in `var.vnets` that hosts a subnet this + NAT Gateway will be assigned to. + - `subnet_keys` - (`list(string)`, required) a list of subnets (key values) the NAT Gateway will be assigned to, defined + in `var.vnets` for a VNET described by `vnet_name`. + - `public_ip` - (`object`, optional) an object defining a public IP resource attached to the NAT Gateway. + - `public_ip_prefix` - (`object`, optional) an object defining a public IP prefix resource attached to the NAT Gatway. + + Example: + ``` + natgws = { + "natgw" = { + name = "natgw" + vnet_key = "transit-vnet" + subnet_keys = ["management"] + public_ip = { + create = true + name = "natgw-pip" + } + } + } + ``` + EOF + default = {} + type = map(object({ + create_natgw = optional(bool, true) + name = string + resource_group_name = optional(string) + zone = optional(string) + idle_timeout = optional(number, 4) + vnet_key = string + subnet_keys = list(string) + public_ip = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + })) + public_ip_prefix = optional(object({ + create = bool + name = string + resource_group_name = optional(string) + length = optional(number) + })) + })) +} + + + +### Load Balancing +variable "load_balancers" { + description = <<-EOF + A map containing configuration for all (private and public) Load Balancers. + + This is a brief description of available properties. For a detailed one please refer to + [module documentation](../../modules/loadbalancer/README.md). + + Following properties are available: + + - `name` - (`string`, required) a name of the Load Balancer + - `zones` - (`list`, optional, defaults to `["1", "2", "3"]`) list of zones the resource will be + available in, please check the + [module documentation](../../modules/loadbalancer/README.md#zones) for more details + - `health_probes` - (`map`, optional, defaults to `null`) a map defining health probes that will be used by + load balancing rules; + please check [module documentation](../../modules/loadbalancer/README.md#health_probes) + for more specific use cases and available properties + - `nsg_auto_rules_settings` - (`map`, optional, defaults to `null`) a map defining a location of an existing NSG rule + that will be populated with `Allow` rules for each load balancing rule (`in_rules`); please check + [module documentation](../../modules/loadbalancer/README.md#nsg_auto_rules_settings) + for available properties; please note that in this example two additional properties are + available: + - `nsg_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to an NSG definition + in the `var.vnets` map + - `nsg_vnet_key` - (`string`, optional, mutually exclusive with `nsg_name`) a key pointing to a VNET definition + in the `var.vnets` map that stores the NSG described by `nsg_key` + - `frontend_ips` - (`map`, optional, defaults to `{}`) a map containing frontend IP configuration with respective + `in_rules` and `out_rules` + + Please refer to [module documentation](../../modules/loadbalancer/README.md#frontend_ips) for available properties. + + > [!NOTE] + > In this example the `subnet_id` is not available directly, three other properties were introduced instead. + + - `subnet_key` - (`string`, optional, defaults to `null`) a key pointing to a Subnet definition in the `var.vnets` map + - `vnet_key` - (`string`, optional, defaults to `null`) a key pointing to a VNET definition in the `var.vnets` map + that stores the Subnet described by `subnet_key` + EOF + default = {} + nullable = false + type = map(object({ + name = string + zones = optional(list(string), ["1", "2", "3"]) + health_probes = optional(map(object({ + name = string + protocol = string + port = optional(number) + probe_threshold = optional(number) + interval_in_seconds = optional(number) + request_path = optional(string) + }))) + nsg_auto_rules_settings = optional(object({ + nsg_name = optional(string) + nsg_vnet_key = optional(string) + nsg_key = optional(string) + nsg_resource_group_name = optional(string) + source_ips = list(string) + base_priority = optional(number) + })) + frontend_ips = optional(map(object({ + name = string + public_ip_name = optional(string) + create_public_ip = optional(bool, false) + public_ip_resource_group_name = optional(string) + vnet_key = optional(string) + subnet_key = optional(string) + private_ip_address = optional(string) + gwlb_key = optional(string) + in_rules = optional(map(object({ + name = string + protocol = string + port = number + backend_port = optional(number) + health_probe_key = optional(string) + floating_ip = optional(bool) + session_persistence = optional(string) + nsg_priority = optional(number) + })), {}) + out_rules = optional(map(object({ + name = string + protocol = string + allocated_outbound_ports = optional(number) + enable_tcp_reset = optional(bool) + idle_timeout_in_minutes = optional(number) + })), {}) + })), {}) + })) +} + + +variable "gateway_load_balancers" { + description = <<-EOF + Map with Gateway Load Balancer definitions. Following settings are supported: + + TODO: adjust type and description + + Please consult [module documentation](../../modules/gwlb/README.md) for details. + EOF + default = {} + type = any +} + + +variable "availability_sets" { + description = <<-EOF + A map defining availability sets. Can be used to provide infrastructure high availability when zones cannot be used. + + Following properties are supported: + - `name` - name of the Application Insights. + - `update_domain_count` - specifies the number of update domains that are used, defaults to 5 (Azure defaults). + - `fault_domain_count` - specifies the number of fault domains that are used, defaults to 3 (Azure defaults). + + Please keep in mind that Azure defaults are not working for each region (especially the small ones, w/o any Availability Zones). + Please verify how many update and fault domain are supported in a region before deploying this resource. + EOF + default = {} + nullable = false + type = map(object({ + name = string + update_domain_count = optional(number, 5) + fault_domain_count = optional(number, 3) + })) +} + + +variable "ngfw_metrics" { + description = <<-EOF + A map controlling metrics-relates resources. + + When set to explicit `null` (default) it will disable any metrics resources in this deployment. + + When defined it will either create or source a Log Analytics Workspace and create Application Insights instances (one per each + Scale Set). All instances will be automatically connected to the workspace. + The name of the Application Insights instance will be derived from the Scale Set name and suffixed with `-ai`. + + All the settings available below are common to the Log Analytics Workspace and Application Insight instances. + + Following properties are available: + + - `name` - (`string`, required) name of the (common) Log Analytics Workspace + - `create_workspace` - (`bool`, optional, defaults to `true`) controls whether we create or source an existing Log + Analytics Workspace + - `resource_group_name` - (`string`, optional, defaults to `var.resource_group_name`) name of the Resource Group hosting + the Log Analytics Workspace + - `sku` - (`string`, optional, defaults to module defaults) the SKU of the Log Analytics Workspace. + - `metrics_retention_in_days` - (`number`, optional, defaults to module defaults) workspace and insights data retention in + days, possible values are between 30 and 730. For sourced Workspaces this applies only to + the Application Insights instances. + EOF + default = null + type = object({ + name = string + create_workspace = optional(bool, true) + resource_group_name = optional(string) + sku = optional(string) + metrics_retention_in_days = optional(number) + }) +} + +variable "bootstrap_storages" { + description = <<-EOF + A map defining Azure Storage Accounts used to host file shares for bootstrapping NGFWs. + + You can create or re-use an existing Storage Account and/or File Share. For details on all available properties please refer to + [module's documentation](../../modules/bootstrap/README.md). Following is just an extract of the most important ones: + + - `name` - (`string`, required) name of the Storage Account that will be created or sourced. + + **Note** \ + For new Storage Accounts this name will not be prefixed with `var.name_prefix`. \ + Please note the limitations on naming. This has to be a globally unique name, between 3 and 63 chars, only lower-case + letters and numbers. + + - `resource_group_name` - (`string`, optional, defaults to `null`) name of the Resource Group that hosts (sourced) or will + host (created) a Storage Account. When skipped the code will fall back to + `var.resource_group_name`. + - `storage_account` - (`map`, optional, defaults to `{}`) a map controlling basic Storage Account configuration, for + detailed documentation see + [module's documentation](../../modules/bootstrap/README.md#storage_account). The property you + should pay attention to is: + - `create` - (`bool`, optional, defaults to module defaults) controls if the Storage Account specified in + the `name` property will be created or sourced. + - `storage_network_security` - (`map`, optional, defaults to `{}`) a map defining network security settings for a **new** + storage account, for details see + [module's documentation](../../modules/bootstrap/README.md#storage_network_security). Properties + worth mentioning are: + - `allowed_subnet_keys` - (`list`, optional, defaults to `[]`) a list of keys pointing to Subnet definitions in the + `var.vnets` map. These Subnets will have dedicated access to the Storage Account. For this to + work they also need to have the Storage Account Service Endpoint enabled. + - `vnet_key` - a key pointing to a VNET definition in the `var.vnets` map that stores the Subnets described + in `allowed_subnet_keys`. + - `file_shares_configuration` - (`map`, optional, defaults to `{}`) a map defining common File Share setting. For detailed + documentation see + [module's documentation](../../modules/bootstrap/README.md#file_shares_configuration). The + properties you should pay your attention to are: + - `create_file_shares` - (`bool`, optional, defaults to module defaults) controls if the File Shares defined in the + `file_shares` property will be created or sourced. + - `disable_package_dirs_creation` - (`bool`, optional, defaults to module defaults) for sourced File Shares, controls if the + bootstrap package folder structure will be created. + - `file_shares` - (`map`, optional, defaults to `{}`) a map that holds File Shares and bootstrap package + configuration. For detailed description see + [module's documentation](../../modules/bootstrap/README.md#file_shares). + + EOF + default = {} + nullable = false + type = map(object({ + name = string + resource_group_name = optional(string) + storage_account = optional(object({ + create = optional(bool) + replication_type = optional(string) + kind = optional(string) + tier = optional(string) + }), {}) + storage_network_security = optional(object({ + min_tls_version = optional(string) + allowed_public_ips = optional(list(string)) + vnet_key = optional(string) + allowed_subnet_keys = optional(list(string), []) + }), {}) + file_shares_configuration = optional(object({ + create_file_shares = optional(bool) + disable_package_dirs_creation = optional(bool) + quota = optional(number) + access_tier = optional(string) + }), {}) + file_shares = optional(map(object({ + name = string + bootstrap_package_path = optional(string) + bootstrap_files = optional(map(string)) + bootstrap_files_md5 = optional(map(string)) + quota = optional(number) + access_tier = optional(string) + })), {}) + })) +} + +variable "vmseries" { + description = <<-EOF + A map defining Azure Virtual Machines based on Palo Alto Networks Next Generation Firewall image.. + + For details and defaults for available options please refer to the [`vmseries`](../../modules/vmseries/README.md) module. + + The most basic properties are as follows: + + - `name` - (`string`, required) name of the VM, will be prefixed with the value of `var.name_prefix`. + - `authentication` - (`map`, optional, defaults to example defaults) authentication settings for the deployed VM. + + The `authentication` property is optional and holds the firewall admin access details. By default, standard username + `panadmin` will be set and a random password will be auto-generated for you (available in Terraform outputs). + + **Note!** \ + The `disable_password_authentication` property is by default `false` in this example. When using this value, you don't have + to specify anything but you can still additionally pass SSH keys for authentication. You can however set this property to + `true`, then you have to specify `ssh_keys` property. + + For all properties and their default values see [module's documentation](../../modules/vmseries/README.md#authentication). + + - `image` - (`map`, required) properties defining a base image used by the deployed VM. + + The `image` property is required but there are only 2 properties (mutually exclusive) that have to be set, either: + + - `version` - (`string`) describes the PAN-OS image version from Azure Marketplace. + - `custom_id` - (`string`) absolute ID of your own custom PAN-OS image. + + For details on the other properties refer to [module's documentation](../../modules/vmseries/README.md#image). + + - `virtual_machine` - (`map`, optional, defaults to module defaults) a map that groups most common VM configuration options. + + The most often used option are as follows: + + - `vnet_key` - (`string`, required) a key of a VNET defined in `var.vnets`. This is the VNET that hosts subnets used to + deploy network interfaces for deployed VM. + - `size` - (`string`, optional, defaults to module defaults) Azure VM size (type). Consult the *VM-Series Deployment + Guide* as only a few selected sizes are supported. + - `zone` - (`string`, optional, defaults to module defaults) the Availability Zone in which the VM and (if deployed) + public IP addresses will be created. + - `disk_type` - (`string`, optional, defaults to module defaults) type of a Managed Disk which should be created, possible + values are `Standard_LRS`, `StandardSSD_LRS` or `Premium_LRS` (works only for selected `size` values). + - `bootstrap_options` - (`string`, optional, mutually exclusive with `bootstrap_package`) bootstrap options passed to PAN-OS + when launched for the 1st time, for details see module documentation. + - `bootstrap_package` - (`map`, optional, mutually exclusive with `bootstrap_options`) a map defining content of the + bootstrap package. + + **Note!** \ + At least one of `static_files`, `bootstrap_xml_template` or `bootstrap_package_path` is required. You can use a + combination of all 3. The `bootstrap_package_path` is the less important. For details on this mechanism and for details + on the other properties see the [`bootstrap` module documentation](../../modules/bootstrap/README.md). + + Following properties are available: + + - `bootstrap_storage_key` - (`string`, required) a key of a bootstrap storage defined in `var.bootstrap_storages` that + will host bootstrap packages. Each package will be hosted on a separate File Share. + The File Shares will be created automatically, one for each firewall. + - `static_files` - (`map`, optional, defaults to `{}`) a map containing files that will be copied to a File + Share, see [`file_shares.bootstrap_files`](../../modules/bootstrap/README.md#file_shares) + property documentation for details. + - `bootstrap_package_path` - (`string`, optional, defaults to `null`) a path to a folder containing a full bootstrap + package. + - `bootstrap_xml_template` - (`string`, optional, defaults to `null`) a path to a `bootstrap.xml` template. If this + example is using full bootstrap method, the sample templates are in + [`templates`](./templates) folder. + + The templates are used to provide `day0` like configuration which consists of: + + - network interfaces configuration. + - one or more (depending on the architecture) Virtual Routers configurations. This config contains static routes + required for the Load Balancer (and Application Gateway, if defined) health checks to work and routes that allow + Inbound and OBEW traffic. + - *any-any* security rule. + - an outbound NAT rule that will allow the Outbound traffic to flow to the internet. + + **Note!** \ + Day0 configuration is **not meant** to be **secure**. It's here marly to help with the basic firewall setup. + + When `bootstrap_xml_template` is set, one of the following properties might be required. + + - `private_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a private Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a private + Load Balancer health checks and for Inbound traffic. + - `public_snet_key` - (`string`, required only when `bootstrap_xml_template` is set, defaults to `null`) a key + pointing to a public Subnet definition in `var.vnets` (the `vnet_key` property is used to + identify a VNET). The Subnet definition is used to calculate static routes for a public + Load Balancer health checks and for Outbound traffic. + - `ai_update_interval` - (`number`, optional, defaults to `5`) Application Insights update interval, used only when + `ngfw_metrics` module is defined and used in this example. The Application Insights + Instrumentation Key will be populated automatically. + - `intranet_cidr` - (`string`, optional, defaults to `null`) a CIDR of the Intranet - combined CIDR of all + private networks. When set it will override the private Subnet CIDR for inbound traffic + static routes. + + For details on the other properties refer to [module's documentation](../../modules/panorama/README.md#virtual_machine). + + - `interfaces` - (`list`, required) configuration of all network interfaces + + **Note!** \ + Order of the interfaces does matter - the 1st interface is the management one. + + For details on available properties please see [module's documentation](../../modules/panorama/README.md#interfaces). + + The most important ones are listed below: + + - `name` - (`string`, required) name of the network interface (will be prefixed with `var.name_prefix`). + - `subnet_key` - (`string`, required) a key of a subnet to which the interface will be assigned as defined in + `var.vnets`. Key identifying the VNET is defined in `virtual_machine.vnet_key` property. + - `create_public_ip` - (`bool`, optional, defaults to `false`) create a Public IP for an interface. + - `load_balancer_key` - (`string`, optional, defaults to `null`) key of a Load Balancer defined in `var.loadbalancers` + variable, network interface that has this property defined will be added to the Load Balancer's + backend pool + - `add_to_appgw_backend` - (`bool`, optional, defaults to `false`) when set an interface's private IP address will be added + to the Application Gateway's backend pool. + + EOF + default = {} + nullable = false + type = map(object({ + name = string + authentication = optional(object({ + username = optional(string, "panadmin") + password = optional(string) + disable_password_authentication = optional(bool, false) + ssh_keys = optional(list(string), []) + }), {}) + image = object({ + version = optional(string) + publisher = optional(string) + offer = optional(string) + sku = optional(string) + enable_marketplace_plan = optional(bool) + custom_id = optional(string) + }) + virtual_machine = object({ + vnet_key = string + size = optional(string) + bootstrap_options = optional(string) + bootstrap_package = optional(object({ + bootstrap_storage_key = string + static_files = optional(map(string), {}) + bootstrap_package_path = optional(string) + bootstrap_xml_template = optional(string) + private_snet_key = optional(string) + public_snet_key = optional(string) + ai_update_interval = optional(number, 5) + intranet_cidr = optional(string) + })) + zone = string + disk_type = optional(string) + disk_name = optional(string) + avset_key = optional(string) + accelerated_networking = optional(bool) + encryption_at_host_enabled = optional(bool) + disk_encryption_set_id = optional(string) + diagnostics_storage_uri = optional(string) + identity_type = optional(string) + identity_ids = optional(list(string)) + allow_extension_operations = optional(bool) + }) + interfaces = list(object({ + name = string + subnet_key = string + create_public_ip = optional(bool, false) + public_ip_name = optional(string) + public_ip_resource_group_name = optional(string) + private_ip_address = optional(string) + load_balancer_key = optional(string) + gwlb_key = optional(string) + gwlb_backend_key = optional(string) + add_to_appgw_backend = optional(bool, false) + })) + })) + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_options != null && v.virtual_machine.bootstrap_package == null || + v.virtual_machine.bootstrap_options == null && v.virtual_machine.bootstrap_package != null + ]) + error_message = "Either `bootstrap_options` or `bootstrap_package` property can be set." + } + validation { + condition = alltrue([ + for _, v in var.vmseries : + v.virtual_machine.bootstrap_package.bootstrap_xml_template != null ? v.virtual_machine.bootstrap_package.private_snet_key != null && v.virtual_machine.bootstrap_package.public_snet_key != null : true + if v.virtual_machine.bootstrap_package != null + ]) + error_message = "The `private_snet_key` and `public_snet_key` are required when `bootstrap_xml_template` is set." + } +} + +### Application Gateway +variable "appgws" { + description = <<-EOF + A map defining all Application Gateways in the current deployment. + + For detailed documentation on how to configure this resource, for available properties, especially for the defaults, refer to [module documentation](../../modules/appgw/README.md). + + Following properties are supported: + - `name` - (`string`, required) name of the Application Gateway. + - `public_ip` - (`string`, required) public IP address. + - `vnet_key` - (`string`, required) a key of a VNET defined in the `var.vnets` map. + - `subnet_key` - (`string`, required) a key of a subnet as defined in `var.vnets`. This has to be a subnet dedicated to Application Gateways v2. + - `managed_identities` - (`list`, optional) a list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault. + - `capacity` - (`number`, object) capacity configuration for Application Gateway (refer to [module documentation](../../modules/appgw/README.md) for details) + - `waf` - (`object`, required) WAF basic configuration, defining WAF rules is not supported + - `enable_http2` - (`bool`, optional) enable HTTP2 support on the Application Gateway + - `zones` - (`list`, required) for zonal deployment this is a list of all zones in a region - this property is used by both: the Application Gateway and the Public IP created in front of the AppGW. + - `frontend_ip_configuration_name` - (`string`, optional) frontend IP configuration name + - `vmseries_public_nic_name` - (`string`, optional) VM-Series NIC name, for which IP address will be used in backend pool + - `listeners` - (`map`, required) map of listeners (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backend_pool` - (`object`, optional) backend pool (refer to [module documentation](../../modules/appgw/README.md) for details) + - `backends` - (`map`, optional) map of backends (refer to [module documentation](../../modules/appgw/README.md) for details) + - `probes` - (`map`, optional) map of probes (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rewrites` - (`map`, optional) map of rewrites (refer to [module documentation](../../modules/appgw/README.md) for details) + - `rules` - (`map`, required) map of rules (refer to [module documentation](../../modules/appgw/README.md) for details) + - `redirects` - (`map`, optional) map of redirects (refer to [module documentation](../../modules/appgw/README.md) for details) + - `url_path_maps` - (`map`, optional) map of URL path maps (refer to [module documentation](../../modules/appgw/README.md) for details) + - `ssl_policy_type` - (`string`, optional) type of an SSL policy, defaults to `Predefined` + - `ssl_policy_name` - (`string`, optional) name of an SSL policy, for `ssl_policy_type` set to `Predefined` + - `ssl_policy_min_protocol_version` - (`string`, optional) minimum version of the TLS protocol for SSL Policy, for `ssl_policy_type` set to `Custom` + - `ssl_policy_cipher_suites` - (`list`, optional) a list of accepted cipher suites, for `ssl_policy_type` set to `Custom` + - `ssl_profiles` - (`map`, optional) a map of SSL profiles that can be later on referenced in HTTPS listeners by providing a name of the profile in the `ssl_profile_name` property + EOF + default = {} + nullable = false + type = map(object({ + name = string + public_ip = object({ + name = string + resource_group_name = optional(string) + create = optional(bool, true) + }) + vnet_key = string + subnet_key = string + managed_identities = optional(list(string)) + capacity = object({ + static = optional(number) + autoscale = optional(object({ + min = optional(number) + max = optional(number) + })) + }) + waf = optional(object({ + prevention_mode = bool + rule_set_type = optional(string, "OWASP") + rule_set_version = optional(string) + })) + enable_http2 = optional(bool) + zones = list(string) + frontend_ip_configuration_name = optional(string, "public_ipconfig") + vmseries_public_nic_name = optional(string, "public") + listeners = map(object({ + name = string + port = number + protocol = optional(string, "Http") + host_names = optional(list(string)) + ssl_profile_name = optional(string) + ssl_certificate_path = optional(string) + ssl_certificate_pass = optional(string) + ssl_certificate_vault_id = optional(string) + custom_error_pages = optional(map(string), {}) + })) + backend_pool = optional(object({ + name = string + vmseries_ips = optional(list(string), []) + })) + backends = optional(map(object({ + name = string + path = optional(string) + hostname_from_backend = optional(string) + hostname = optional(string) + port = optional(number, 80) + protocol = optional(string, "Http") + timeout = optional(number, 60) + cookie_based_affinity = optional(string, "Enabled") + affinity_cookie_name = optional(string) + probe = optional(string) + root_certs = optional(map(object({ + name = string + path = string + })), {}) + }))) + probes = optional(map(object({ + name = string + path = string + host = optional(string) + port = optional(number) + protocol = optional(string, "Http") + interval = optional(number, 5) + timeout = optional(number, 30) + threshold = optional(number, 2) + match_code = optional(list(number)) + match_body = optional(string) + })), {}) + rewrites = optional(map(object({ + name = optional(string) + rules = optional(map(object({ + name = string + sequence = number + conditions = optional(map(object({ + pattern = string + ignore_case = optional(bool, false) + negate = optional(bool, false) + })), {}) + request_headers = optional(map(string), {}) + response_headers = optional(map(string), {}) + }))) + })), {}) + rules = map(object({ + name = string + priority = number + backend = optional(string) + listener = string + rewrite = optional(string) + url_path_map = optional(string) + redirect = optional(string) + })) + redirects = optional(map(object({ + name = string + type = string + target_listener = optional(string) + target_url = optional(string) + include_path = optional(bool, false) + include_query_string = optional(bool, false) + })), {}) + url_path_maps = optional(map(object({ + name = string + backend = string + path_rules = optional(map(object({ + paths = list(string) + backend = optional(string) + redirect = optional(string) + }))) + })), {}) + ssl_global = optional(object({ + ssl_policy_type = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })) + ssl_profiles = optional(map(object({ + name = string + ssl_policy_name = optional(string) + ssl_policy_min_protocol_version = optional(string) + ssl_policy_cipher_suites = optional(list(string)) + })), {}) + })) +} + +variable "appvms" { + description = <<-EOF + Configuration for sample application VMs. Available settings: + + TODO: adjust type and description + + EOF + default = {} + type = any +} diff --git a/examples/gwlb_with_vmseries/versions.tf b/examples/gwlb_with_vmseries/versions.tf new file mode 100644 index 00000000..85620563 --- /dev/null +++ b/examples/gwlb_with_vmseries/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.5, < 2.0" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + } + random = { + source = "hashicorp/random" + } + http = { + source = "hashicorp/http" + } + } +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} From 0b2852ed9ed0757382b2161e6fb995122f73f9d4 Mon Sep 17 00:00:00 2001 From: Sebastian Czech Date: Tue, 23 Jan 2024 22:31:43 +0100 Subject: [PATCH 02/14] Adjust example to changes in bootstrap module --- examples/gwlb_with_vmseries/README.md | 13 +- examples/gwlb_with_vmseries/example.tfvars | 232 +++++++++++++-------- examples/gwlb_with_vmseries/main.tf | 20 +- examples/gwlb_with_vmseries/variables.tf | 17 +- 4 files changed, 156 insertions(+), 126 deletions(-) diff --git a/examples/gwlb_with_vmseries/README.md b/examples/gwlb_with_vmseries/README.md index 6f9a55ea..37f3afb1 100644 --- a/examples/gwlb_with_vmseries/README.md +++ b/examples/gwlb_with_vmseries/README.md @@ -746,14 +746,10 @@ The most basic properties are as follows: 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 + - `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 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. @@ -812,8 +808,7 @@ map(object({ static_files = optional(map(string), {}) bootstrap_package_path = optional(string) bootstrap_xml_template = optional(string) - private_snet_key = optional(string) - public_snet_key = optional(string) + data_snet_key = optional(string) ai_update_interval = optional(number, 5) intranet_cidr = optional(string) })) diff --git a/examples/gwlb_with_vmseries/example.tfvars b/examples/gwlb_with_vmseries/example.tfvars index 86769e85..968810b1 100644 --- a/examples/gwlb_with_vmseries/example.tfvars +++ b/examples/gwlb_with_vmseries/example.tfvars @@ -1,6 +1,6 @@ # --- GENERAL --- # location = "North Europe" -resource_group_name = "transit-vnet-common" +resource_group_name = "gwlb" name_prefix = "example-" tags = { "CreatedBy" = "Palo Alto Networks" @@ -29,60 +29,29 @@ vnets = { } } } - "public" = { - name = "public-nsg" + "data" = { + name = "data-nsg" } } route_tables = { "management" = { name = "mgmt-rt" routes = { - "private_blackhole" = { - name = "private-blackhole-udr" + "data_blackhole" = { + name = "data-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" - } } } - "private" = { - name = "private-rt" + "data" = { + name = "data-rt" routes = { - "default" = { - 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" - } - } - } - "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" - } } } } @@ -94,16 +63,39 @@ vnets = { route_table_key = "management" enable_storage_service_endpoint = true } - "private" = { - name = "private-snet" + "data" = { + name = "data-snet" address_prefixes = ["10.0.0.16/28"] - route_table_key = "private" + route_table_key = "data" } - "public" = { - name = "public-snet" - address_prefixes = ["10.0.0.32/28"] - network_security_group_key = "public" - route_table_key = "public" + } + } + "app1" = { + name = "app1" + address_space = ["10.0.2.0/25"] + network_security_groups = { + "application_inbound" = { + name = "application-inbound-nsg" + rules = { + app_inbound = { + name = "application-allow-inbound" + priority = 100 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_address_prefixes = ["134.238.135.14", "134.238.135.140"] + source_port_range = "*" + destination_address_prefix = "*" + destination_port_ranges = ["22", "80", "443"] + } + } + } + } + subnets = { + "app1" = { + name = "app1-snet" + address_prefixes = ["10.0.2.0/28"] + network_security_group_key = "application_inbound" } } } @@ -112,11 +104,11 @@ vnets = { # --- LOAD BALANCING PART --- # load_balancers = { - "public" = { - name = "public-lb" + "app1" = { + name = "app1-lb" nsg_auto_rules_settings = { - nsg_vnet_key = "transit" - nsg_key = "public" + nsg_vnet_key = "app1" + nsg_key = "application_inbound" source_ips = ["0.0.0.0/0"] } frontend_ips = { @@ -124,6 +116,7 @@ load_balancers = { name = "app1" public_ip_name = "public-lb-app1-pip" create_public_ip = true + gwlb_key = "gwlb" in_rules = { "balanceHttp" = { name = "HTTP" @@ -131,30 +124,71 @@ load_balancers = { port = 80 } } + out_rules = { + outbound = { + name = "tcp-outbound" + protocol = "Tcp" + } + } } } } - "private" = { - name = "private-lb" - frontend_ips = { - "ha-ports" = { - name = "private-vmseries" - vnet_key = "transit" - subnet_key = "private" - private_ip_address = "10.0.0.30" - in_rules = { - HA_PORTS = { - name = "HA-ports" - port = 0 - protocol = "All" +} + +# --- GWLB PART --- # +gateway_load_balancers = { + gwlb = { + name = "vmseries-gwlb" + + frontend_ip = { + vnet_key = "transit" + subnet_key = "data" + } + + health_probe = { + name = "custom-health-probe" + port = 80 + protocol = "Tcp" + } + + backends = { + backend = { + name = "custom-backend" + tunnel_interfaces = { + internal = { + identifier = 800 + port = 2000 + protocol = "VXLAN" + type = "Internal" + } + external = { + identifier = 801 + port = 2001 + protocol = "VXLAN" + type = "External" } } } } + + lb_rule = { + name = "custom-lb-rule" + } } } # --- VMSERIES PART --- # +bootstrap_storages = { + "bootstrap" = { + name = "examplegwlbbootstrap" + storage_network_security = { + vnet_key = "transit" + allowed_subnet_keys = ["management"] + allowed_public_ips = ["134.238.135.14", "134.238.135.140"] + } + } +} + vmseries = { "fw-1" = { name = "firewall01" @@ -162,10 +196,15 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 1 - bootstrap_options = "type=dhcp-client" + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 1 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap-gwlb.tftpl" + data_snet_key = "data" + } } interfaces = [ { @@ -174,15 +213,10 @@ vmseries = { create_public_ip = true }, { - name = "vm01-private" - subnet_key = "private" - load_balancer_key = "private" - }, - { - name = "vm01-public" - subnet_key = "public" - create_public_ip = true - load_balancer_key = "public" + name = "vm01-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" } ] } @@ -192,10 +226,15 @@ vmseries = { version = "10.2.3" } virtual_machine = { - vnet_key = "transit" - size = "Standard_DS3_v2" - zone = 2 - bootstrap_options = "type=dhcp-client" + vnet_key = "transit" + size = "Standard_DS3_v2" + zone = 2 + bootstrap_package = { + bootstrap_storage_key = "bootstrap" + static_files = { "files/init-cfg.txt" = "config/init-cfg.txt" } + bootstrap_xml_template = "templates/bootstrap-gwlb.tftpl" + data_snet_key = "data" + } } interfaces = [ { @@ -204,16 +243,31 @@ vmseries = { create_public_ip = true }, { - name = "vm02-private" - subnet_key = "private" - load_balancer_key = "private" - }, - { - name = "vm02-public" - subnet_key = "public" - create_public_ip = true - load_balancer_key = "public" + name = "vm02-data" + subnet_key = "data" + gwlb_key = "gwlb" + gwlb_backend_key = "backend" } ] } } + + +appvms = { + app1vm01 = { + name = "app1-vm01" + avzone = "3" + vnet_key = "app1" + subnet_key = "app1" + load_balancer_key = "app1" + username = "appadmin" + custom_data = <