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.
Usage
Example usage:
- The code below is designed to be used with vmseries,vpcandsubnet_setmodules. 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:
- defaultsrule shows a minimum setup that uses only default values
- https-customrule 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
| Name | Version | 
|---|---|
| terraform | >= 1.5.0, < 2.0.0 | 
| aws | ~> 5.17 | 
Providers
| Name | Version | 
|---|---|
| aws | ~> 5.17 | 
Modules
No modules.
Resources
| Name | Type | 
|---|---|
| aws_lb.this | resource | 
| aws_lb_listener.this | resource | 
| aws_lb_listener_rule.this | resource | 
| aws_lb_target_group.this | resource | 
| aws_lb_target_group_attachment.this | resource | 
| aws_s3_bucket.this | resource | 
| aws_s3_bucket_acl.this | resource | 
| aws_s3_bucket_policy.this | resource | 
| aws_s3_bucket_public_access_block.this | resource | 
| aws_s3_bucket_server_side_encryption_configuration.example | resource | 
| aws_s3_bucket_versioning.this | resource | 
| aws_elb_service_account.this | data source | 
| aws_iam_policy_document.this | data source | 
| aws_partition.this | data source | 
| aws_s3_bucket.this | data source | 
Inputs
| Name | Description | Type | Default | Required | 
|---|---|---|---|---|
| access_logs_byob | Bring 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 Policiesfor existing buckets. They have to be already in place. | bool | false | no | 
| access_logs_s3_bucket_name | Name of an S3 Bucket that will be used as storage for Load Balancer's access logs. When used with configure_access_logsit becomes the name of a newly created S3 Bucket.When used with access_logs_byobit is a name of an existing bucket. | string | "pantf-alb-access-logs-bucket" | no | 
| access_logs_s3_bucket_prefix | A path to a location inside a bucket under which access logs will be stored. When omitted defaults to the root folder of a bucket. | string | null | no | 
| configure_access_logs | Configure Load Balancer to store access logs in an S3 Bucket. When used with access_logs_byobset tofalseforces creation of a new bucket.If, however, access_logs_byobis set totruean existing bucket can be used.The name of the newly created or existing bucket is controlled via access_logs_s3_bucket_name. | bool | false | no | 
| desync_mitigation_mode | Determines 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. | string | null | no | 
| drop_invalid_header_fields | Indicates whether HTTP headers with header fields that are not valid are removed by the Load Balancer or not. | bool | false | no | 
| enable_cross_zone_load_balancing | Enable load balancing between instances in different AZs. Defaults to true.Change to falseonly 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. | bool | true | no | 
| idle_timeout | The time in seconds that the connection to the Load Balancer can be idle. | number | 60 | no | 
| lb_name | Name of the Load Balancer to be created. | string | n/a | yes | 
| rules | An 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 HTTPorHTTPS"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 HTTPorHTTPS, defaults to communication protocol"health_check_port = "port used by the target group health check, if omitted, traffic-portwill 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\_rulesblock below for more information} }The application_namekey 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 acatch-allrule. For the listener to send traffic to backends a listener rule has to be created. This is controlled via thelistener_rulesmap.A key in this map is the priority of the listener rule. Priority can be between 1and50000(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_stringsorsource_iphas 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_versionandtarget_portrespectively).The listener_rulesmap 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 HTTPorHTTPS"protocol_version = "one of HTTP1,HTTP/2orGRPC, defaults toHTTP1"round_robin = "bool, if set to true (default) the round-robinload 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" } } EXAMPLElistener_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"] } } | any | n/a | yes | 
| security_groups | A 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_groupkey refer to the VPC module documentation. | list(string) | n/a | yes | 
| subnets | Map of subnets used with a Load Balancer. Each key is the availability zone name and the value is an object that has an attribute ididentifying 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_setsmodule:subnets        = { for k, v in module.subnet_sets["untrust"].subnets : k => { id = v.id } } | map(object({ id = string })) | n/a | yes | 
| tags | Map of AWS tags to apply to all the created resources. | map(string) | {} | no | 
| target_group_az | Availability Zones of Target Group ('all' for target group outside of VPC) | string | null | no | 
| targets | A 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 ofvar.vmseriescheck thevmseriesmodule. 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, theforloop returns the following map:{vm01 = "1.1.1.1" vm02 = "2.2.2.2" ... } | map(string) | n/a | yes | 
| vpc_id | ID of the security VPC for the Load Balancer. | string | n/a | yes | 
Outputs
| Name | Description | 
|---|---|
| lb_fqdn | A FQDN for the Load Balancer. | 
| target_group | n/a |