Skip to main content

Palo Alto Networks VNet Module for Azure

A terraform module for deploying an Application Gateway v2. The module is dedicated to work with the Next Generation Firewalls, hence it supports only one backend. It supports only v2 and WAF v2 Gateways.

In the center of module's configuration is the rules property. See the the rules property explained and rules property examples topics for more details.

GitHub Logo Terraform Logo

Rules property explained

The rules property combines configuration for several Application Gateway components and groups them by a logical application. In other words an application defines a listener, http settings, health check probe, redirect rules, rewrite rule sets or url path maps (some fo them are mutually exclusive, check details on each of them below). Those are always unique for an application, meaning that you cannot share them between application definitions. Most of settings are optional and depend on a use case. The only one that is required is the listener port and the priority of the rule.

In general rules property is a map where key is the logical application name and value is a set of properties, like below:

rules = {
"redirect_2_app_1 = {
priority = 1
listener = {
port = 80
}
redirect = {
type = "Temporary"
target_listener_name = "application_1"
include_path = true
include_query_string = true
}
}
"application_1" = {
priority = 2
listener = {
port = 443
protocol = "Https"
ssl_certificate_path = "/path/to/cert"
ssl_certificate_pass = "cert_password"
}
}
}

The example above is a setup where the Application Gateway serves only as a reverse proxy terminating SSL connections (by default all traffic sent to the backend pool is sent to port 80, plain text). It also redirects all http communication sent to listener port 80 to https on port 443.

As you can see in the target_listener_name property, all Application Gateway component created for an application are equal to the application name (so the key value).

For each application one can configure the following properties:

  • priority - rule's priority
  • listener - provides general listener settings like port, protocol, error pages, etc
  • backend - (optional) complete backend http settings configuration
  • probe - (optional) backend health check probe configuration
  • redirect - (optional) mutually exclusive with backend and probe, creates a redirect rule
  • rewrite_sets - (optional) a set of rewrite rules used to modify response and request headers.
  • url_path_maps - (optional) a map of URL paths with their routing configuration - creates a rule of PathBasedRouting type (if not specified the rule is of Basic type)

For details on each of them (except for priority) check the topics below.

property: listener

Configures the listener, frontend port and (optionally) the SSL Certificate component that will be used by the listener (required for https listeners). The following properties are available:

NameDescriptionTypeDefaultRequired
porta port numbernumbern/ayes
protocoleither Http or Https (case sensitive)string"Http"no
host_nameshost header values this rule should react on, this creates a Multi-Site listenerlist(string)nullno
ssl_profile_namea name (key) of an SSL Profile defined in ssl_profiles propertystringnullno
ssl_certificate_patha path to a certificate in .pfx formatstringnullyes if protocol == "Https", mutually exclusive with ssl_certificate_vault_id
ssl_certificate_passa password matching the certificate specified in ssl_certificate_pathstringnullyes if protocol == "Https", mutually exclusive with ssl_certificate_vault_id
ssl_certificate_vault_idan ID of a certificate stored in an Azure Key Vault, requires managed_identities property, the identity(-ties) used have to have at least GET access to Key Vault's secretsstringnullyes if protocol == "Https", mutually exclusive with ssl_certificate_path
custom_error_pagesa map that contains ULRs for custom error pages, for more information see belowmapnullno

The custom_error_pages map has the following format:

custom_error_pages = { 
HttpStatus403 = "http://error.com/403/page.html",
HttpStatus502 = "http://error.com/502/page.html"
}

Keys can have values of HttpStatus403 and HttpStatus502 only. Both are optional. Only the error page path is customizable and it has to point to an HTML file.

property: backend

Configures the backend's http settings, so port and protocol properties for a connection between an Application Gateway and the actual Firewall. Following properties are available:

NameDescriptionTypeDefaultRequired
portport on which the backend is listeningnumber80no
protocolprotocol for the backend service, this can be Http or Httpsstring"Http"no
hostname_from_backendoverride request host header with backend's host nameboolfalseno, mutually exclusive with hostname
hostnameoverride request host header with a custom host namestringnullno, mutually exclusive with hostname_from_backend
pathpath prefix, in case we need to shift the url path for the backendstringnullno
timeouttimeout for backend's response in secondsnumber60no
cookie_based_affinitycookie based routingstring"Enabled"no
affinity_cookie_namename of the affinity cookie, when skipped defaults to Azure's default namestringnullno
root_certsfor https traffic only, a map of custom root certificates used to sign backend's certificate (see below)mapnullno

When hostname_from_backend nor hostname is not set the request's host header is not changed. This requires that the health check probe's (if used) host property is set (Application Gateway limitation). However, if one of this properties is set you can skip probe's host property - the host header will be inherited from the backend's http settings.

