Skip to main content

Palo Alto Networks Application Load Balancer Module for AWS

A Terraform module for deploying an Application Load Balancer in AWS cloud. This is always a public Load Balancer with Target Groups of IP type. It is intended to be placed just in front of Next Generation Firewalls.

GitHub Logo Terraform Logo

Usage

Example usage:

  • The code below is designed to be used with vmseries, vpc and subnet_set modules. Check these modules for information on outputs used in this code.
  • Firewalls' public facing interfaces are placed in a subnet set called untrust.
  • There are two rules shown below:
    • defaults rule shows a minimum setup that uses only default values
    • https-custom rule shows some of the configurable properties and example values.
module "public_alb" {
source = "../../modules/alb"

lb_name = "public-alb"
region = var.region

subnets = { for k, v in module.security_subnet_sets["untrust"].subnets : k => { id = v.id } }
desync_mitigation_mode = "monitor"
vpc_id = module.security_vpc.id
configure_access_logs = true
access_logs_s3_bucket_name = "alb-logs-bucket"
security_groups = [module.security_vpc.security_group_ids["load_balancer"]]

rules = {
"defaults" = {
protocol = "HTTP"
listener_rules = {
"1" = {
target_port = 8080
target_protocol = "HTTP"
host_headers = ["default.com", "www.default.com"]
}
}
}
"https-custom" = {
protocol = "HTTPS"
port = 443
certificate_arn = "arn:aws:acm:eu-west-1:123456789012:certificate/97bd27c1-3822-4082-967d-d7084e0fe52f"

health_check_port = "80"
health_check_protocol = "HTTP"
health_check_matcher = "302"
health_check_path = "/"
health_check_interval = 10

listener_rules = {
"1" = {
target_port = 8443
target_protocol = "HTTP"
host_headers = ["www.custom.org"]
http_request_method = ["GET", "HEAD"]
}
"2" = {
target_port = 8444
target_protocol = "HTTP"
host_headers = ["api.custom.org"]
http_request_method = ["POST", "OPTIONS", "DELETE"]
}
}
}
}

targets = { for k, v in var.vmseries : k => module.vmseries[k].interfaces["untrust"].private_ip }

tags = var.global_tags
}

Reference

Requirements

NameVersion
terraform>= 1.0.0, < 2.0.0
aws~> 5.17

Providers

NameVersion
aws~> 5.17

Modules

No modules.

Resources

NameType
aws_lb.thisresource
aws_lb_listener.thisresource
aws_lb_listener_rule.thisresource
aws_lb_target_group.thisresource
aws_lb_target_group_attachment.thisresource
aws_s3_bucket.thisresource
aws_s3_bucket_acl.thisresource
aws_s3_bucket_policy.thisresource
aws_s3_bucket_public_access_block.thisresource
aws_s3_bucket_server_side_encryption_configuration.exampleresource
aws_s3_bucket_versioning.thisresource
aws_elb_service_account.thisdata source
aws_iam_policy_document.thisdata source
aws_partition.thisdata source
aws_s3_bucket.thisdata source

Inputs

NameDescriptionTypeDefaultRequired
access_logs_byobBring Your Own Bucket - in case you would like to re-use an existing S3 Bucket for Load Balancer's access logs.

NOTICE.
This code does not set up proper Bucket Policies for existing buckets. They have to be already in place.
boolfalseno
access_logs_s3_bucket_nameName of an S3 Bucket that will be used as storage for Load Balancer's access logs.

When used with configure_access_logs it becomes the name of a newly created S3 Bucket.
When used with access_logs_byob it is a name of an existing bucket.
string"pantf-alb-access-logs-bucket"no
access_logs_s3_bucket_prefixA path to a location inside a bucket under which access logs will be stored. When omitted defaults to the root folder of a bucket.stringnullno
configure_access_logsConfigure Load Balancer to store access logs in an S3 Bucket.

When used with access_logs_byob set to false forces creation of a new bucket.
If, however, access_logs_byob is set to true an existing bucket can be used.

The name of the newly created or existing bucket is controlled via access_logs_s3_bucket_name.
boolfalseno
desync_mitigation_modeDetermines how the Load Balancer handles requests that might pose a security risk to an application due to HTTP desync.
Defaults to AWS default. For possible values and current defaults refer to documentation.
stringnullno
drop_invalid_header_fieldsIndicates whether HTTP headers with header fields that are not valid are removed by the Load Balancer or not.boolfalseno
enable_cross_zone_load_balancingEnable load balancing between instances in different AZs. Defaults to true.
Change to false only if absolutely necessary. By default, there is only one FW in each AZ.
Turning this off means 1:1 correlation between a public IP assigned to an AZ and a FW deployed in that AZ.
booltrueno
idle_timeoutThe time in seconds that the connection to the Load Balancer can be idle.number60no
lb_nameName of the Load Balancer to be created.stringn/ayes
rulesAn object that contains the listener, listener_rules, target group, and health check configuration.
It consists of maps of applications with their properties, like in the following example:
rules = {
"application_name" = {
protocol = "communication protocol, since this is an ALB module accepted values are HTTP or HTTPS"
port = "communication port, defaults to protocol's default port"

certificate_arn = "(HTTPS ONLY) this is the arn of an existing certificate, this module will not create one for you"
ssl_policy = "(HTTPS ONLY) name of an ssl policy used by the Load Balancer's listener, defaults to AWS default, for available options see AWS documentation"

health_check_protocol = "this can be either HTTP or HTTPS, defaults to communication protocol"
health_check_port = "port used by the target group health check, if omitted, traffic-port will be used (which will be the same as communication port)"
health_check_healthy_threshold = "number of consecutive health checks before considering target healthy, defaults to 3"
health_check_unhealthy_threshold = "number of consecutive health checks before considering target unhealthy, defaults to 3"
health_check_interval = "time between each health check, between 5 and 300 seconds, defaults to 30s"
health_check_timeout = "health check probe timeout, defaults to AWS default value"
health_check_matcher = "response codes expected during health check, defaults to 200"
health_check_path = "destination used by the health check request, defaults to /"

listener_rules = "a map of rules for a listener created for this application, see listener\_rules block below for more information
}
}
The application_name key is valid only for letters, numbers and a dash (-) - that's an AWS limitation.