The root_certs map has the following format:

root_certs = {
root_cert_name = "./files/ca.crt"
}

property: probe

Configures a health check probe. A probe is fully customizable, meaning that one decides what should be probed, the FW or an application behind it.

One can decide on the port used by the probe but the protocol is always aligned to the one set in http settings (Application Gateway limitation).

NameDescriptionTypeDefaultRequired
pathurl for the health check endpoint, this property controls if the custom probe is created or not; if this is not set, http settings will have the property Use custom probe set to Nostringnullyes to enable a probe
hosthost header for the health check probe, when omitted sets the Pick host name from backend HTTP settings to Yes, cannot be skipped when backend.hostname or backend.hostname_from_backend are not setstringnullno
port(v2 only) port for the health check, defaults to default protocol portnumbern/ano
intervalprobe interval in secondsnubmer5no
timeoutprobe timeout in secondsnubmer30no
thresholdnumber of failed probes until the backend is marked as downnubmer2no
match_codea list of acceptable http response codes, this property controls the custom match condition for a health probe, if not set, it disables themlist(nubmer)nullno
match_bodya snippet of the backend response that can be matched for health check conditionsstringnullno

property: redirect

Configures a rule that only redirects traffic (traffic matched by this rules never reaches the Firewalls). Hence it is mutually exclusive with backend and probe properties.

NameDescriptionTypeDefaultRequired
typethis property triggers creation of a redirect rule, possible values are: Permanent, Temporary, Found and SeeOtherstringnullno
target_listener_namea name of an existing listener to which traffic will be redirected, this is basically a name of a rulestringnullno, mutually exclusive with target_url
target_urla URL to which traffic will be redirectedstringnullno, mutually exclusive with target_listener_name
include_pathdecides whether to include the path in the redirected Urlboolfalseno
include_query_stringdecides whether to include the query string in the redirected Urlboolfalseno

property: rewrite_sets

Creates rewrite rules used to modify the HTTP response and request headers. A set of rewrite rules cannot be shared between applications. For details on building the rules refer to Microsoft's documentation.

The whole property is a map, where key is the rule name and value is a map of rule's properties. Example of a rule that strips a port number from the X-Forwarded-For header:

rewrite_sets = {
"xff-strip-port" = {
sequence = 100
request_header = {
"X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}"
}
}
}

Properties for a rule are described below.

NameDescriptionTypeDefaultRequired
sequencea rule prioritynumbern/ayes
conditionsa map of pre-conditions for a rule, for details see property: rewrite_sets.conditionsmapnullno
request_headersa key-value map of request headers to modify, where a key is the header name and the value is the new value (to delete a header set the value to an empty string)mapnullno
response_headersa key-value map of response headers to modify, where a key is the header name and the value is the new value (to delete a header set the value to an empty string)mapnullno

property: rewrite_sets.conditions

This is a map where the key is a variable that will be checked and value is a set of properties describing the actual condition.

For details on the variables see Microsoft's documentation. But generally value of this variable brakes into 3 scenarios controlled by a prefix:

  • var_ - the condition is based on a server variable, the variable name follows the prefix
  • http_req_ - a request header condition, the header name follows the prefix
  • http_resp - a response header condition, the header name follows the prefix.

Example:

conditions = {
"var_client_ip" = {
pattern = "1.1.1.1"
ignore_case = true
}
"http_req_X-Forwarded-Proto" = {
pattern = "https"
ignore_case = true
negate = true
}
}
NameDescriptionTypeDefaultRequired
conditions.patterna fix string or a regular expression to evaluate the conditionstringnullyes
conditions.ignore_casecase in-sensitive comparisonboolfalseno
conditions.negatenegate the conditionboolfalseno

property: url_path_maps

Triggers creation of a PathBasedRouting rule for an application. It's a map where key is a name of a routing configuration for a specific path and value contains the actual configuration.

NameDescriptionTypeDefaultRequired
patha URL path that will be matched for this configurationstringn/ayes
backenda backend configuration like specified abovemapnullno, mutually exclusive with redirect
probea probe configuration like specified abovemapnullno, mutually exclusive with redirect
redirecta redirect configuration like specified abovemapnullno, mutually exclusive with backend and probe

As one can see the only specific setting is path. The rest of configuration is similar to a regular application configuration. For each path a pair backend settings and probe or a redirect configuration is created.

Usage

General

Module requires that Firewalls and a dedicated subnet are set up already.