There is always one listener created per application. The listener has always a default action that responds with 503. This should be treated as a catch-all rule. For the listener to send traffic to backends a listener rule has to be created. This is controlled via the listener_rules map.

A key in this map is the priority of the listener rule. Priority can be between 1 and 50000 (AWS specifics). All properties under a particular key refer to either rule's condition(s) or the target group that should receive traffic if a rule is met.

Rule conditions - at least one but not more than five of: host_headers, http_headers, http_request_method, path_pattern, query_strings or source_ip has to be set. For more information on what conditions can be set for each type refer to documentation.

Target group - keep in mind that all target group attachments are always pointing to VMSeries' public interfaces. The difference between target groups for each rule is the protocol and/or port to which the traffic is being directed. And these are the only properties you can configure (target_protocol, protocol_version and target_port respectively).

The listener_rules map presents as follows:
listener_rules = {
"rule_priority" = { # string representation of a rule's priority (number from 1 - 50000)
target_port = "port on which the target is listening for requests"
target_protocol = "target protocol, can be HTTP or HTTPS"
protocol_version = "one of HTTP1, HTTP/2 or GRPC, defaults to HTTP1"

round_robin = "bool, if set to true (default) the round-robin load balancing algorithm is used, otherwise a target attachment with least outstanding requests is chosen.

host_headers = "a list of possible host headers, case insensitive, wildcards (*,?) are supported"
http_headers = "a map of key-value pairs, where key is a name of an HTTP header and value is a list of possible values, same rules apply like for host\_headers"
http_request_method = "a list of possible HTTP request methods, case sensitive (upper case only), strict matching (no wildcards)"
path_pattern = "a list of path patterns (w/o query strings), case sensitive, wildcards supported"
query_strings = "a map of key-value pairs, key is a query string key pattern and value is a query string value pattern, case insensitive, wildcards supported, it is possible to match only a value pattern (the key value should be prefixed with nokey\_)"
source_ip = "a list of source IP CDIR notation to match"
}
}


EXAMPLE
listener_rules = {
"1" = {
target_port = 8080
target_protocol = "HTTP"
host_headers = ["public-alb-1050443040.eu-west-1.elb.amazonaws.com"]
http_headers = {
"X-Forwarded-For" = ["192.168.1.*"]
}
http_request_method = ["GET"]
}
"99" = {
host_headers = ["www.else.org"]
target_port = 8081
target_protocol = "HTTP"
path_pattern = ["/", "/login.php"]
query_strings = {
"lang" = "us"
"nokey_1" = "test"
}
source_ip = ["10.0.0.0/8"]
}
}
anyn/ayes
security_groupsA list of security group IDs to use with a Load Balancer.

If security groups are created with a VPC module you can use output from that module like this:
security_groups              = [module.vpc.security_group_ids["load_balancer_security_group"]]
For more information on the load_balancer_security_group key refer to the VPC module documentation.
list(string)n/ayes
subnetsMap of subnets used with a Load Balancer. Each key is the availability zone name and the value is an object that has an attribute
id identifying AWS subnet.

Examples:

You can define the values directly:
subnets = {
"us-east-1a" = { id = "snet-123007" }
"us-east-1b" = { id = "snet-123008" }
}
You can also use output from the subnet_sets module:
subnets        = { for k, v in module.subnet_sets["untrust"].subnets : k => { id = v.id } }
map(object({
id = string
}))
n/ayes
tagsMap of AWS tags to apply to all the created resources.map(string){}no
target_group_azAvailability Zones of Target Group ('all' for target group outside of VPC)stringnullno
targetsA list of backends accepting traffic. For Application Load Balancer all targets are of type IP. This is because this is the only option that allows a direct routing between a Load Balancer and a specific VMSeries' network interface. The Application Load Balancer is meant to be always public, therefore the VMSeries IPs should be from the public facing subnet. An example on how to feed this variable with data:
fw_instance_ips = { for k, v in var.vmseries : k => module.vmseries[k].interfaces["untrust"].private_ip }
For format of var.vmseries check the vmseries module. The key is the VM name. By using those keys, we can loop through all vmseries modules and take the private IP from the interface that is assigned to the subnet we require. The subnet can be identified by the subnet set name (like above). In other words, the for loop returns the following map:
{
vm01 = "1.1.1.1"
vm02 = "2.2.2.2"
...
}
map(string)n/ayes
vpc_idID of the security VPC for the Load Balancer.stringn/ayes

Outputs

NameDescription
lb_fqdnA FQDN for the Load Balancer.
target_groupn/a