An example invocation (assuming usage of other Palo Alto's Azure modules) with a minium set of rules (at least one rule is required):

module "Application Gateway" {
source = "PaloAltoNetworks/vmseries-modules/azurerm//modules/appgw"

name = "Application Gateway"
resource_group_name = azurerm_resource_group.this.name
location = var.location
subnet_id = module.security_vnet.subnet_ids["subnet-Application Gateway"]
capacity = 2

vmseries_ips = [for k, v in module.vmseries : v.interfaces[1].private_ip_address]

rules = {
"minimum" = {
priority = 1
listener = {
port = 8080
}
}
}
}

rules property examples

The rules property is quite flexible, there are several limitations though. Their origin comes from the Application Gateway rather than the code itself. They are:

  • priority property is required since 2021-08-01 AzureRM API update
  • listener.port has to be specified at minimum to create a valid rule
  • listener.port has to be unique between rules unless listener.host_names is used (all rules sharing a port have to have listener.host_names specified)
  • a health check probe has to have a host header specified, this is done by either setting the header directly in probe.host property, or by inheriting it from http backend settings (one of backend.hostname_from_backend or backend.hostname has to be set)
  • when creating a redirect rule backend and probe cannot be set
  • the probe has to use the same protocol as the associated http backend settings, different port can be used though

The examples below are meant to show most common use cases and to serve as a base for more complex rules.

SSL termination with a redirect from HTTP to HTTPS

This rule redirects all http traffic to a https listener. The ssl certificate is taken from an Azure Key Vault service.

rules = {
"http-2-https" = {
priority = 1

listener = {
port = 80
}

redirect = {
type = "Permanent"
target_listener_name = "https"
include_path = true
include_query_string = true
}
}
"https" = {
priority = 2

listener = {
port = 443
protocol = "Https"
ssl_certificate_vault_id = "https://kv.vault.azure.net/secrets/cert/bb1391bba15042a59adaea584a8208e8"
}
}
}

Multiple websites hosted on a single port

This rule demonstrates how to split hostname based traffic to different ports on a Firewall. For simplicity http traffic is configured only.

rules = {
"application-1" = {
priority = 1

listener = {
port = 80
host_names = ["www.app_1.com"]
}

backend = {
port = 8080
}
}
"application-2" = {
priority = 2

listener = {
port = 80
host_names = ["www.app_2.com"]
}

backend = {
port = 8081
}
}
}

Probing a Firewall availability in an HA pair

In a typical HA scenario the probe is set to check the Management Service exposed on a public interface. The example below shows how to achieve that.

rules = {
"application-1" = {
priority = 1

listener = {
port = 80
}

backend = {
port = 8080
}

probe = {
path = "/php/login.php"
port = 80
host = "127.0.0.1"
}
}
}

Rewriting HTTP headers

This is a simple rule used to terminate SSL traffic. However the application behind the Firewall has two limitations:

  1. it expects the protocol to be still HTTPS, to achieve that we set the X-Forwarded-Proto header
  2. it expects that the X-Forwarded-For does not include ports (which is default for an Application Gateway).

We also use an SSL certificate stored in a file instead of an Azure Key Vault.

NOTICE, there are some defaults used in this config:

  • backend has no port or protocol specified - this means 80 and Http are used respectively.
  • probe has no port or host specified - this means port 80 is used (default port for protocol, which is inherited from backend's protocol) and host headers are inherited from backen's host headers.
rules = {
"application-1" = {
priority = 1

listener = {
port = 443
protocol = "Https"
ssl_certificate_path = "./files/certificate.pfx"
ssl_certificate_pass = "password"
}

backend = {
hostname_from_backend = true
}

probe = {
path = "/php/login.php"
}

rewrite_sets = {
"xff-strip-port" = {
sequence = 100
conditions = {
"http_resp_X-Forwarded-Proto" = {
pattern = "https"
ignore_case = true
negate = true
}
}
request_headers = {
"X-Forwarded-For" = "{var_add_x_forwarded_for_proxy}"
"X-Forwarded-Proto" = "https"
}
}
}
}
}

Path based configuration

Here we show a configuration for a 'complex' application. This means that under different paths we have different applications. The host header remains the same. Each application is served on the Firewall under a different port. We use path based routing to split the traffic on an Application Gateway.

Notice, that the general backend configuration serves as a 'catch-all' rule, while each path has it's own dedicated backend, probe or redirect configuration block.

rules = {
"complex-application" = {
priority = 1
listener = {
port = 80
host_names = ["www.complex.app"]
}
backend = {
port = 8080
}
probe = {
path = "/healthcheck"
host = "127.0.0.1"
}
url_path_maps = {
"menu" = {
path = "/api/menu/"
backend = {
port = 8081
}
probe = {
path = "/api/menu/healthcheck"
host = "127.0.0.1"
}
}
"header" = {
path = "/api/header/"
backend = {
port = 8082
}
probe = {
path = "/api/header/healthcheck"
host = "127.0.0.1"
}
}
"old_url_fix" = {
path = "/old/app/path/"
redirect = {
type = "Permanent"
target_url = "https://www.complex.app"
}
}
}
}
}

Reference

Requirements

NameVersion
terraform>= 1.2, < 2.0
azurerm~> 3.25

Providers

NameVersion
azurerm~> 3.25

Modules

No modules.

Resources

NameType
azurerm_application_gateway.thisresource
azurerm_public_ip.thisresource

Inputs

NameDescriptionTypeDefaultRequired
resource_group_nameName of an existing resource group.stringn/ayes
locationLocation to place the Application Gateway in.stringn/ayes
zonesA list of zones the Application Gateway should be available in.

NOTICE: this is also enforced on the Public IP. The Public IP object brings in some limitations as it can only be non-zonal, pinned to a single zone or zone-redundant (so available in all zones in a region).
Therefore make sure that if you specify more than one zone you specify all available in a region. You can use a subset, but the Public IP will be created in all zones anyway. This fact will cause terraform to recreate the IP resource during next terraform apply as there will be difference between the state and the actual configuration.

For details on zones currently available in a region of your choice refer to Microsoft's documentation.

Example:
zones = ["1","2","3"]
list(string)nullno
nameName of the Application Gateway.stringn/ayes
domain_name_labelLabel for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system.stringnullno
managed_identitiesA list of existing User-Assigned Managed Identities, which Application Gateway uses to retrieve certificates from Key Vault.

These identities have to have at least GET access to Key Vault's secrets. Otherwise Application Gateway will not be able to use certificates stored in the Vault.
list(string)nullno
waf_enabledEnables WAF Application Gateway. This only sets the SKU. This module does not support WAF rules configuration.bool"false"no
capacityA number of Application Gateway instances. A value bewteen 1 and 125.

This property is not used when autoscaling is enabled.
number2no
capacity_minWhen set enables autoscaling and becomes the minimum capacity.numbernullno
capacity_maxOptional, maximum capacity for autoscaling.numbernullno
enable_http2Enable HTTP2 on the Application Gateway.boolfalseno
subnet_idAn ID of a subnet that will host the Application Gateway. Keep in mind that this subnet can contain only AppGWs and only of the same type.stringn/ayes
vmseries_ipsIP addresses of VMSeries' interfaces that will serve as backends for the Application Gateway.list(string)[]no
rulesA map of rules for the Application Gateway. A rule combines listener, http settings and health check configuration.
A key is an application name that is used to prefix all components inside Application Gateway that are created for this application.

Details on configuration can be found here.
anyn/ayes
ssl_policy_typeType of an SSL policy. Possible values are Predefined or Custom.
If the value is Custom the following values are mandatory: ssl_policy_cipher_suites and ssl_policy_min_protocol_version.
string"Predefined"no
ssl_policy_nameName of an SSL policy. Supported only for ssl_policy_type set to Predefined. Normally you can set it also for Custom policies but the name is discarded on Azure side causing an update to Application Gateway each time terraform code is run. Therefore this property is omitted in the code for Custom policies.

For the Predefined polcies, check the Microsoft documentation for possible values as they tend to change over time. The default value is currently (Q1 2022) a Microsoft's default.
string"AppGwSslPolicy20220101S"no
ssl_policy_min_protocol_versionMinimum version of the TLS protocol for SSL Policy. Required only for ssl_policy_type set to Custom.

Possible values are: TLSv1_0, TLSv1_1, TLSv1_2 or null (only to be used with a Predefined policy).
string"TLSv1_2"no
ssl_policy_cipher_suitesA list of accepted cipher suites. Required only for ssl_policy_type set to Custom.
For possible values see documentation.
list(string)
[
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
]
no
ssl_profilesA 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.

The structure of the map is as follows:
{
profile_name = {
ssl_policy_type = string
ssl_policy_min_protocol_version = string
ssl_policy_cipher_suites = list
}
}
For possible values check the: ssl_policy_type, ssl_policy_min_protocol_version and ssl_policy_cipher_suites variables as SSL profile is a named SSL policy - same properties apply. The only difference is that you cannot name an SSL policy inside an SSL profile.
map(any){}no
tagsAzure tags to apply to the created resources.map(string){}no

Outputs

NameDescription
public_ipA public IP assigned to the Application Gateway.
public_domain_namePublic domain name assigned to the Application Gateway.
backend_pool_idThe identifier of the Application Gateway backend address pool.