From b6cf42464c2f7ac6202a7393c44fdd1eec346562 Mon Sep 17 00:00:00 2001 From: Michael Strache Date: Tue, 6 Jun 2023 21:42:19 +0200 Subject: [PATCH 01/28] fix: Explicitly setting http_tcp_listener.action_type to forward fails (#281) Co-authored-by: Anton Babenko --- .pre-commit-config.yaml | 2 +- examples/complete-alb/README.md | 10 +++++----- examples/complete-alb/main.tf | 20 +++++++++++++------- examples/complete-nlb/README.md | 4 ++-- examples/complete-nlb/main.tf | 17 ++++++++++++----- main.tf | 2 +- wrappers/outputs.tf | 2 +- 7 files changed, 35 insertions(+), 22 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8206b6d..eedadaab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.77.1 + rev: v1.80.0 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index bd3241e1..75c10f32 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -34,13 +34,13 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| -| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 3.0 | +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | | [alb](#module\_alb) | ../../ | n/a | -| [lambda\_with\_allowed\_triggers](#module\_lambda\_with\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 3.0 | -| [lambda\_without\_allowed\_triggers](#module\_lambda\_without\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 3.0 | +| [lambda\_with\_allowed\_triggers](#module\_lambda\_with\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 5.0 | +| [lambda\_without\_allowed\_triggers](#module\_lambda\_without\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 5.0 | | [lb\_disabled](#module\_lb\_disabled) | ../../ | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | -| [wildcard\_cert](#module\_wildcard\_cert) | terraform-aws-modules/acm/aws | ~> 3.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [wildcard\_cert](#module\_wildcard\_cert) | terraform-aws-modules/acm/aws | ~> 4.0 | ## Resources diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 7ac5476d..64dfcc48 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -1,5 +1,11 @@ provider "aws" { region = local.region + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true } data "aws_availability_zones" "available" {} @@ -500,7 +506,7 @@ resource "null_resource" "download_package" { module "lambda_with_allowed_triggers" { source = "terraform-aws-modules/lambda/aws" - version = "~> 3.0" + version = "~> 5.0" function_name = "${local.name}-with-allowed-triggers" description = "My awesome lambda function (with allowed triggers)" @@ -524,7 +530,7 @@ module "lambda_with_allowed_triggers" { module "lambda_without_allowed_triggers" { source = "terraform-aws-modules/lambda/aws" - version = "~> 3.0" + version = "~> 5.0" function_name = "${local.name}-without-allowed-triggers" description = "My awesome lambda function (without allowed triggers)" @@ -548,7 +554,7 @@ module "lambda_without_allowed_triggers" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 5.0" name = local.name cidr = local.vpc_cidr @@ -557,8 +563,8 @@ module "vpc" { private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - enable_nat_gateway = true - single_nat_gateway = true + # Disabled NAT gateway to save a few seconds running this example + enable_nat_gateway = false enable_dns_hostnames = true tags = local.tags @@ -570,7 +576,7 @@ data "aws_route53_zone" "this" { module "acm" { source = "terraform-aws-modules/acm/aws" - version = "~> 3.0" + version = "~> 4.0" domain_name = var.domain_name zone_id = data.aws_route53_zone.this.id @@ -578,7 +584,7 @@ module "acm" { module "wildcard_cert" { source = "terraform-aws-modules/acm/aws" - version = "~> 3.0" + version = "~> 4.0" domain_name = "*.${var.domain_name}" zone_id = data.aws_route53_zone.this.id diff --git a/examples/complete-nlb/README.md b/examples/complete-nlb/README.md index ce75f9a3..b0337bba 100644 --- a/examples/complete-nlb/README.md +++ b/examples/complete-nlb/README.md @@ -32,9 +32,9 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Source | Version | |------|--------|---------| -| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 3.0 | +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | | [nlb](#module\_nlb) | ../../ | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources diff --git a/examples/complete-nlb/main.tf b/examples/complete-nlb/main.tf index f189ca86..fbb5e056 100644 --- a/examples/complete-nlb/main.tf +++ b/examples/complete-nlb/main.tf @@ -1,5 +1,11 @@ provider "aws" { region = local.region + + # Make it faster by skipping something + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true } data "aws_availability_zones" "available" {} @@ -125,7 +131,7 @@ module "nlb" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 5.0" name = local.name cidr = local.vpc_cidr @@ -134,8 +140,8 @@ module "vpc" { private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - enable_nat_gateway = true - single_nat_gateway = true + # Disabled NAT gateway to save a few seconds running this example + enable_nat_gateway = false enable_dns_hostnames = true tags = local.tags @@ -147,7 +153,7 @@ data "aws_route53_zone" "this" { module "acm" { source = "terraform-aws-modules/acm/aws" - version = "~> 3.0" + version = "~> 4.0" domain_name = var.domain_name zone_id = data.aws_route53_zone.this.id @@ -155,5 +161,6 @@ module "acm" { resource "aws_eip" "this" { count = length(local.azs) - vpc = true + + domain = "vpc" } diff --git a/main.tf b/main.tf index 64de0782..aac064c8 100644 --- a/main.tf +++ b/main.tf @@ -635,7 +635,7 @@ resource "aws_lb_listener" "frontend_http_tcp" { # Defaults to forward action if action_type not specified content { type = lookup(default_action.value, "action_type", "forward") - target_group_arn = contains([null, ""], lookup(default_action.value, "action_type", "")) ? aws_lb_target_group.main[lookup(default_action.value, "target_group_index", count.index)].id : null + target_group_arn = contains([null, "", "forward"], lookup(default_action.value, "action_type", "")) ? aws_lb_target_group.main[lookup(default_action.value, "target_group_index", count.index)].id : null dynamic "redirect" { for_each = length(keys(lookup(default_action.value, "redirect", {}))) == 0 ? [] : [lookup(default_action.value, "redirect", {})] diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf index 5da7c09b..ec6da5f4 100644 --- a/wrappers/outputs.tf +++ b/wrappers/outputs.tf @@ -1,5 +1,5 @@ output "wrapper" { description = "Map of outputs of a wrapper." value = module.wrapper - # sensitive = false # No sensitive module output found + # sensitive = false # No sensitive module output found } From 286b327c8a71d74f490080c9d0a6dbfcdb2db0b8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 6 Jun 2023 19:42:52 +0000 Subject: [PATCH 02/28] chore(release): version 8.6.1 [skip ci] ### [8.6.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.0...v8.6.1) (2023-06-06) ### Bug Fixes * Explicitly setting http_tcp_listener.action_type to forward fails ([#281](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/281)) ([b6cf424](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b6cf42464c2f7ac6202a7393c44fdd1eec346562)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 061c35b1..24d21758 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +### [8.6.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.0...v8.6.1) (2023-06-06) + + +### Bug Fixes + +* Explicitly setting http_tcp_listener.action_type to forward fails ([#281](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/281)) ([b6cf424](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b6cf42464c2f7ac6202a7393c44fdd1eec346562)) + ## [8.6.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.5.0...v8.6.0) (2023-03-24) From b3b00f0b7a46f473d630dcd4ca6cce3d9a70629f Mon Sep 17 00:00:00 2001 From: Ricky Chew <41740185+rickychew77@users.noreply.github.com> Date: Fri, 16 Jun 2023 17:56:18 +0800 Subject: [PATCH 03/28] feat: Add web acl association (#291) Co-authored-by: Anton Babenko --- .pre-commit-config.yaml | 2 +- README.md | 2 ++ main.tf | 6 ++++++ variables.tf | 6 ++++++ wrappers/main.tf | 1 + 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eedadaab..e79e67b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.80.0 + rev: v1.81.0 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/README.md b/README.md index b45e968e..e359711d 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,7 @@ No modules. | [aws_lb_target_group_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_wafv2_web_acl_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource | ## Inputs @@ -372,6 +373,7 @@ No modules. | [target\_group\_tags](#input\_target\_group\_tags) | A map of tags to add to all target groups | `map(string)` | `{}` | no | | [target\_groups](#input\_target\_groups) | A list of maps containing key/value pairs that define the target groups to be created. Order of these maps is important and the index of these are to be referenced in listener definitions. Required key/values: name, backend\_protocol, backend\_port | `any` | `[]` | no | | [vpc\_id](#input\_vpc\_id) | VPC id where the load balancer and other resources will be deployed. | `string` | `null` | no | +| [web\_acl\_arn](#input\_web\_acl\_arn) | WAF ARN to associate this LB with. | `string` | `null` | no | | [xff\_header\_processing\_mode](#input\_xff\_header\_processing\_mode) | Determines how the load balancer modifies the X-Forwarded-For header in the HTTP request before sending the request to the target. | `string` | `"append"` | no | ## Outputs diff --git a/main.tf b/main.tf index aac064c8..63dc9662 100644 --- a/main.tf +++ b/main.tf @@ -841,3 +841,9 @@ resource "aws_security_group_rule" "this" { self = lookup(each.value, "self", null) source_security_group_id = lookup(each.value, "source_security_group_id", null) } + +resource "aws_wafv2_web_acl_association" "this" { + count = var.web_acl_arn != null ? 1 : 0 + resource_arn = aws_lb.this[0].arn + web_acl_arn = var.web_acl_arn +} diff --git a/variables.tf b/variables.tf index 66021ea5..1849ab7f 100644 --- a/variables.tf +++ b/variables.tf @@ -277,3 +277,9 @@ variable "security_group_tags" { type = map(string) default = {} } + +variable "web_acl_arn" { + description = "WAF ARN to associate this LB with." + type = string + default = null +} diff --git a/wrappers/main.tf b/wrappers/main.tf index 7496b644..1c78bb65 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -49,4 +49,5 @@ module "wrapper" { security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) security_group_rules = try(each.value.security_group_rules, var.defaults.security_group_rules, {}) security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + web_acl_arn = try(each.value.web_acl_arn, var.defaults.web_acl_arn, null) } From cb8e43d456a863e954f6b97a4a821f41d4280ab8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Jun 2023 09:56:44 +0000 Subject: [PATCH 04/28] chore(release): version 8.7.0 [skip ci] ## [8.7.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.1...v8.7.0) (2023-06-16) ### Features * Add web acl association ([#291](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/291)) ([b3b00f0](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b3b00f0b7a46f473d630dcd4ca6cce3d9a70629f)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24d21758..3ec0cee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [8.7.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.1...v8.7.0) (2023-06-16) + + +### Features + +* Add web acl association ([#291](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/291)) ([b3b00f0](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b3b00f0b7a46f473d630dcd4ca6cce3d9a70629f)) + ### [8.6.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.0...v8.6.1) (2023-06-06) From 39d5627d51251c0ccbe9d404cf1ea9ccb4f5b3b7 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 27 Oct 2023 11:52:25 -0400 Subject: [PATCH 05/28] feat!: Refactor module to use maps instead of lists (#305) Co-authored-by: Anton Babenko --- .pre-commit-config.yaml | 4 +- README.md | 547 ++++++++-------- UPGRADE-9.0.md | 931 ++++++++++++++++++++++++++ docs/patterns.md | 5 + examples/README.md | 8 + examples/complete-alb/README.md | 35 +- examples/complete-alb/main.tf | 741 ++++++++++----------- examples/complete-alb/outputs.tf | 90 ++- examples/complete-alb/versions.tf | 2 +- examples/complete-nlb/README.md | 30 +- examples/complete-nlb/main.tf | 211 ++++-- examples/complete-nlb/outputs.tf | 89 +-- examples/complete-nlb/versions.tf | 2 +- main.tf | 1011 +++++++++++------------------ outputs.tf | 84 ++- variables.tf | 294 ++++----- versions.tf | 2 +- wrappers/main.tf | 75 +-- 18 files changed, 2463 insertions(+), 1698 deletions(-) create mode 100644 UPGRADE-9.0.md create mode 100644 docs/patterns.md create mode 100644 examples/README.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e79e67b2..e809a4e4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.81.0 + rev: v1.83.5 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each @@ -24,7 +24,7 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer diff --git a/README.md b/README.md index e359711d..7e83fa49 100644 --- a/README.md +++ b/README.md @@ -6,280 +6,331 @@ Terraform module which creates Application and Network Load Balancer resources o ## Usage +When you're using ALB Listener rules, make sure that every rule's `actions` block ends in a `forward`, `redirect`, or `fixed-response` action so that every rule will resolve to some sort of an HTTP response. Checkout the [AWS documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html) for more information. + ### Application Load Balancer -HTTP and HTTPS listeners with default actions: +#### HTTP to HTTPS redirect ```hcl module "alb" { - source = "terraform-aws-modules/alb/aws" - version = "~> 8.0" + source = "terraform-aws-modules/alb/aws" - name = "my-alb" - - load_balancer_type = "application" + name = "my-alb" + vpc_id = "vpc-abcde012" + subnets = ["subnet-abcde012", "subnet-bcde012a"] - vpc_id = "vpc-abcde012" - subnets = ["subnet-abcde012", "subnet-bcde012a"] - security_groups = ["sg-edcd9784", "sg-edcd9785"] + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + description = "HTTP web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + all_https = { + from_port = 443 + to_port = 443 + ip_protocol = "tcp" + description = "HTTPS web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "10.0.0.0/16" + } + } access_logs = { bucket = "my-alb-logs" } - target_groups = [ - { - name_prefix = "pref-" - backend_protocol = "HTTP" - backend_port = 80 - target_type = "instance" - targets = { - my_target = { - target_id = "i-0123456789abcdefg" - port = 80 - } - my_other_target = { - target_id = "i-a1b2c3d4e5f6g7h8i" - port = 8080 - } + listeners = { + ex-http-https-redirect = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" } } - ] - - https_listeners = [ - { - port = 443 - protocol = "HTTPS" - certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - target_group_index = 0 + ex-https = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" + + forward = { + target_group_key = "ex-instance" + } } - ] + } - http_tcp_listeners = [ - { - port = 80 - protocol = "HTTP" - target_group_index = 0 + target_groups = { + ex-instance = { + name_prefix = "h1" + backend_protocol = "HTTP" + backend_port = 80 + target_type = "instance" } - ] + } tags = { - Environment = "Test" + Environment = "Development" + Project = "Example" } } ``` -HTTP to HTTPS redirect and HTTPS cognito authentication: +#### Cognito authentication ```hcl module "alb" { - source = "terraform-aws-modules/alb/aws" - version = "~> 8.0" - - name = "my-alb" - - load_balancer_type = "application" - - vpc_id = "vpc-abcde012" - subnets = ["subnet-abcde012", "subnet-bcde012a"] - security_groups = ["sg-edcd9784", "sg-edcd9785"] + source = "terraform-aws-modules/alb/aws" - access_logs = { - bucket = "my-alb-logs" - } + # Truncated for brevity ... - target_groups = [ - { - name_prefix = "pref-" - backend_protocol = "HTTPS" - backend_port = 443 - target_type = "instance" - } - ] - - https_listeners = [ - { - port = 443 - protocol = "HTTPS" - certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - action_type = "authenticate-cognito" - target_group_index = 0 - authenticate_cognito = { - user_pool_arn = "arn:aws:cognito-idp::123456789012:userpool/test-pool" - user_pool_client_id = "6oRmFiS0JHk=" - user_pool_domain = "test-domain-com" - } - } - ] - - http_tcp_listeners = [ - { - port = 80 - protocol = "HTTP" - action_type = "redirect" + listeners = { + ex-http-https-redirect = { + port = 80 + protocol = "HTTP" redirect = { port = "443" protocol = "HTTPS" status_code = "HTTP_301" } } - ] + ex-cognito = { + port = 444 + protocol = "HTTPS" + certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - tags = { - Environment = "Test" + authenticate_cognito = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = "arn:aws:cognito-idp:us-west-2:123456789012:userpool/us-west-2_abcdefghi" + user_pool_client_id = "us-west-2_fak3p001B" + user_pool_domain = "https://fak3p001B.auth.us-west-2.amazoncognito.com" + } + + forward = { + target_group_key = "ex-instance" + } + + rules = { + ex-oidc = { + priority = 2 + + actions = [ + { + type = "authenticate-oidc" + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://foobar.com/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://foobar.com" + token_endpoint = "https://foobar.com/token" + user_info_endpoint = "https://foobar.com/user_info" + }, + { + type = "forward" + target_group_key = "ex-instance" + } + ] + } + } + } } } ``` -Cognito Authentication only on certain routes, with redirects for other routes: +#### Cognito authentication on certain paths, redirects for others ```hcl module "alb" { - source = "terraform-aws-modules/alb/aws" - version = "~> 8.0" + source = "terraform-aws-modules/alb/aws" - name = "my-alb" + # Truncated for brevity ... - load_balancer_type = "application" + listeners = { + https = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - vpc_id = "vpc-abcde012" - subnets = ["subnet-abcde012", "subnet-bcde012a"] - security_groups = ["sg-edcd9784", "sg-edcd9785"] + forward = { + target_group_key = "instance" + } - access_logs = { - bucket = "my-alb-logs" + rules = { + redirect = { + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + path_pattern = { + values = ["/onboarding", "/docs"] + } + }] + } + + cognito = { + priority = 2 + actions = [ + { + type = "authenticate-cognito" + user_pool_arn = "arn:aws:cognito-idp::123456789012:userpool/test-pool" + user_pool_client_id = "6oRmFiS0JHk=" + user_pool_domain = "test-domain-com" + }, + { + type = "forward" + target_group_key = "instance" + } + ] + + conditions = [{ + path_pattern = { + values = ["/protected-route", "private/*"] + } + }] + } + } + } } - target_groups = [ - { + target_groups = { + instance = { name_prefix = "default" backend_protocol = "HTTPS" backend_port = 443 target_type = "instance" } - ] - - https_listeners = [ - { - port = 443 - certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - } - ] - - https_listener_rules = [ - { - https_listener_index = 0 - priority = 5000 - - actions = [{ - type = "redirect" - status_code = "HTTP_302" - host = "www.youtube.com" - path = "/watch" - query = "v=dQw4w9WgXcQ" - protocol = "HTTPS" - }] - - conditions = [{ - path_patterns = ["/onboarding", "/docs"] - }] - }, - { - https_listener_index = 0 - priority = 2 - - actions = [ - { - type = "authenticate-cognito" - - user_pool_arn = "arn:aws:cognito-idp::123456789012:userpool/test-pool" - user_pool_client_id = "6oRmFiS0JHk=" - user_pool_domain = "test-domain-com" - }, - { - type = "forward" - target_group_index = 0 - } - ] - - conditions = [{ - path_patterns = ["/protected-route", "private/*"] - }] - } - ] + } } ``` -When you're using ALB Listener rules, make sure that every rule's `actions` block ends in a `forward`, `redirect`, or `fixed-response` action so that every rule will resolve to some sort of an HTTP response. Checkout the [AWS documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html) for more information. +### Network Load Balancer -### Network Load Balancer (TCP_UDP, UDP, TCP and TLS listeners) +#### TCP_UDP, UDP, TCP and TLS listeners ```hcl module "nlb" { - source = "terraform-aws-modules/alb/aws" - version = "~> 8.0" - - name = "my-nlb" + source = "terraform-aws-modules/alb/aws" + name = "my-nlb" load_balancer_type = "network" + vpc_id = "vpc-abcde012" + subnets = ["subnet-abcde012", "subnet-bcde012a"] - vpc_id = "vpc-abcde012" - subnets = ["subnet-abcde012", "subnet-bcde012a"] + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 82 + ip_protocol = "tcp" + description = "HTTP web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + all_https = { + from_port = 443 + to_port = 445 + ip_protocol = "tcp" + description = "HTTPS web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "10.0.0.0/16" + } + } access_logs = { bucket = "my-nlb-logs" } - target_groups = [ - { + listeners = { + ex-tcp-udp = { + port = 81 + protocol = "TCP_UDP" + forward = { + target_group_key = "ex-target" + } + } + + ex-udp = { + port = 82 + protocol = "UDP" + forward = { + target_group_key = "ex-target" + } + } + + ex-tcp = { + port = 83 + protocol = "TCP" + forward = { + target_group_key = "ex-target" + } + } + + ex-tls = { + port = 84 + protocol = "TLS" + certificate_arn = certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" + forward = { + target_group_key = "ex-target" + } + } + } + + target_groups = { + ex-target = { name_prefix = "pref-" backend_protocol = "TCP" backend_port = 80 target_type = "ip" } - ] - - https_listeners = [ - { - port = 443 - protocol = "TLS" - certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" - target_group_index = 0 - } - ] - - http_tcp_listeners = [ - { - port = 80 - protocol = "TCP" - target_group_index = 0 - } - ] + } tags = { - Environment = "Test" + Environment = "Development" + Project = "Example" } } ``` -## Assumptions - -It's recommended you use this module with [terraform-aws-vpc](https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws), [terraform-aws-security-group](https://registry.terraform.io/modules/terraform-aws-modules/security-group/aws), and [terraform-aws-autoscaling](https://registry.terraform.io/modules/terraform-aws-modules/autoscaling/aws/). - -## Notes - -1. Terraform AWS provider version v2.39.0 and newer has [issue #16674](https://github.com/hashicorp/terraform-provider-aws/issues/16674) related to "Provider produced inconsistent final plan". It means that S3 bucket has to be created before referencing it as an argument inside `access_logs = { bucket = "my-already-created-bucket-for-logs" }`, so this won't work: `access_logs = { bucket = module.log_bucket.s3_bucket_id }`. - ## Conditional creation -Sometimes you need to have a way to create ALB resources conditionally but Terraform does not allow to use `count` inside `module` block, so the solution is to specify argument `create_lb`. +The following values are provided to toggle on/off creation of the associated resources as desired: ```hcl -# This LB will not be created -module "lb" { - source = "terraform-aws-modules/alb/aws" +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Disable creation of the LB and all resources + create = false - create_lb = false # ... omitted } ``` @@ -295,13 +346,13 @@ module "lb" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.59 | +| [aws](#requirement\_aws) | >= 5.13 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.59 | +| [aws](#provider\_aws) | >= 5.13 | ## Modules @@ -311,90 +362,80 @@ No modules. | Name | Type | |------|------| -| [aws_lambda_permission.lb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | +| [aws_lambda_permission.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | | [aws_lb.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb) | resource | -| [aws_lb_listener.frontend_http_tcp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | -| [aws_lb_listener.frontend_https](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | -| [aws_lb_listener_certificate.https_listener](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_certificate) | resource | -| [aws_lb_listener_rule.http_tcp_listener_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | -| [aws_lb_listener_rule.https_listener_rule](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | -| [aws_lb_target_group.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | +| [aws_lb_listener.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener) | resource | +| [aws_lb_listener_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_certificate) | resource | +| [aws_lb_listener_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | +| [aws_lb_target_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | | [aws_lb_target_group_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource | +| [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | -| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_vpc_security_group_egress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | | [aws_wafv2_web_acl_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association) | resource | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [access\_logs](#input\_access\_logs) | Map containing access logging configuration for load balancer. | `map(string)` | `{}` | no | -| [create\_lb](#input\_create\_lb) | Controls if the Load Balancer should be created | `bool` | `true` | no | +| [access\_logs](#input\_access\_logs) | Map containing access logging configuration for load balancer | `map(string)` | `{}` | no | +| [associate\_web\_acl](#input\_associate\_web\_acl) | Indicates whether a Web Application Firewall (WAF) ACL should be associated with the load balancer | `bool` | `false` | no | +| [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | | [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | -| [desync\_mitigation\_mode](#input\_desync\_mitigation\_mode) | Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. | `string` | `"defensive"` | no | -| [drop\_invalid\_header\_fields](#input\_drop\_invalid\_header\_fields) | Indicates whether invalid header fields are dropped in application load balancers. Defaults to false. | `bool` | `false` | no | -| [enable\_cross\_zone\_load\_balancing](#input\_enable\_cross\_zone\_load\_balancing) | Indicates whether cross zone load balancing should be enabled in application load balancers. | `bool` | `false` | no | -| [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false. | `bool` | `false` | no | -| [enable\_http2](#input\_enable\_http2) | Indicates whether HTTP/2 is enabled in application load balancers. | `bool` | `true` | no | -| [enable\_tls\_version\_and\_cipher\_suite\_headers](#input\_enable\_tls\_version\_and\_cipher\_suite\_headers) | Indicates whether the two headers (x-amzn-tls-version and x-amzn-tls-cipher-suite), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. | `bool` | `false` | no | -| [enable\_waf\_fail\_open](#input\_enable\_waf\_fail\_open) | Indicates whether to route requests to targets if lb fails to forward the request to AWS WAF | `bool` | `false` | no | -| [enable\_xff\_client\_port](#input\_enable\_xff\_client\_port) | Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in application load balancers. | `bool` | `true` | no | -| [extra\_ssl\_certs](#input\_extra\_ssl\_certs) | A list of maps describing any extra SSL certificates to apply to the HTTPS listeners. Required key/values: certificate\_arn, https\_listener\_index (the index of the listener within https\_listeners which the cert applies toward). | `list(map(string))` | `[]` | no | -| [http\_tcp\_listener\_rules](#input\_http\_tcp\_listener\_rules) | A list of maps describing the Listener Rules for this ALB. Required key/values: actions, conditions. Optional key/values: priority, http\_tcp\_listener\_index (default to http\_tcp\_listeners[count.index]) | `any` | `[]` | no | -| [http\_tcp\_listener\_rules\_tags](#input\_http\_tcp\_listener\_rules\_tags) | A map of tags to add to all http listener rules | `map(string)` | `{}` | no | -| [http\_tcp\_listeners](#input\_http\_tcp\_listeners) | A list of maps describing the HTTP listeners or TCP ports for this ALB. Required key/values: port, protocol. Optional key/values: target\_group\_index (defaults to http\_tcp\_listeners[count.index]) | `any` | `[]` | no | -| [http\_tcp\_listeners\_tags](#input\_http\_tcp\_listeners\_tags) | A map of tags to add to all http listeners | `map(string)` | `{}` | no | -| [https\_listener\_rules](#input\_https\_listener\_rules) | A list of maps describing the Listener Rules for this ALB. Required key/values: actions, conditions. Optional key/values: priority, https\_listener\_index (default to https\_listeners[count.index]) | `any` | `[]` | no | -| [https\_listener\_rules\_tags](#input\_https\_listener\_rules\_tags) | A map of tags to add to all https listener rules | `map(string)` | `{}` | no | -| [https\_listeners](#input\_https\_listeners) | A list of maps describing the HTTPS listeners for this ALB. Required key/values: port, certificate\_arn. Optional key/values: ssl\_policy (defaults to ELBSecurityPolicy-2016-08), target\_group\_index (defaults to https\_listeners[count.index]) | `any` | `[]` | no | -| [https\_listeners\_tags](#input\_https\_listeners\_tags) | A map of tags to add to all https listeners | `map(string)` | `{}` | no | -| [idle\_timeout](#input\_idle\_timeout) | The time in seconds that the connection is allowed to be idle. | `number` | `60` | no | -| [internal](#input\_internal) | Boolean determining if the load balancer is internal or externally facing. | `bool` | `false` | no | -| [ip\_address\_type](#input\_ip\_address\_type) | The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack. | `string` | `"ipv4"` | no | -| [lb\_tags](#input\_lb\_tags) | A map of tags to add to load balancer | `map(string)` | `{}` | no | -| [listener\_ssl\_policy\_default](#input\_listener\_ssl\_policy\_default) | The security policy if using HTTPS externally on the load balancer. [See](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html). | `string` | `"ELBSecurityPolicy-2016-08"` | no | -| [load\_balancer\_create\_timeout](#input\_load\_balancer\_create\_timeout) | Timeout value when creating the ALB. | `string` | `"10m"` | no | -| [load\_balancer\_delete\_timeout](#input\_load\_balancer\_delete\_timeout) | Timeout value when deleting the ALB. | `string` | `"10m"` | no | -| [load\_balancer\_type](#input\_load\_balancer\_type) | The type of load balancer to create. Possible values are application or network. | `string` | `"application"` | no | -| [load\_balancer\_update\_timeout](#input\_load\_balancer\_update\_timeout) | Timeout value when updating the ALB. | `string` | `"10m"` | no | -| [name](#input\_name) | The resource name and Name tag of the load balancer. | `string` | `null` | no | -| [name\_prefix](#input\_name\_prefix) | The resource name prefix and Name tag of the load balancer. Cannot be longer than 6 characters | `string` | `null` | no | -| [preserve\_host\_header](#input\_preserve\_host\_header) | Indicates whether Host header should be preserve and forward to targets without any change. Defaults to false. | `bool` | `false` | no | +| [customer\_owned\_ipv4\_pool](#input\_customer\_owned\_ipv4\_pool) | The ID of the customer owned ipv4 pool to use for this load balancer | `string` | `null` | no | +| [default\_port](#input\_default\_port) | Default port used across the listener and target group | `number` | `80` | no | +| [default\_protocol](#input\_default\_protocol) | Default protocol used across the listener and target group | `string` | `"HTTP"` | no | +| [desync\_mitigation\_mode](#input\_desync\_mitigation\_mode) | Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest` | `string` | `null` | no | +| [drop\_invalid\_header\_fields](#input\_drop\_invalid\_header\_fields) | Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (`true`) or routed to targets (`false`). The default is `true`. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application` | `bool` | `true` | no | +| [enable\_cross\_zone\_load\_balancing](#input\_enable\_cross\_zone\_load\_balancing) | If `true`, cross-zone load balancing of the load balancer will be enabled. For application load balancer this feature is always enabled (`true`) and cannot be disabled. Defaults to `true` | `bool` | `true` | no | +| [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | If `true`, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to `true` | `bool` | `true` | no | +| [enable\_http2](#input\_enable\_http2) | Indicates whether HTTP/2 is enabled in application load balancers. Defaults to `true` | `bool` | `null` | no | +| [enable\_tls\_version\_and\_cipher\_suite\_headers](#input\_enable\_tls\_version\_and\_cipher\_suite\_headers) | Indicates whether the two headers (`x-amzn-tls-version` and `x-amzn-tls-cipher-suite`), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. Only valid for Load Balancers of type `application`. Defaults to `false` | `bool` | `null` | no | +| [enable\_waf\_fail\_open](#input\_enable\_waf\_fail\_open) | Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. Defaults to `false` | `bool` | `null` | no | +| [enable\_xff\_client\_port](#input\_enable\_xff\_client\_port) | Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in `application` load balancers. Defaults to `false` | `bool` | `null` | no | +| [idle\_timeout](#input\_idle\_timeout) | The time in seconds that the connection is allowed to be idle. Only valid for Load Balancers of type `application`. Default: `60` | `number` | `null` | no | +| [internal](#input\_internal) | If true, the LB will be internal. Defaults to `false` | `bool` | `null` | no | +| [ip\_address\_type](#input\_ip\_address\_type) | The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack` | `string` | `null` | no | +| [listeners](#input\_listeners) | Map of listener configurations to create | `any` | `{}` | no | +| [load\_balancer\_type](#input\_load\_balancer\_type) | The type of load balancer to create. Possible values are `application`, `gateway`, or `network`. The default value is `application` | `string` | `"application"` | no | +| [name](#input\_name) | The name of the LB. This name must be unique within your AWS account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen | `string` | `null` | no | +| [name\_prefix](#input\_name\_prefix) | Creates a unique name beginning with the specified prefix. Conflicts with `name` | `string` | `null` | no | +| [preserve\_host\_header](#input\_preserve\_host\_header) | Indicates whether the Application Load Balancer should preserve the Host header in the HTTP request and send it to the target without any change. Defaults to `false` | `bool` | `null` | no | | [putin\_khuylo](#input\_putin\_khuylo) | Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo! | `bool` | `true` | no | +| [route53\_records](#input\_route53\_records) | Map of Route53 records to create. Each record map should contain `zone_id`, `name`, and `type` | `any` | `{}` | no | | [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Security group egress rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Security group ingress rules to add to the security group created | `any` | `{}` | no | | [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | -| [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | | [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | -| [security\_groups](#input\_security\_groups) | The security groups to attach to the load balancer. e.g. ["sg-edcd9784","sg-edcd9785"] | `list(string)` | `[]` | no | -| [subnet\_mapping](#input\_subnet\_mapping) | A list of subnet mapping blocks describing subnets to attach to network load balancer | `list(map(string))` | `[]` | no | -| [subnets](#input\_subnets) | A list of subnets to associate with the load balancer. e.g. ['subnet-1a2b3c4d','subnet-1a2b3c4e','subnet-1a2b3c4f'] | `list(string)` | `null` | no | +| [security\_groups](#input\_security\_groups) | A list of security group IDs to assign to the LB | `list(string)` | `[]` | no | +| [subnet\_mapping](#input\_subnet\_mapping) | A list of subnet mapping blocks describing subnets to attach to load balancer | `list(map(string))` | `[]` | no | +| [subnets](#input\_subnets) | A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type `network`. Changing this value for load balancers of type `network` will force a recreation of the resource | `list(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [target\_group\_tags](#input\_target\_group\_tags) | A map of tags to add to all target groups | `map(string)` | `{}` | no | -| [target\_groups](#input\_target\_groups) | A list of maps containing key/value pairs that define the target groups to be created. Order of these maps is important and the index of these are to be referenced in listener definitions. Required key/values: name, backend\_protocol, backend\_port | `any` | `[]` | no | -| [vpc\_id](#input\_vpc\_id) | VPC id where the load balancer and other resources will be deployed. | `string` | `null` | no | -| [web\_acl\_arn](#input\_web\_acl\_arn) | WAF ARN to associate this LB with. | `string` | `null` | no | -| [xff\_header\_processing\_mode](#input\_xff\_header\_processing\_mode) | Determines how the load balancer modifies the X-Forwarded-For header in the HTTP request before sending the request to the target. | `string` | `"append"` | no | +| [target\_groups](#input\_target\_groups) | Map of target group configurations to create | `any` | `{}` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the load balancer | `map(string)` | `{}` | no | +| [vpc\_id](#input\_vpc\_id) | Identifier of the VPC where the security group will be created | `string` | `null` | no | +| [web\_acl\_arn](#input\_web\_acl\_arn) | Web Application Firewall (WAF) ARN of the resource to associate with the load balancer | `string` | `null` | no | +| [xff\_header\_processing\_mode](#input\_xff\_header\_processing\_mode) | Determines how the load balancer modifies the X-Forwarded-For header in the HTTP request before sending the request to the target. The possible values are `append`, `preserve`, and `remove`. Only valid for Load Balancers of type `application`. The default is `append` | `string` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| [http\_tcp\_listener\_arns](#output\_http\_tcp\_listener\_arns) | The ARN of the TCP and HTTP load balancer listeners created | -| [http\_tcp\_listener\_ids](#output\_http\_tcp\_listener\_ids) | The IDs of the TCP and HTTP load balancer listeners created | -| [https\_listener\_arns](#output\_https\_listener\_arns) | The ARNs of the HTTPS load balancer listeners created | -| [https\_listener\_ids](#output\_https\_listener\_ids) | The IDs of the load balancer listeners created | -| [lb\_arn](#output\_lb\_arn) | The ID and ARN of the load balancer we created | -| [lb\_arn\_suffix](#output\_lb\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch | -| [lb\_dns\_name](#output\_lb\_dns\_name) | The DNS name of the load balancer | -| [lb\_id](#output\_lb\_id) | The ID and ARN of the load balancer we created | -| [lb\_zone\_id](#output\_lb\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records | +| [arn](#output\_arn) | The ID and ARN of the load balancer we created | +| [arn\_suffix](#output\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch | +| [dns\_name](#output\_dns\_name) | The DNS name of the load balancer | +| [id](#output\_id) | The ID and ARN of the load balancer we created | +| [listener\_rules](#output\_listener\_rules) | Map of listeners rules created and their attributes | +| [listeners](#output\_listeners) | Map of listeners created and their attributes | +| [route53\_records](#output\_route53\_records) | The Route53 records created and attached to the load balancer | | [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | | [security\_group\_id](#output\_security\_group\_id) | ID of the security group | -| [target\_group\_arn\_suffixes](#output\_target\_group\_arn\_suffixes) | ARN suffixes of our target groups - can be used with CloudWatch | -| [target\_group\_arns](#output\_target\_group\_arns) | ARNs of the target groups. Useful for passing to your Auto Scaling group | -| [target\_group\_attachments](#output\_target\_group\_attachments) | ARNs of the target group attachment IDs | -| [target\_group\_names](#output\_target\_group\_names) | Name of the target group. Useful for passing to your CodeDeploy Deployment Group | +| [target\_groups](#output\_target\_groups) | Map of target groups created and their attributes | +| [zone\_id](#output\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records | ## Authors diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md new file mode 100644 index 00000000..fc1234da --- /dev/null +++ b/UPGRADE-9.0.md @@ -0,0 +1,931 @@ +# Upgrade from v8.x to v9.x + +Please consult the `examples` directory for reference example configurations. If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- `target_groups` previously were defined by an array of target group definitions that were created using the `count` meta-argument. This has been replaced with a map of target group definitions that are created using the `for_each` meta-argument in order to provide better stability when adding/removing target group definitions. +- `target_groups` no longer support multiple targets per target group. There are alternate methods to achieve similar functionality such as weighted target groups or using an autoscaling group as a target when targetting EC2 instances. +- The previous methods for creating listeners have been removed in favor of one argument, `listeners`, which take a map of listener definitions that are created using the `for_each` meta-argument in order to provide better stability when adding/removing listener definitions. Previously the `target_group_index` was used to associate/reference a target group; that is now replaced with `target_group_key` which is the key of the target group definition in the `target_groups` map. +- `security_group_rules` has been replaced by `security_group_ingress_rules` and `security_group_egress_rules` to align with the new underlying resources. +- Minimum supported version of Terraform AWS provider updated to `v5.13` to support the latest features provided via the resources utilized. +- Minimum supported version of Terraform updated to `v1.0` +- The `Name` tag has been removed from resources + +## Additional changes + +### Added + +- Security group attachment restrictions have been removed now that both ALB and NLB support security groups +- Support for creating Route53 records for ALB/NLB DNS names via the `route53_records` variable + +### Modified + +- `enable_cross_zone_load_balancing` now defaults to `true` +- `drop_invalid_header_fields` now defaults to `true` +- `enable_deletion_protection` now defaults to `true` +- `associate_web_acl` has been added to identify when a WAFv2 Web ACL should be associated with the ALB; previously this was accomplished by checking for the presence of a value passed to `web_acl_arn` which is known to cause issues when the value does not exist and is computed. + +### Removed + +- None + +### Variable and output changes + +1. Removed variables: + + - `target_group_tags` + - `https_listener_rules_tags` + - `http_tcp_listener_rules_tags` + - `https_listeners_tags` + - `http_tcp_listeners_tags` + - `load_balancer_create_timeout` -> replaced with `timeouts` + - `load_balancer_update_timeout` -> replaced with `timeouts` + - `load_balancer_delete_timeout` -> replaced with `timeouts` + +2. Renamed variables: + + - `create_lb` -> `create` + +3. Added variables: + + - `customer_owned_ipv4_pool` + - `default_port` + - `default_protocol` + - `route53_records` + - `associate_web_acl` + +4. Removed outputs: + + - `http_tcp_listener_arns` -> replaced with `listeners` + - `http_tcp_listener_ids` -> replaced with `listeners` + - `https_listener_arns` -> replaced with `listeners` + - `https_listener_ids` -> replaced with `listeners` + - `target_group_arns` -> replaced with `target_groups` + - `target_group_arn_suffixes` -> replaced with `target_groups` + - `target_group_names` -> replaced with `target_groups` + - `target_group_attachments` -> replaced with `target_groups` + +5. Renamed outputs: + + - Outputs previously prefixed with `lb_` have been renamed to remove this prefix (i.e. - `lb_arn` is now `arn`) + +6. Added outputs: + + - `route53_records` + +## Upgrade Migrations + +### Before (v8.x) vs After (v9.x) + +#### Before (v8.x) + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "8.7.0" + + name = local.name + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # Security group + security_group_rules = { + ingress_all_http = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "HTTP web traffic" + cidr_blocks = ["0.0.0.0/0"] + } + ingress_all_https = { + type = "ingress" + from_port = 443 + to_port = 443 + protocol = "tcp" + description = "HTTPS web traffic" + cidr_blocks = ["0.0.0.0/0"] + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + # Listener(s) + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + }, + { + port = 81 + protocol = "HTTP" + action_type = "forward" + forward = { + target_groups = [ + { + target_group_index = 0 + weight = 100 + }, + { + target_group_index = 1 + weight = 0 + } + ] + } + }, + { + port = 82 + protocol = "HTTP" + action_type = "redirect" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + }, + { + port = 83 + protocol = "HTTP" + action_type = "fixed-response" + fixed_response = { + content_type = "text/plain" + message_body = "Fixed message" + status_code = "200" + } + }, + ] + + https_listeners = [ + { + port = 443 + protocol = "HTTPS" + certificate_arn = module.acm.acm_certificate_arn + target_group_index = 1 + }, + { + port = 444 + protocol = "HTTPS" + action_type = "authenticate-cognito" + target_group_index = 1 + certificate_arn = module.acm.acm_certificate_arn + authenticate_cognito = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = aws_cognito_user_pool.this.arn + user_pool_client_id = aws_cognito_user_pool_client.this.id + user_pool_domain = aws_cognito_user_pool_domain.this.domain + } + }, + { + port = 445 + protocol = "HTTPS" + action_type = "authenticate-oidc" + target_group_index = 1 + certificate_arn = module.acm.acm_certificate_arn + authenticate_oidc = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://${var.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${var.domain_name}" + token_endpoint = "https://${var.domain_name}/token" + user_info_endpoint = "https://${var.domain_name}/user_info" + } + }, + ] + + extra_ssl_certs = [ + { + https_listener_index = 0 + certificate_arn = module.wildcard_cert.acm_certificate_arn + } + ] + + # Listener rule(s) + https_listener_rules = [ + { + https_listener_index = 0 + + actions = [ + { + type = "authenticate-cognito" + + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = aws_cognito_user_pool.this.arn + user_pool_client_id = aws_cognito_user_pool_client.this.id + user_pool_domain = aws_cognito_user_pool_domain.this.domain + }, + { + type = "forward" + target_group_index = 0 + } + ] + + conditions = [{ + path_patterns = ["/some/auth/required/route"] + }] + }, + { + https_listener_index = 1 + priority = 2 + + actions = [ + { + type = "authenticate-oidc" + + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://${var.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${var.domain_name}" + token_endpoint = "https://${var.domain_name}/token" + user_info_endpoint = "https://${var.domain_name}/user_info" + }, + { + type = "forward" + target_group_index = 1 + } + ] + + conditions = [{ + host_headers = ["foobar.com"] + }] + }, + { + https_listener_index = 0 + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_headers = [{ + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + }] + }] + }, + { + https_listener_index = 0 + priority = 4 + + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_index = 1 + weight = 2 + }, + { + target_group_index = 0 + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_strings = [{ + key = "weighted" + value = "true" + }] + }] + }, + { + https_listener_index = 0 + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_strings = [{ + key = "video" + value = "random" + }] + }] + }, + ] + + http_tcp_listener_rules = [ + { + http_tcp_listener_index = 0 + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_headers = [{ + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + }] + }] + }, + { + http_tcp_listener_index = 0 + priority = 4 + + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_index = 1 + weight = 2 + }, + { + target_group_index = 0 + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_strings = [{ + key = "weighted" + value = "true" + }] + }] + }, + { + http_tcp_listener_index = 0 + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_strings = [{ + key = "video" + value = "random" + }] + }] + }, + ] + + # Target Group(s) + target_groups = [ + { + name_prefix = "h1" + backend_protocol = "HTTP" + backend_port = 80 + target_type = "instance" + + health_check = { + enabled = true + interval = 30 + path = "/healthz" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + protocol = "HTTP" + matcher = "200-399" + } + + protocol_version = "HTTP1" + targets = { + my_ec2 = { + target_id = aws_instance.this.id + port = 80 + } + } + }, + { + name_prefix = "l1-" + target_type = "lambda" + lambda_multi_value_headers_enabled = true + targets = { + lambda_with_allowed_triggers = { + target_id = module.lambda_with_allowed_triggers.lambda_function_arn + } + } + }, + { + name_prefix = "l2-" + target_type = "lambda" + targets = { + lambda_without_allowed_triggers = { + target_id = module.lambda_without_allowed_triggers.lambda_function_arn + attach_lambda_permission = true + } + } + }, + ] +} +``` + +#### After (v9.x) + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "9.0.0" + + name = local.name + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # Security group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + description = "HTTP web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + all_https = { + from_port = 443 + to_port = 443 + ip_protocol = "tcp" + description = "HTTPS web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + # Listener(s) w/ Listener Rule(s) + listeners = { + default = { + port = 80 + protocol = "HTTP" + forward = { + target_group_key = "lambda-with-trigger" + } + } + + http-https-redirect = { + port = 82 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + + rules = { + fixed-response = { + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_header = { + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + } + }] + } + + weighted-forward = { + priority = 4 + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "lambda-with-trigger" + weight = 2 + }, + { + target_group_key = "instance" + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_string = { + key = "weighted" + value = "true" + } + }] + } + + redirect = { + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_string = { + key = "video" + value = "random" + } + }] + } + } + } + + http-weighted-target = { + port = 81 + protocol = "HTTP" + weighted_forward = { + target_groups = [ + { + target_group_key = "lambda-with-trigger" + weight = 0 + }, + { + target_group_key = "instance" + weight = 100 + } + ] + } + } + + fixed-response = { + port = 83 + protocol = "HTTP" + fixed_response = { + content_type = "text/plain" + message_body = "Fixed message" + status_code = "200" + } + } + + https = { + port = 443 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = module.acm.acm_certificate_arn + additional_certificate_arns = [module.wildcard_cert.acm_certificate_arn] + + forward = { + target_group_key = "lambda-with-trigger" + } + + rules = { + cognito = { + actions = [ + { + type = "authenticate-cognito" + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = aws_cognito_user_pool.this.arn + user_pool_client_id = aws_cognito_user_pool_client.this.id + user_pool_domain = aws_cognito_user_pool_domain.this.domain + }, + { + type = "forward" + target_group_key = "instance" + } + ] + + conditions = [{ + path_pattern = { + values = ["/some/auth/required/route"] + } + }] + } + + fixed-response = { + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_header = { + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + } + }] + } + + weight-forward = { + priority = 4 + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "instance" + weight = 2 + }, + { + target_group_key = "lambda-with-trigger" + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_string = { + key = "weighted" + value = "true" + } + }] + } + + redirect = { + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_string = { + key = "video" + value = "random" + } + }] + } + } + } + + cognito = { + port = 444 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = module.acm.acm_certificate_arn + + authenticate_cognito = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = aws_cognito_user_pool.this.arn + user_pool_client_id = aws_cognito_user_pool_client.this.id + user_pool_domain = aws_cognito_user_pool_domain.this.domain + } + + forward = { + target_group_key = "lambda-with-trigger" + } + + rules = { + oidc = { + priority = 2 + + actions = [ + { + type = "authenticate-oidc" + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://${var.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${var.domain_name}" + token_endpoint = "https://${var.domain_name}/token" + user_info_endpoint = "https://${var.domain_name}/user_info" + }, + { + type = "forward" + target_group_key = "lambda-with-trigger" + } + ] + + conditions = [{ + host_header = { + values = ["foobar.com"] + } + }] + } + } + } + + oidc = { + port = 445 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = module.acm.acm_certificate_arn + action_type = "authenticate-oidc" + authenticate_oidc = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://${var.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${var.domain_name}" + token_endpoint = "https://${var.domain_name}/token" + user_info_endpoint = "https://${var.domain_name}/user_info" + } + + forward = { + target_group_key = "lambda-with-trigger" + } + } + } + + # Target Group(s) + target_groups = { + instance = { + name_prefix = "h1" + backend_protocol = "HTTP" + backend_port = 80 + target_type = "instance" + + health_check = { + enabled = true + interval = 30 + path = "/healthz" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + protocol = "HTTP" + matcher = "200-399" + } + + protocol_version = "HTTP1" + target_id = aws_instance.this.id + port = 80 + } + lambda-with-trigger = { + name_prefix = "l1-" + target_type = "lambda" + lambda_multi_value_headers_enabled = true + target_id = module.lambda_with_allowed_triggers.lambda_function_arn + } + lambda-without-trigger = { + name_prefix = "l2-" + target_type = "lambda" + target_id = module.lambda_without_allowed_triggers.lambda_function_arn + attach_lambda_permission = true + } + } +} +``` + +## Terraform State Moves + +### Listener(s) + +Each listener will need to be migrated using the index position in v8.x to the key of the listener in v9.x - see example state move commands below: + +```sh +terraform state mv 'module.alb.aws_lb_listener.frontend_http_tcp[0]' 'module.alb.aws_lb_listener.this["default"]' +terraform state mv 'module.alb.aws_lb_listener.frontend_http_tcp[1]' 'module.alb.aws_lb_listener.this["http-weighted-target"]' +terraform state mv 'module.alb.aws_lb_listener.frontend_http_tcp[2]' 'module.alb.aws_lb_listener.this["http-https-redirect"]' +terraform state mv 'module.alb.aws_lb_listener.frontend_http_tcp[3]' 'module.alb.aws_lb_listener.this["fixed-response"]' + +terraform state mv 'module.alb.aws_lb_listener.frontend_https[0]' 'module.alb.aws_lb_listener.this["https"]' +terraform state mv 'module.alb.aws_lb_listener.frontend_https[1]' 'module.alb.aws_lb_listener.this["cognito"]' +terraform state mv 'module.alb.aws_lb_listener.frontend_https[2]' 'module.alb.aws_lb_listener.this["oidc"]' +``` + +### Listener Rule(s) + +Each listener rule will need to be migrated using the index position in v8.x to the key of the listener rule in v9.x - see example state move commands below: + +```sh +# HTTP +terraform state mv 'module.alb.aws_lb_listener_rule.http_tcp_listener_rule[0]' 'module.alb.aws_lb_listener_rule.this["http-https-redirect/fixed-response"]' +terraform state mv 'module.alb.aws_lb_listener_rule.http_tcp_listener_rule[1]' 'module.alb.aws_lb_listener_rule.this["http-https-redirect/weighted-forward"]' +terraform state mv 'module.alb.aws_lb_listener_rule.http_tcp_listener_rule[2]' 'module.alb.aws_lb_listener_rule.this["http-https-redirect/redirect"]' + +# HTTPS +terraform state mv 'module.alb.aws_lb_listener_rule.https_listener_rule[0]' 'module.alb.aws_lb_listener_rule.this["https/cognito"]' +terraform state mv 'module.alb.aws_lb_listener_rule.https_listener_rule[1]' 'module.alb.aws_lb_listener_rule.this["cognito/oidc"]' +terraform state mv 'module.alb.aws_lb_listener_rule.https_listener_rule[2]' 'module.alb.aws_lb_listener_rule.this["https/fixed-response"]' +terraform state mv 'module.alb.aws_lb_listener_rule.https_listener_rule[3]' 'module.alb.aws_lb_listener_rule.this["https/weight-forward"]' +terraform state mv 'module.alb.aws_lb_listener_rule.https_listener_rule[4]' 'module.alb.aws_lb_listener_rule.this["https/redirect"]' +``` + +### Additional SSL Certificate(s) + +Each additional SSL certificate will need to be migrated using the index position in v8.x to the of the additional SSL certificate in v9.x - see example state move commands below: + +```sh +terraform state mv 'module.alb.aws_lb_listener_certificate.https_listener[0]' 'module.alb.aws_lb_listener_certificate.this["https/0"]' +``` + +### Target Group(s) + +Each target group will need to be migrated using the index position in v8.x to the key of the target group in v9.x - see example state move commands below: + +```sh +terraform state mv 'module.alb.aws_lb_target_group.main[0]' 'module.alb.aws_lb_target_group.this["instance"]' +terraform state mv 'module.alb.aws_lb_target_group.main[1]' 'module.alb.aws_lb_target_group.this["lambda-with-trigger"]' +terraform state mv 'module.alb.aws_lb_target_group.main[2]' 'module.alb.aws_lb_target_group.this["lambda-without-trigger"]' +``` + +### Lambda Permission(s) + +Each lambda permission will need to be migrated using the position in v8.x to the key of the lambda permission in v9.x - see example state move commands below: + +```sh +terraform state mv 'module.alb.aws_lambda_permission.lb["2.lambda_without_allowed_triggers"]' 'module.alb.aws_lambda_permission.this["lambda-without-trigger"]' +``` + +### Security Group Rule(s) + +The security group rules have been changed from the `aws_security_group_rule` resource to the new `aws_vpc_security_group_ingress_rule`/`aws_vpc_security_group_egress_rule` resources. +If you do not wish for the rules to be recreated during the upgrade, you will need to remove the existing rules from the Terraform state, and re-import (you cannot `terrraform state mv` across +different resource types). For example, for one rule, you would perform the following snippet: + +Example of security group rules for v8.x + +```hcl + security_group_rules = { + ingress_all_http = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "HTTP web traffic" + cidr_blocks = ["0.0.0.0/0"] + } + } +``` + +Example of security group rules for v8.x + +```hcl + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + description = "HTTP web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } +``` + +```sh +terraform state rm 'aws_security_group_rule.this["ingress_all_http"]' +terraform import 'aws_vpc_security_group_ingress_rule.this["all_http"]' sgr-xxx # ensure the key matches your updated implementation +``` diff --git a/docs/patterns.md b/docs/patterns.md new file mode 100644 index 00000000..2a205004 --- /dev/null +++ b/docs/patterns.md @@ -0,0 +1,5 @@ +# Usage Patterns + +Various usage patterns are prescribed below. + + diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..f417c0ad --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index 75c10f32..08e30b4d 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.59 | +| [aws](#requirement\_aws) | >= 5.13 | | [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.59 | +| [aws](#provider\_aws) | >= 5.13 | | [null](#provider\_null) | >= 2.0 | ## Modules @@ -36,9 +36,10 @@ Note that this example may create resources which cost money. Run `terraform des |------|--------|---------| | [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | | [alb](#module\_alb) | ../../ | n/a | -| [lambda\_with\_allowed\_triggers](#module\_lambda\_with\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 5.0 | -| [lambda\_without\_allowed\_triggers](#module\_lambda\_without\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 5.0 | -| [lb\_disabled](#module\_lb\_disabled) | ../../ | n/a | +| [alb\_disabled](#module\_alb\_disabled) | ../../ | n/a | +| [lambda\_with\_allowed\_triggers](#module\_lambda\_with\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 6.0 | +| [lambda\_without\_allowed\_triggers](#module\_lambda\_without\_allowed\_triggers) | terraform-aws-modules/lambda/aws | ~> 6.0 | +| [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | | [wildcard\_cert](#module\_wildcard\_cert) | terraform-aws-modules/acm/aws | ~> 4.0 | @@ -51,9 +52,9 @@ Note that this example may create resources which cost money. Run `terraform des | [aws_cognito_user_pool_domain.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_domain) | resource | | [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | -| [aws_ami.amazon_linux](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +| [aws_ssm_parameter.al2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | ## Inputs @@ -65,19 +66,15 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Description | |------|-------------| -| [http\_tcp\_listener\_arns](#output\_http\_tcp\_listener\_arns) | The ARN of the TCP and HTTP load balancer listeners created. | -| [http\_tcp\_listener\_ids](#output\_http\_tcp\_listener\_ids) | The IDs of the TCP and HTTP load balancer listeners created. | -| [https\_listener\_arns](#output\_https\_listener\_arns) | The ARNs of the HTTPS load balancer listeners created. | -| [https\_listener\_ids](#output\_https\_listener\_ids) | The IDs of the load balancer listeners created. | -| [lb\_arn](#output\_lb\_arn) | The ID and ARN of the load balancer we created. | -| [lb\_arn\_suffix](#output\_lb\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch. | -| [lb\_dns\_name](#output\_lb\_dns\_name) | The DNS name of the load balancer. | -| [lb\_id](#output\_lb\_id) | The ID and ARN of the load balancer we created. | -| [lb\_zone\_id](#output\_lb\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records. | +| [arn](#output\_arn) | The ID and ARN of the load balancer we created | +| [arn\_suffix](#output\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch | +| [dns\_name](#output\_dns\_name) | The DNS name of the load balancer | +| [id](#output\_id) | The ID and ARN of the load balancer we created | +| [listener\_rules](#output\_listener\_rules) | Map of listeners rules created and their attributes | +| [listeners](#output\_listeners) | Map of listeners created and their attributes | +| [route53\_records](#output\_route53\_records) | The Route53 records created and attached to the load balancer | | [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | | [security\_group\_id](#output\_security\_group\_id) | ID of the security group | -| [target\_group\_arn\_suffixes](#output\_target\_group\_arn\_suffixes) | ARN suffixes of our target groups - can be used with CloudWatch. | -| [target\_group\_arns](#output\_target\_group\_arns) | ARNs of the target groups. Useful for passing to your Auto Scaling group. | -| [target\_group\_attachments](#output\_target\_group\_attachments) | ARNs of the target group attachment IDs. | -| [target\_group\_names](#output\_target\_group\_names) | Name of the target group. Useful for passing to your CodeDeploy Deployment Group. | +| [target\_groups](#output\_target\_groups) | Map of target groups created and their attributes | +| [zone\_id](#output\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records | diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 64dfcc48..099dc8ae 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -1,26 +1,20 @@ provider "aws" { region = local.region - - # Make it faster by skipping something - skip_metadata_api_check = true - skip_region_validation = true - skip_credentials_validation = true - skip_requesting_account_id = true } data "aws_availability_zones" "available" {} locals { - name = "ex-${basename(path.cwd)}" region = "eu-west-1" + name = "ex-${basename(path.cwd)}" vpc_cidr = "10.0.0.0/16" azs = slice(data.aws_availability_zones.available.names, 0, 3) tags = { + Name = local.name Example = local.name - GithubRepo = "terraform-aws-alb" - GithubOrg = "terraform-aws-modules" + Repository = "https://github.com/terraform-aws-modules/terraform-aws-alb" } } @@ -31,107 +25,252 @@ locals { module "alb" { source = "../../" - name = local.name - - load_balancer_type = "application" - + name = local.name vpc_id = module.vpc.vpc_id subnets = module.vpc.public_subnets - # Attach security groups - security_groups = [module.vpc.default_security_group_id] - # Attach rules to the created security group - security_group_rules = { - ingress_all_http = { - type = "ingress" + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { from_port = 80 - to_port = 80 - protocol = "tcp" + to_port = 82 + ip_protocol = "tcp" description = "HTTP web traffic" - cidr_blocks = ["0.0.0.0/0"] + cidr_ipv4 = "0.0.0.0/0" } - ingress_all_icmp = { - type = "ingress" - from_port = -1 - to_port = -1 - protocol = "icmp" - description = "ICMP" - cidr_blocks = ["0.0.0.0/0"] + all_https = { + from_port = 443 + to_port = 445 + ip_protocol = "tcp" + description = "HTTPS web traffic" + cidr_ipv4 = "0.0.0.0/0" } - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block } } - # # See notes in README (ref: https://github.com/terraform-providers/terraform-provider-aws/issues/7987) - # access_logs = { - # bucket = module.log_bucket.s3_bucket_id - # } - - http_tcp_listeners = [ - # Forward action is default, either when defined or undefined - { - port = 80 - protocol = "HTTP" - target_group_index = 0 - # action_type = "forward" - }, - { - port = 81 - protocol = "HTTP" - action_type = "forward" - forward = { + access_logs = { + bucket = module.log_bucket.s3_bucket_id + } + + listeners = { + ex-http-https-redirect = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + + rules = { + ex-fixed-response = { + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_header = { + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + } + }] + } + + ex-weighted-forward = { + priority = 4 + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "ex-lambda-with-trigger" + weight = 2 + }, + { + target_group_key = "ex-instance" + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_string = { + key = "weighted" + value = "true" + } + }] + } + + ex-redirect = { + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_string = { + key = "video" + value = "random" + } + }] + } + } + } + + ex-http-weighted-target = { + port = 81 + protocol = "HTTP" + weighted_forward = { target_groups = [ { - target_group_index = 0 - weight = 100 + target_group_key = "ex-lambda-with-trigger" + weight = 60 }, { - target_group_index = 1 - weight = 0 + target_group_key = "ex-instance" + weight = 40 } ] } - }, - { - port = 82 - protocol = "HTTP" - action_type = "redirect" - redirect = { - port = "443" - protocol = "HTTPS" - status_code = "HTTP_301" - } - }, - { - port = 83 - protocol = "HTTP" - action_type = "fixed-response" + } + + ex-fixed-response = { + port = 82 + protocol = "HTTP" fixed_response = { content_type = "text/plain" message_body = "Fixed message" status_code = "200" } - }, - ] - - https_listeners = [ - { - port = 443 - protocol = "HTTPS" - certificate_arn = module.acm.acm_certificate_arn - target_group_index = 1 - }, - # Authentication actions only allowed with HTTPS - { - port = 444 - protocol = "HTTPS" - action_type = "authenticate-cognito" - target_group_index = 1 - certificate_arn = module.acm.acm_certificate_arn + } + + ex-https = { + port = 443 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06" + certificate_arn = module.acm.acm_certificate_arn + additional_certificate_arns = [module.wildcard_cert.acm_certificate_arn] + + forward = { + target_group_key = "ex-instance" + } + + rules = { + ex-cognito = { + actions = [ + { + type = "authenticate-cognito" + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${local.name}" + session_timeout = 3600 + user_pool_arn = aws_cognito_user_pool.this.arn + user_pool_client_id = aws_cognito_user_pool_client.this.id + user_pool_domain = aws_cognito_user_pool_domain.this.domain + }, + { + type = "forward" + target_group_key = "ex-instance" + } + ] + + conditions = [{ + path_pattern = { + values = ["/some/auth/required/route"] + } + }] + } + + ex-fixed-response = { + priority = 3 + actions = [{ + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + }] + + conditions = [{ + http_header = { + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + } + }] + } + + ex-weighted-forward = { + priority = 4 + actions = [{ + type = "weighted-forward" + target_groups = [ + { + target_group_key = "ex-instance" + weight = 2 + }, + { + target_group_key = "ex-lambda-with-trigger" + weight = 1 + } + ] + stickiness = { + enabled = true + duration = 3600 + } + }] + + conditions = [{ + query_string = { + key = "weighted" + value = "true" + } + }] + } + + ex-redirect = { + priority = 5000 + actions = [{ + type = "redirect" + status_code = "HTTP_302" + host = "www.youtube.com" + path = "/watch" + query = "v=dQw4w9WgXcQ" + protocol = "HTTPS" + }] + + conditions = [{ + query_string = { + key = "video" + value = "random" + } + }] + } + } + } + + ex-cognito = { + port = 444 + protocol = "HTTPS" + certificate_arn = module.acm.acm_certificate_arn + authenticate_cognito = { authentication_request_extra_params = { display = "page" @@ -144,13 +283,49 @@ module "alb" { user_pool_client_id = aws_cognito_user_pool_client.this.id user_pool_domain = aws_cognito_user_pool_domain.this.domain } - }, - { - port = 445 - protocol = "HTTPS" - action_type = "authenticate-oidc" - target_group_index = 1 - certificate_arn = module.acm.acm_certificate_arn + + forward = { + target_group_key = "ex-instance" + } + + rules = { + ex-oidc = { + priority = 2 + + actions = [ + { + type = "authenticate-oidc" + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + authorization_endpoint = "https://${var.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${var.domain_name}" + token_endpoint = "https://${var.domain_name}/token" + user_info_endpoint = "https://${var.domain_name}/user_info" + }, + { + type = "forward" + target_group_key = "ex-lambda-with-trigger" + } + ] + + conditions = [{ + host_header = { + values = ["foobar.com"] + } + }] + } + } + } + + ex-oidc = { + port = 445 + protocol = "HTTPS" + certificate_arn = module.acm.acm_certificate_arn + action_type = "authenticate-oidc" authenticate_oidc = { authentication_request_extra_params = { display = "page" @@ -163,213 +338,22 @@ module "alb" { token_endpoint = "https://${var.domain_name}/token" user_info_endpoint = "https://${var.domain_name}/user_info" } - }, - ] - extra_ssl_certs = [ - { - https_listener_index = 0 - certificate_arn = module.wildcard_cert.acm_certificate_arn + forward = { + target_group_key = "ex-instance" + } } - ] - - https_listener_rules = [ - { - https_listener_index = 0 - - actions = [ - { - type = "authenticate-cognito" - - on_unauthenticated_request = "authenticate" - session_cookie_name = "session-${local.name}" - session_timeout = 3600 - user_pool_arn = aws_cognito_user_pool.this.arn - user_pool_client_id = aws_cognito_user_pool_client.this.id - user_pool_domain = aws_cognito_user_pool_domain.this.domain - }, - { - type = "forward" - target_group_index = 0 - } - ] - - conditions = [{ - path_patterns = ["/some/auth/required/route"] - }] - }, - { - https_listener_index = 1 - priority = 2 - - actions = [ - { - type = "authenticate-oidc" - - authentication_request_extra_params = { - display = "page" - prompt = "login" - } - authorization_endpoint = "https://${var.domain_name}/auth" - client_id = "client_id" - client_secret = "client_secret" - issuer = "https://${var.domain_name}" - token_endpoint = "https://${var.domain_name}/token" - user_info_endpoint = "https://${var.domain_name}/user_info" - }, - { - type = "forward" - target_group_index = 1 - } - ] - - conditions = [{ - host_headers = ["foobar.com"] - }] - }, - { - https_listener_index = 0 - priority = 3 - actions = [{ - type = "fixed-response" - content_type = "text/plain" - status_code = 200 - message_body = "This is a fixed response" - }] - - conditions = [{ - http_headers = [{ - http_header_name = "x-Gimme-Fixed-Response" - values = ["yes", "please", "right now"] - }] - }] - }, - { - https_listener_index = 0 - priority = 4 - - actions = [{ - type = "weighted-forward" - target_groups = [ - { - target_group_index = 1 - weight = 2 - }, - { - target_group_index = 0 - weight = 1 - } - ] - stickiness = { - enabled = true - duration = 3600 - } - }] - - conditions = [{ - query_strings = [{ - key = "weighted" - value = "true" - }] - }] - }, - { - https_listener_index = 0 - priority = 5000 - actions = [{ - type = "redirect" - status_code = "HTTP_302" - host = "www.youtube.com" - path = "/watch" - query = "v=dQw4w9WgXcQ" - protocol = "HTTPS" - }] - - conditions = [{ - query_strings = [{ - key = "video" - value = "random" - }] - }] - }, - ] - - http_tcp_listener_rules = [ - { - http_tcp_listener_index = 0 - priority = 3 - actions = [{ - type = "fixed-response" - content_type = "text/plain" - status_code = 200 - message_body = "This is a fixed response" - }] - - conditions = [{ - http_headers = [{ - http_header_name = "x-Gimme-Fixed-Response" - values = ["yes", "please", "right now"] - }] - }] - }, - { - http_tcp_listener_index = 0 - priority = 4 - - actions = [{ - type = "weighted-forward" - target_groups = [ - { - target_group_index = 1 - weight = 2 - }, - { - target_group_index = 0 - weight = 1 - } - ] - stickiness = { - enabled = true - duration = 3600 - } - }] - - conditions = [{ - query_strings = [{ - key = "weighted" - value = "true" - }] - }] - }, - { - http_tcp_listener_index = 0 - priority = 5000 - actions = [{ - type = "redirect" - status_code = "HTTP_302" - host = "www.youtube.com" - path = "/watch" - query = "v=dQw4w9WgXcQ" - protocol = "HTTPS" - }] - - conditions = [{ - query_strings = [{ - key = "video" - value = "random" - }] - }] - }, - ] - - target_groups = [ - { + } + + target_groups = { + ex-instance = { name_prefix = "h1" backend_protocol = "HTTP" backend_port = 80 target_type = "instance" deregistration_delay = 10 load_balancing_cross_zone_enabled = false + health_check = { enabled = true interval = 30 @@ -381,113 +365,56 @@ module "alb" { protocol = "HTTP" matcher = "200-399" } + protocol_version = "HTTP1" - targets = { - my_ec2 = { - target_id = aws_instance.this.id - port = 80 - }, - my_ec2_again = { - target_id = aws_instance.this.id - port = 8080 - } - } + target_id = aws_instance.this.id + port = 80 tags = { InstanceTargetGroupTag = "baz" } - }, - { + } + + ex-lambda-with-trigger = { name_prefix = "l1-" target_type = "lambda" lambda_multi_value_headers_enabled = true - targets = { - lambda_with_allowed_triggers = { - target_id = module.lambda_with_allowed_triggers.lambda_function_arn - } - } - }, - { - name_prefix = "l2-" - target_type = "lambda" - targets = { - lambda_without_allowed_triggers = { - target_id = module.lambda_without_allowed_triggers.lambda_function_arn - attach_lambda_permission = true - } - } - }, - ] - - tags = { - Project = "Unknown" - } - - lb_tags = { - MyLoadBalancer = "foo" - } - - target_group_tags = { - MyGlobalTargetGroupTag = "bar" - } + target_id = module.lambda_with_allowed_triggers.lambda_function_arn + } - https_listener_rules_tags = { - MyLoadBalancerHTTPSListenerRule = "bar" + ex-lambda-without-trigger = { + name_prefix = "l2-" + target_type = "lambda" + target_id = module.lambda_without_allowed_triggers.lambda_function_arn + attach_lambda_permission = true + } } - https_listeners_tags = { - MyLoadBalancerHTTPSListener = "bar" + # Route53 Record(s) + route53_records = { + A = { + name = local.name + type = "A" + zone_id = data.aws_route53_zone.this.id + } + AAAA = { + name = local.name + type = "AAAA" + zone_id = data.aws_route53_zone.this.id + } } - http_tcp_listeners_tags = { - MyLoadBalancerTCPListener = "bar" - } + tags = local.tags } -######################### -# LB will not be created -######################### - -module "lb_disabled" { +module "alb_disabled" { source = "../../" - create_lb = false -} - -################## -# Extra resources -################## - -data "aws_ami" "amazon_linux" { - most_recent = true - - owners = ["amazon"] - - filter { - name = "name" - - values = [ - "amzn-ami-hvm-*-x86_64-gp2", - ] - } - - filter { - name = "owner-alias" - - values = [ - "amazon", - ] - } -} - -resource "aws_instance" "this" { - ami = data.aws_ami.amazon_linux.id - instance_type = "t3.nano" - subnet_id = element(module.vpc.private_subnets, 0) + create = false } -############################################# +################################################################################ # Using packaged function from Lambda module -############################################# +################################################################################ locals { package_url = "https://raw.githubusercontent.com/terraform-aws-modules/terraform-aws-lambda/master/examples/fixtures/python3.8-zip/existing_package.zip" @@ -506,22 +433,21 @@ resource "null_resource" "download_package" { module "lambda_with_allowed_triggers" { source = "terraform-aws-modules/lambda/aws" - version = "~> 5.0" + version = "~> 6.0" function_name = "${local.name}-with-allowed-triggers" description = "My awesome lambda function (with allowed triggers)" handler = "index.lambda_handler" runtime = "python3.8" - publish = true - + publish = true create_package = false local_existing_package = local.downloaded allowed_triggers = { AllowExecutionFromELB = { service = "elasticloadbalancing" - source_arn = module.alb.target_group_arns[1] # index should match the correct target_group + source_arn = module.alb.target_groups["ex-lambda-with-trigger"].arn } } @@ -530,15 +456,14 @@ module "lambda_with_allowed_triggers" { module "lambda_without_allowed_triggers" { source = "terraform-aws-modules/lambda/aws" - version = "~> 5.0" + version = "~> 6.0" function_name = "${local.name}-without-allowed-triggers" description = "My awesome lambda function (without allowed triggers)" handler = "index.lambda_handler" runtime = "python3.8" - publish = true - + publish = true create_package = false local_existing_package = local.downloaded @@ -563,10 +488,6 @@ module "vpc" { private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - # Disabled NAT gateway to save a few seconds running this example - enable_nat_gateway = false - enable_dns_hostnames = true - tags = local.tags } @@ -590,6 +511,16 @@ module "wildcard_cert" { zone_id = data.aws_route53_zone.this.id } +data "aws_ssm_parameter" "al2" { + name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" +} + +resource "aws_instance" "this" { + ami = data.aws_ssm_parameter.al2.value + instance_type = "t3.nano" + subnet_id = element(module.vpc.private_subnets, 0) +} + ################################################################## # AWS Cognito User Pool ################################################################## @@ -612,3 +543,25 @@ resource "aws_cognito_user_pool_domain" "this" { domain = local.name user_pool_id = aws_cognito_user_pool.this.id } + +module "log_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket_prefix = "${local.name}-logs-" + acl = "log-delivery-write" + + # For example only + force_destroy = true + + control_object_ownership = true + object_ownership = "ObjectWriter" + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs + + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + + tags = local.tags +} diff --git a/examples/complete-alb/outputs.tf b/examples/complete-alb/outputs.tf index f80e8b5a..ffd55b30 100644 --- a/examples/complete-alb/outputs.tf +++ b/examples/complete-alb/outputs.tf @@ -1,66 +1,55 @@ -output "lb_id" { - description = "The ID and ARN of the load balancer we created." - value = module.alb.lb_id -} - -output "lb_arn" { - description = "The ID and ARN of the load balancer we created." - value = module.alb.lb_arn -} - -output "lb_dns_name" { - description = "The DNS name of the load balancer." - value = module.alb.lb_dns_name -} +################################################################################ +# Load Balancer +################################################################################ -output "lb_arn_suffix" { - description = "ARN suffix of our load balancer - can be used with CloudWatch." - value = module.alb.lb_arn_suffix +output "id" { + description = "The ID and ARN of the load balancer we created" + value = module.alb.id } -output "lb_zone_id" { - description = "The zone_id of the load balancer to assist with creating DNS records." - value = module.alb.lb_zone_id +output "arn" { + description = "The ID and ARN of the load balancer we created" + value = module.alb.arn } -output "http_tcp_listener_arns" { - description = "The ARN of the TCP and HTTP load balancer listeners created." - value = module.alb.http_tcp_listener_arns +output "arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch" + value = module.alb.arn_suffix } -output "http_tcp_listener_ids" { - description = "The IDs of the TCP and HTTP load balancer listeners created." - value = module.alb.http_tcp_listener_ids +output "dns_name" { + description = "The DNS name of the load balancer" + value = module.alb.dns_name } -output "https_listener_arns" { - description = "The ARNs of the HTTPS load balancer listeners created." - value = module.alb.https_listener_arns +output "zone_id" { + description = "The zone_id of the load balancer to assist with creating DNS records" + value = module.alb.zone_id } -output "https_listener_ids" { - description = "The IDs of the load balancer listeners created." - value = module.alb.https_listener_ids -} +################################################################################ +# Listener(s) +################################################################################ -output "target_group_arns" { - description = "ARNs of the target groups. Useful for passing to your Auto Scaling group." - value = module.alb.target_group_arns +output "listeners" { + description = "Map of listeners created and their attributes" + value = module.alb.listeners + sensitive = true } -output "target_group_arn_suffixes" { - description = "ARN suffixes of our target groups - can be used with CloudWatch." - value = module.alb.target_group_arn_suffixes +output "listener_rules" { + description = "Map of listeners rules created and their attributes" + value = module.alb.listener_rules + sensitive = true } -output "target_group_names" { - description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group." - value = module.alb.target_group_names -} +################################################################################ +# Target Group(s) +################################################################################ -output "target_group_attachments" { - description = "ARNs of the target group attachment IDs." - value = module.alb.target_group_attachments +output "target_groups" { + description = "Map of target groups created and their attributes" + value = module.alb.target_groups } ################################################################################ @@ -76,3 +65,12 @@ output "security_group_id" { description = "ID of the security group" value = module.alb.security_group_id } + +################################################################################ +# Route53 Record(s) +################################################################################ + +output "route53_records" { + description = "The Route53 records created and attached to the load balancer" + value = module.alb.route53_records +} diff --git a/examples/complete-alb/versions.tf b/examples/complete-alb/versions.tf index d12294a8..807bfb0d 100644 --- a/examples/complete-alb/versions.tf +++ b/examples/complete-alb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.59" + version = ">= 5.13" } null = { source = "hashicorp/null" diff --git a/examples/complete-nlb/README.md b/examples/complete-nlb/README.md index b0337bba..28040a9c 100644 --- a/examples/complete-nlb/README.md +++ b/examples/complete-nlb/README.md @@ -20,19 +20,20 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.59 | +| [aws](#requirement\_aws) | >= 5.13 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.59 | +| [aws](#provider\_aws) | >= 5.13 | ## Modules | Name | Source | Version | |------|--------|---------| | [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | +| [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | | [nlb](#module\_nlb) | ../../ | n/a | | [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | @@ -41,8 +42,10 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Type | |------|------| | [aws_eip.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | +| [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | | [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +| [aws_ssm_parameter.al2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | ## Inputs @@ -54,16 +57,15 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Description | |------|-------------| -| [http\_tcp\_listener\_arns](#output\_http\_tcp\_listener\_arns) | The ARN of the TCP and HTTP load balancer listeners created. | -| [http\_tcp\_listener\_ids](#output\_http\_tcp\_listener\_ids) | The IDs of the TCP and HTTP load balancer listeners created. | -| [https\_listener\_arns](#output\_https\_listener\_arns) | The ARNs of the HTTPS load balancer listeners created. | -| [https\_listener\_ids](#output\_https\_listener\_ids) | The IDs of the load balancer listeners created. | -| [lb\_arn](#output\_lb\_arn) | The ID and ARN of the load balancer we created. | -| [lb\_arn\_suffix](#output\_lb\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch. | -| [lb\_dns\_name](#output\_lb\_dns\_name) | The DNS name of the load balancer. | -| [lb\_id](#output\_lb\_id) | The ID and ARN of the load balancer we created. | -| [lb\_zone\_id](#output\_lb\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records. | -| [target\_group\_arn\_suffixes](#output\_target\_group\_arn\_suffixes) | ARN suffixes of our target groups - can be used with CloudWatch. | -| [target\_group\_arns](#output\_target\_group\_arns) | ARNs of the target groups. Useful for passing to your Auto Scaling group. | -| [target\_group\_names](#output\_target\_group\_names) | Name of the target group. Useful for passing to your CodeDeploy Deployment Group. | +| [arn](#output\_arn) | The ID and ARN of the load balancer we created | +| [arn\_suffix](#output\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch | +| [dns\_name](#output\_dns\_name) | The DNS name of the load balancer | +| [id](#output\_id) | The ID and ARN of the load balancer we created | +| [listener\_rules](#output\_listener\_rules) | Map of listeners rules created and their attributes | +| [listeners](#output\_listeners) | Map of listeners created and their attributes | +| [route53\_records](#output\_route53\_records) | The Route53 records created and attached to the load balancer | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [target\_groups](#output\_target\_groups) | Map of target groups created and their attributes | +| [zone\_id](#output\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records | diff --git a/examples/complete-nlb/main.tf b/examples/complete-nlb/main.tf index fbb5e056..2340bdcf 100644 --- a/examples/complete-nlb/main.tf +++ b/examples/complete-nlb/main.tf @@ -1,26 +1,20 @@ provider "aws" { region = local.region - - # Make it faster by skipping something - skip_metadata_api_check = true - skip_region_validation = true - skip_credentials_validation = true - skip_requesting_account_id = true } data "aws_availability_zones" "available" {} locals { - name = "ex-${basename(path.cwd)}" region = "eu-west-1" + name = "ex-${basename(path.cwd)}" vpc_cidr = "10.0.0.0/16" azs = slice(data.aws_availability_zones.available.names, 0, 3) tags = { + Name = local.name Example = local.name - GithubRepo = "terraform-aws-alb" - GithubOrg = "terraform-aws-modules" + Repository = "https://github.com/terraform-aws-modules/terraform-aws-alb" } } @@ -36,73 +30,116 @@ module "nlb" { load_balancer_type = "network" vpc_id = module.vpc.vpc_id - # Use `subnets` if you don't want to attach EIPs + # https://github.com/hashicorp/terraform-provider-aws/issues/17281 # subnets = module.vpc.private_subnets # Use `subnet_mapping` to attach EIPs - subnet_mapping = [for i, eip in aws_eip.this : { allocation_id : eip.id, subnet_id : module.vpc.private_subnets[i] }] - - # # See notes in README (ref: https://github.com/terraform-providers/terraform-provider-aws/issues/7987) - # access_logs = { - # bucket = module.log_bucket.s3_bucket_id - # } - - # TCP_UDP, UDP, TCP - http_tcp_listeners = [ - { - port = 81 - protocol = "TCP_UDP" - target_group_index = 0 - }, + subnet_mapping = [for i, eip in aws_eip.this : { - port = 82 - protocol = "UDP" - target_group_index = 1 - }, - { - port = 83 - protocol = "TCP" - target_group_index = 2 - }, + allocation_id = eip.id + subnet_id = module.vpc.private_subnets[i] + } ] - # TLS - https_listeners = [ - { - port = 84 - protocol = "TLS" - certificate_arn = module.acm.acm_certificate_arn - target_group_index = 3 - }, - ] + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_tcp = { + from_port = 80 + to_port = 84 + ip_protocol = "tcp" + description = "TCP traffic" + cidr_ipv4 = "0.0.0.0/0" + } + all_udp = { + from_port = 80 + to_port = 84 + ip_protocol = "udp" + description = "UDP traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } - target_groups = [ - { - name_prefix = "tu1-" - backend_protocol = "TCP_UDP" - backend_port = 81 + access_logs = { + bucket = module.log_bucket.s3_bucket_id + } + + listeners = { + ex-one = { + port = 81 + protocol = "TCP_UDP" + forward = { + target_group_key = "ex-target-one" + } + } + + ex-two = { + port = 82 + protocol = "UDP" + forward = { + target_group_key = "ex-target-two" + } + } + + ex-three = { + port = 83 + protocol = "TCP" + forward = { + target_group_key = "ex-target-three" + } + } + + ex-four = { + port = 84 + protocol = "TLS" + certificate_arn = module.acm.acm_certificate_arn + forward = { + target_group_key = "ex-target-four" + } + } + } + + target_groups = { + ex-target-one = { + name_prefix = "t1-" + protocol = "TCP_UDP" + port = 81 target_type = "instance" + target_id = aws_instance.this.id connection_termination = true preserve_client_ip = true + stickiness = { - enabled = true - type = "source_ip" + type = "source_ip" } + tags = { tcp_udp = true } - }, - { - name_prefix = "u1-" - backend_protocol = "UDP" - backend_port = 82 - target_type = "instance" - }, - { - name_prefix = "t1-" - backend_protocol = "TCP" - backend_port = 83 + } + + ex-target-two = { + name_prefix = "t2-" + protocol = "UDP" + port = 82 + target_type = "instance" + target_id = aws_instance.this.id + } + + ex-target-three = { + name_prefix = "t3-" + protocol = "TCP" + port = 83 target_type = "ip" + target_id = aws_instance.this.private_ip deregistration_delay = 10 health_check = { enabled = true @@ -113,14 +150,16 @@ module "nlb" { unhealthy_threshold = 3 timeout = 6 } - }, - { - name_prefix = "t2-" - backend_protocol = "TLS" - backend_port = 84 - target_type = "instance" - }, - ] + } + + ex-target-four = { + name_prefix = "t4-" + protocol = "TLS" + port = 84 + target_type = "instance" + target_id = aws_instance.this.id + } + } tags = local.tags } @@ -140,10 +179,6 @@ module "vpc" { private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] - # Disabled NAT gateway to save a few seconds running this example - enable_nat_gateway = false - enable_dns_hostnames = true - tags = local.tags } @@ -164,3 +199,35 @@ resource "aws_eip" "this" { domain = "vpc" } + +data "aws_ssm_parameter" "al2" { + name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" +} + +resource "aws_instance" "this" { + ami = data.aws_ssm_parameter.al2.value + instance_type = "t3.nano" + subnet_id = element(module.vpc.private_subnets, 0) +} + +module "log_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket_prefix = "${local.name}-logs-" + acl = "log-delivery-write" + + # For example only + force_destroy = true + + control_object_ownership = true + object_ownership = "ObjectWriter" + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs + + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + + tags = local.tags +} diff --git a/examples/complete-nlb/outputs.tf b/examples/complete-nlb/outputs.tf index 40f57a9d..aa5fa74f 100644 --- a/examples/complete-nlb/outputs.tf +++ b/examples/complete-nlb/outputs.tf @@ -1,59 +1,74 @@ -output "lb_id" { - description = "The ID and ARN of the load balancer we created." - value = module.nlb.lb_id -} +################################################################################ +# Load Balancer +################################################################################ -output "lb_arn" { - description = "The ID and ARN of the load balancer we created." - value = module.nlb.lb_arn +output "id" { + description = "The ID and ARN of the load balancer we created" + value = module.nlb.id } -output "lb_dns_name" { - description = "The DNS name of the load balancer." - value = module.nlb.lb_dns_name +output "arn" { + description = "The ID and ARN of the load balancer we created" + value = module.nlb.arn } -output "lb_arn_suffix" { - description = "ARN suffix of our load balancer - can be used with CloudWatch." - value = module.nlb.lb_arn_suffix +output "arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch" + value = module.nlb.arn_suffix } -output "lb_zone_id" { - description = "The zone_id of the load balancer to assist with creating DNS records." - value = module.nlb.lb_zone_id +output "dns_name" { + description = "The DNS name of the load balancer" + value = module.nlb.dns_name } -output "http_tcp_listener_arns" { - description = "The ARN of the TCP and HTTP load balancer listeners created." - value = module.nlb.http_tcp_listener_arns +output "zone_id" { + description = "The zone_id of the load balancer to assist with creating DNS records" + value = module.nlb.zone_id } -output "http_tcp_listener_ids" { - description = "The IDs of the TCP and HTTP load balancer listeners created." - value = module.nlb.http_tcp_listener_ids +################################################################################ +# Listener(s) +################################################################################ + +output "listeners" { + description = "Map of listeners created and their attributes" + value = module.nlb.listeners } -output "https_listener_arns" { - description = "The ARNs of the HTTPS load balancer listeners created." - value = module.nlb.https_listener_arns +output "listener_rules" { + description = "Map of listeners rules created and their attributes" + value = module.nlb.listener_rules } -output "https_listener_ids" { - description = "The IDs of the load balancer listeners created." - value = module.nlb.https_listener_ids +################################################################################ +# Target Group(s) +################################################################################ + +output "target_groups" { + description = "Map of target groups created and their attributes" + value = module.nlb.target_groups } -output "target_group_arns" { - description = "ARNs of the target groups. Useful for passing to your Auto Scaling group." - value = module.nlb.target_group_arns +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.nlb.security_group_arn } -output "target_group_arn_suffixes" { - description = "ARN suffixes of our target groups - can be used with CloudWatch." - value = module.nlb.target_group_arn_suffixes +output "security_group_id" { + description = "ID of the security group" + value = module.nlb.security_group_id } -output "target_group_names" { - description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group." - value = module.nlb.target_group_names +################################################################################ +# Route53 Record(s) +################################################################################ + +output "route53_records" { + description = "The Route53 records created and attached to the load balancer" + value = module.nlb.route53_records } diff --git a/examples/complete-nlb/versions.tf b/examples/complete-nlb/versions.tf index 33eb30cc..4e31bbf0 100644 --- a/examples/complete-nlb/versions.tf +++ b/examples/complete-nlb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.59" + version = ">= 5.13" } } } diff --git a/main.tf b/main.tf index 63dc9662..b64a95e1 100644 --- a/main.tf +++ b/main.tf @@ -1,798 +1,541 @@ +data "aws_partition" "current" {} + locals { - create_lb = var.create_lb && var.putin_khuylo + create = var.create && var.putin_khuylo + tags = merge(var.tags, { terraform-aws-modules = "alb" }) } +################################################################################ +# Load Balancer +################################################################################ + resource "aws_lb" "this" { - count = local.create_lb ? 1 : 0 + count = local.create ? 1 : 0 - name = var.name - name_prefix = var.name_prefix + dynamic "access_logs" { + for_each = length(var.access_logs) > 0 ? [var.access_logs] : [] - load_balancer_type = var.load_balancer_type - internal = var.internal - security_groups = var.create_security_group && var.load_balancer_type == "application" ? concat([aws_security_group.this[0].id], var.security_groups) : var.security_groups - subnets = var.subnets + content { + bucket = access_logs.value.bucket + enabled = try(access_logs.value.enabled, true) + prefix = try(access_logs.value.prefix, null) + } + } - idle_timeout = var.idle_timeout + customer_owned_ipv4_pool = var.customer_owned_ipv4_pool + desync_mitigation_mode = var.desync_mitigation_mode + drop_invalid_header_fields = var.drop_invalid_header_fields enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing enable_deletion_protection = var.enable_deletion_protection enable_http2 = var.enable_http2 enable_tls_version_and_cipher_suite_headers = var.enable_tls_version_and_cipher_suite_headers + enable_waf_fail_open = var.enable_waf_fail_open enable_xff_client_port = var.enable_xff_client_port + idle_timeout = var.idle_timeout + internal = var.internal ip_address_type = var.ip_address_type - drop_invalid_header_fields = var.drop_invalid_header_fields + load_balancer_type = var.load_balancer_type + name = var.name + name_prefix = var.name_prefix preserve_host_header = var.preserve_host_header - enable_waf_fail_open = var.enable_waf_fail_open - desync_mitigation_mode = var.desync_mitigation_mode - xff_header_processing_mode = var.xff_header_processing_mode - - dynamic "access_logs" { - for_each = length(var.access_logs) > 0 ? [var.access_logs] : [] - - content { - enabled = try(access_logs.value.enabled, try(access_logs.value.bucket, null) != null) - bucket = try(access_logs.value.bucket, null) - prefix = try(access_logs.value.prefix, null) - } - } + security_groups = var.create_security_group ? concat([aws_security_group.this[0].id], var.security_groups) : var.security_groups dynamic "subnet_mapping" { for_each = var.subnet_mapping content { - subnet_id = subnet_mapping.value.subnet_id allocation_id = lookup(subnet_mapping.value, "allocation_id", null) - private_ipv4_address = lookup(subnet_mapping.value, "private_ipv4_address", null) ipv6_address = lookup(subnet_mapping.value, "ipv6_address", null) + private_ipv4_address = lookup(subnet_mapping.value, "private_ipv4_address", null) + subnet_id = subnet_mapping.value.subnet_id } } - tags = merge( - { - Name = (var.name != null) ? var.name : var.name_prefix - }, - var.tags, - var.lb_tags, - ) + subnets = var.subnets + tags = local.tags + xff_header_processing_mode = var.xff_header_processing_mode timeouts { - create = var.load_balancer_create_timeout - update = var.load_balancer_update_timeout - delete = var.load_balancer_delete_timeout + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) } } -resource "aws_lb_target_group" "main" { - count = local.create_lb ? length(var.target_groups) : 0 - - name = lookup(var.target_groups[count.index], "name", null) - name_prefix = lookup(var.target_groups[count.index], "name_prefix", null) - - vpc_id = var.vpc_id - port = try(var.target_groups[count.index].backend_port, null) - protocol = try(upper(var.target_groups[count.index].backend_protocol), null) - protocol_version = try(upper(var.target_groups[count.index].protocol_version), null) - target_type = try(var.target_groups[count.index].target_type, null) - - connection_termination = try(var.target_groups[count.index].connection_termination, null) - deregistration_delay = try(var.target_groups[count.index].deregistration_delay, null) - slow_start = try(var.target_groups[count.index].slow_start, null) - proxy_protocol_v2 = try(var.target_groups[count.index].proxy_protocol_v2, false) - lambda_multi_value_headers_enabled = try(var.target_groups[count.index].lambda_multi_value_headers_enabled, false) - load_balancing_algorithm_type = try(var.target_groups[count.index].load_balancing_algorithm_type, null) - preserve_client_ip = try(var.target_groups[count.index].preserve_client_ip, null) - ip_address_type = try(var.target_groups[count.index].ip_address_type, null) - load_balancing_cross_zone_enabled = try(var.target_groups[count.index].load_balancing_cross_zone_enabled, null) - - dynamic "health_check" { - for_each = try([var.target_groups[count.index].health_check], []) - - content { - enabled = try(health_check.value.enabled, null) - interval = try(health_check.value.interval, null) - path = try(health_check.value.path, null) - port = try(health_check.value.port, null) - healthy_threshold = try(health_check.value.healthy_threshold, null) - unhealthy_threshold = try(health_check.value.unhealthy_threshold, null) - timeout = try(health_check.value.timeout, null) - protocol = try(health_check.value.protocol, null) - matcher = try(health_check.value.matcher, null) - } - } - - dynamic "stickiness" { - for_each = try([var.target_groups[count.index].stickiness], []) - - content { - enabled = try(stickiness.value.enabled, null) - cookie_duration = try(stickiness.value.cookie_duration, null) - type = try(stickiness.value.type, null) - cookie_name = try(stickiness.value.cookie_name, null) - } - } - - tags = merge( - var.tags, - var.target_group_tags, - lookup(var.target_groups[count.index], "tags", {}), - { - "Name" = try(var.target_groups[count.index].name, var.target_groups[count.index].name_prefix, "") - }, - ) - - lifecycle { - create_before_destroy = true - } -} - -locals { - # Merge the target group index into a product map of the targets so we - # can figure out what target group we should attach each target to. - # Target indexes can be dynamically defined, but need to match - # the function argument reference. This means any additional arguments - # can be added later and only need to be updated in the attachment resource below. - # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment#argument-reference - target_group_attachments = merge(flatten([ - for index, group in var.target_groups : [ - for k, targets in group : { - for target_key, target in targets : join(".", [index, target_key]) => merge({ tg_index = index }, target) - } - if k == "targets" - ] - ])...) - - # Filter out the attachments for lambda functions. The ALB target group needs permission to forward a request on to - # the specified lambda function. This filtered list is used to create those permission resources. - # To get the lambda_function_name, the 6th index is taken from the lambda_function_arn format below - # arn:aws:lambda:::function:my-function-name: - target_group_attachments_lambda = { - for k, v in local.target_group_attachments : - (k) => merge(v, { lambda_function_name = split(":", v.target_id)[6] }) - if try(v.attach_lambda_permission, false) - } -} - -resource "aws_lambda_permission" "lb" { - for_each = { for k, v in local.target_group_attachments_lambda : k => v if local.create_lb } - - function_name = each.value.lambda_function_name - qualifier = try(each.value.lambda_qualifier, null) - - statement_id = try(each.value.lambda_statement_id, "AllowExecutionFromLb") - action = try(each.value.lambda_action, "lambda:InvokeFunction") - principal = try(each.value.lambda_principal, "elasticloadbalancing.amazonaws.com") - source_arn = aws_lb_target_group.main[each.value.tg_index].arn - source_account = try(each.value.lambda_source_account, null) - event_source_token = try(each.value.lambda_event_source_token, null) -} - -resource "aws_lb_target_group_attachment" "this" { - for_each = { for k, v in local.target_group_attachments : k => v if local.create_lb } - - target_group_arn = aws_lb_target_group.main[each.value.tg_index].arn - target_id = each.value.target_id - port = lookup(each.value, "port", null) - availability_zone = lookup(each.value, "availability_zone", null) - - depends_on = [aws_lambda_permission.lb] -} +################################################################################ +# Listener(s) +################################################################################ -resource "aws_lb_listener_rule" "https_listener_rule" { - count = local.create_lb ? length(var.https_listener_rules) : 0 +resource "aws_lb_listener" "this" { + for_each = { for k, v in var.listeners : k => v if local.create } - listener_arn = aws_lb_listener.frontend_https[lookup(var.https_listener_rules[count.index], "https_listener_index", count.index)].arn - priority = lookup(var.https_listener_rules[count.index], "priority", null) + alpn_policy = try(each.value.alpn_policy, null) + certificate_arn = try(each.value.certificate_arn, null) - # authenticate-cognito actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "authenticate-cognito" - ] + dynamic "default_action" { + for_each = try([each.value.authenticate_cognito], []) content { - type = action.value["type"] authenticate_cognito { - authentication_request_extra_params = lookup(action.value, "authentication_request_extra_params", null) - on_unauthenticated_request = lookup(action.value, "on_authenticated_request", null) - scope = lookup(action.value, "scope", null) - session_cookie_name = lookup(action.value, "session_cookie_name", null) - session_timeout = lookup(action.value, "session_timeout", null) - user_pool_arn = action.value["user_pool_arn"] - user_pool_client_id = action.value["user_pool_client_id"] - user_pool_domain = action.value["user_pool_domain"] + authentication_request_extra_params = try(default_action.value.authentication_request_extra_params, null) + on_unauthenticated_request = try(default_action.value.on_unauthenticated_request, null) + scope = try(default_action.value.scope, null) + session_cookie_name = try(default_action.value.session_cookie_name, null) + session_timeout = try(default_action.value.session_timeout, null) + user_pool_arn = default_action.value.user_pool_arn + user_pool_client_id = default_action.value.user_pool_client_id + user_pool_domain = default_action.value.user_pool_domain } + + order = try(default_action.value.order, null) + type = "authenticate-cognito" } } - # authenticate-oidc actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "authenticate-oidc" - ] + dynamic "default_action" { + for_each = try([each.value.authenticate_oidc], []) content { - type = action.value["type"] authenticate_oidc { - # Max 10 extra params - authentication_request_extra_params = lookup(action.value, "authentication_request_extra_params", null) - authorization_endpoint = action.value["authorization_endpoint"] - client_id = action.value["client_id"] - client_secret = action.value["client_secret"] - issuer = action.value["issuer"] - on_unauthenticated_request = lookup(action.value, "on_unauthenticated_request", null) - scope = lookup(action.value, "scope", null) - session_cookie_name = lookup(action.value, "session_cookie_name", null) - session_timeout = lookup(action.value, "session_timeout", null) - token_endpoint = action.value["token_endpoint"] - user_info_endpoint = action.value["user_info_endpoint"] + authentication_request_extra_params = try(default_action.value.authentication_request_extra_params, null) + authorization_endpoint = default_action.value.authorization_endpoint + client_id = default_action.value.client_id + client_secret = default_action.value.client_secret + issuer = default_action.value.issuer + on_unauthenticated_request = try(default_action.value.on_unauthenticated_request, null) + scope = try(default_action.value.scope, null) + session_cookie_name = try(default_action.value.session_cookie_name, null) + session_timeout = try(default_action.value.session_timeout, null) + token_endpoint = default_action.value.token_endpoint + user_info_endpoint = default_action.value.user_info_endpoint } - } - } - - # redirect actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "redirect" - ] - content { - type = action.value["type"] - redirect { - host = lookup(action.value, "host", null) - path = lookup(action.value, "path", null) - port = lookup(action.value, "port", null) - protocol = lookup(action.value, "protocol", null) - query = lookup(action.value, "query", null) - status_code = action.value["status_code"] - } + order = try(default_action.value.order, null) + type = "authenticate-oidc" } } - # fixed-response actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "fixed-response" - ] + dynamic "default_action" { + for_each = try([each.value.fixed_response], []) content { - type = action.value["type"] fixed_response { - message_body = lookup(action.value, "message_body", null) - status_code = lookup(action.value, "status_code", null) - content_type = action.value["content_type"] + content_type = default_action.value.content_type + message_body = try(default_action.value.message_body, null) + status_code = try(default_action.value.status_code, null) } + + order = try(default_action.value.order, null) + type = "fixed-response" } } - # forward actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "forward" - ] + dynamic "default_action" { + for_each = try([each.value.forward], []) content { - type = action.value["type"] - target_group_arn = aws_lb_target_group.main[lookup(action.value, "target_group_index", count.index)].id + order = try(default_action.value.order, null) + target_group_arn = length(try(default_action.value.target_groups, [])) > 0 ? null : try(default_action.value.arn, aws_lb_target_group.this[default_action.value.target_group_key].arn, null) + type = "forward" } } - # weighted forward actions - dynamic "action" { - for_each = [ - for action_rule in var.https_listener_rules[count.index].actions : - action_rule - if action_rule.type == "weighted-forward" - ] + dynamic "default_action" { + for_each = try([each.value.weighted_forward], []) content { - type = "forward" forward { dynamic "target_group" { - for_each = action.value["target_groups"] + for_each = try(default_action.value.target_groups, []) content { - arn = aws_lb_target_group.main[target_group.value["target_group_index"]].id - weight = target_group.value["weight"] + arn = try(target_group.value.arn, aws_lb_target_group.this[target_group.value.target_group_key].arn, null) + weight = try(target_group.value.weight, null) } } + dynamic "stickiness" { - for_each = [lookup(action.value, "stickiness", {})] + for_each = try([default_action.value.stickiness], []) content { - enabled = try(stickiness.value["enabled"], false) - duration = try(stickiness.value["duration"], 1) + duration = try(stickiness.value.duration, 60) + enabled = try(stickiness.value.enabled, null) } } } - } - } - - # Path Pattern condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "path_patterns", [])) > 0 - ] - content { - path_pattern { - values = condition.value["path_patterns"] - } + order = try(default_action.value.order, null) + type = "forward" } } - # Host header condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "host_headers", [])) > 0 - ] + dynamic "default_action" { + for_each = try([each.value.redirect], []) content { - host_header { - values = condition.value["host_headers"] + order = try(default_action.value.order, null) + + redirect { + host = try(default_action.value.host, null) + path = try(default_action.value.path, null) + port = try(default_action.value.port, null) + protocol = try(default_action.value.protocol, null) + query = try(default_action.value.query, null) + status_code = default_action.value.status_code } + + type = "redirect" } } - # Http header condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "http_headers", [])) > 0 - ] - - content { - dynamic "http_header" { - for_each = condition.value["http_headers"] + load_balancer_arn = aws_lb.this[0].arn + port = try(each.value.port, var.default_port) + protocol = try(each.value.protocol, var.default_protocol) + ssl_policy = contains(["HTTPS", "TLS"], try(each.value.protocol, var.default_protocol)) ? try(each.value.ssl_policy, "ELBSecurityPolicy-TLS13-1-2-Res-2021-06") : try(each.value.ssl_policy, null) + tags = merge(local.tags, try(each.value.tags, {})) +} - content { - http_header_name = http_header.value["http_header_name"] - values = http_header.value["values"] - } - } - } - } +################################################################################ +# Listener Rule(s) +################################################################################ - # Http request method condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "http_request_methods", [])) > 0 +locals { + # This allows rules to be specified under the listener definition + listener_rules = flatten([ + for listener_key, listener_values in var.listeners : [ + for rule_key, rule_values in lookup(listener_values, "rules", {}) : + merge(rule_values, { + listener_key = listener_key + rule_key = rule_key + }) ] + ]) +} - content { - http_request_method { - values = condition.value["http_request_methods"] - } - } - } +resource "aws_lb_listener_rule" "this" { + for_each = { for v in local.listener_rules : "${v.listener_key}/${v.rule_key}" => v if local.create } - # Query string condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "query_strings", [])) > 0 - ] + listener_arn = try(each.value.listener_arn, aws_lb_listener.this[each.value.listener_key].arn) + priority = try(each.value.priority, null) + + dynamic "action" { + for_each = [for action in each.value.actions : action if action.type == "authenticate-cognito"] content { - dynamic "query_string" { - for_each = condition.value["query_strings"] + type = "authenticate-cognito" + order = try(action.value.order, null) - content { - key = lookup(query_string.value, "key", null) - value = query_string.value["value"] - } + authenticate_cognito { + authentication_request_extra_params = try(action.value.authentication_request_extra_params, null) + on_unauthenticated_request = try(action.value.on_unauthenticated_request, null) + scope = try(action.value.scope, null) + session_cookie_name = try(action.value.session_cookie_name, null) + session_timeout = try(action.value.session_timeout, null) + user_pool_arn = action.value.user_pool_arn + user_pool_client_id = action.value.user_pool_client_id + user_pool_domain = action.value.user_pool_domain } } } - # Source IP address condition - dynamic "condition" { - for_each = [ - for condition_rule in var.https_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "source_ips", [])) > 0 - ] + dynamic "action" { + for_each = [for action in each.value.actions : action if action.type == "authenticate-oidc"] content { - source_ip { - values = condition.value["source_ips"] + type = "authenticate-oidc" + order = try(action.value.order, null) + + authenticate_oidc { + authentication_request_extra_params = try(action.value.authentication_request_extra_params, null) + authorization_endpoint = action.value.authorization_endpoint + client_id = action.value.client_id + client_secret = action.value.client_secret + issuer = action.value.issuer + on_unauthenticated_request = try(action.value.on_unauthenticated_request, null) + scope = try(action.value.scope, null) + session_cookie_name = try(action.value.session_cookie_name, null) + session_timeout = try(action.value.session_timeout, null) + token_endpoint = action.value.token_endpoint + user_info_endpoint = action.value.user_info_endpoint } } } - tags = merge( - var.tags, - var.https_listener_rules_tags, - lookup(var.https_listener_rules[count.index], "tags", {}), - ) -} - -resource "aws_lb_listener_rule" "http_tcp_listener_rule" { - count = local.create_lb ? length(var.http_tcp_listener_rules) : 0 - - listener_arn = aws_lb_listener.frontend_http_tcp[lookup(var.http_tcp_listener_rules[count.index], "http_tcp_listener_index", count.index)].arn - priority = lookup(var.http_tcp_listener_rules[count.index], "priority", null) - - # redirect actions dynamic "action" { - for_each = [ - for action_rule in var.http_tcp_listener_rules[count.index].actions : - action_rule - if action_rule.type == "redirect" - ] + for_each = [for action in each.value.actions : action if action.type == "redirect"] content { - type = action.value["type"] + type = "redirect" + order = try(action.value.order, null) + redirect { - host = lookup(action.value, "host", null) - path = lookup(action.value, "path", null) - port = lookup(action.value, "port", null) - protocol = lookup(action.value, "protocol", null) - query = lookup(action.value, "query", null) - status_code = action.value["status_code"] + host = try(action.value.host, null) + path = try(action.value.path, null) + port = try(action.value.port, null) + protocol = try(action.value.protocol, null) + query = try(action.value.query, null) + status_code = action.value.status_code } } } - # fixed-response actions dynamic "action" { - for_each = [ - for action_rule in var.http_tcp_listener_rules[count.index].actions : - action_rule - if action_rule.type == "fixed-response" - ] + for_each = [for action in each.value.actions : action if action.type == "fixed-response"] content { - type = action.value["type"] + type = "fixed-response" + order = try(action.value.order, null) + fixed_response { - message_body = lookup(action.value, "message_body", null) - status_code = lookup(action.value, "status_code", null) - content_type = action.value["content_type"] + content_type = action.value.content_type + message_body = try(action.value.message_body, null) + status_code = try(action.value.status_code, null) } } } - # forward actions dynamic "action" { - for_each = [ - for action_rule in var.http_tcp_listener_rules[count.index].actions : - action_rule - if action_rule.type == "forward" - ] + for_each = [for action in each.value.actions : action if action.type == "forward"] content { - type = action.value["type"] - target_group_arn = aws_lb_target_group.main[lookup(action.value, "target_group_index", count.index)].id + type = "forward" + order = try(action.value.order, null) + target_group_arn = try(action.value.target_group_arn, aws_lb_target_group.this[action.value.target_group_key].arn, null) } } - # weighted forward actions dynamic "action" { - for_each = [ - for action_rule in var.http_tcp_listener_rules[count.index].actions : - action_rule - if action_rule.type == "weighted-forward" - ] + for_each = [for action in each.value.actions : action if action.type == "weighted-forward"] content { - type = "forward" + type = "forward" + order = try(action.value.order, null) + forward { dynamic "target_group" { - for_each = action.value["target_groups"] + for_each = try(action.value.target_groups, []) content { - arn = aws_lb_target_group.main[target_group.value["target_group_index"]].id - weight = target_group.value["weight"] + arn = try(target_group.value.arn, aws_lb_target_group.this[target_group.value.target_group_key].arn) + weight = try(target_group.value.weight, null) } } + dynamic "stickiness" { - for_each = [lookup(action.value, "stickiness", {})] + for_each = try([action.value.stickiness], []) content { - enabled = try(stickiness.value["enabled"], false) - duration = try(stickiness.value["duration"], 1) + enabled = try(stickiness.value.enabled, null) + duration = try(stickiness.value.duration, 60) } } } } } - # Path Pattern condition dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "path_patterns", [])) > 0 - ] + for_each = try(each.value.conditions, []) content { - path_pattern { - values = condition.value["path_patterns"] - } - } - } + dynamic "host_header" { + for_each = try([condition.value.host_header], []) - # Host header condition - dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "host_headers", [])) > 0 - ] - - content { - host_header { - values = condition.value["host_headers"] + content { + values = host_header.value.values + } } - } - } - # Http header condition - dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "http_headers", [])) > 0 - ] - - content { dynamic "http_header" { - for_each = condition.value["http_headers"] + for_each = try([condition.value.http_header], []) content { - http_header_name = http_header.value["http_header_name"] - values = http_header.value["values"] + http_header_name = http_header.value.http_header_name + values = http_header.value.values } } - } - } - # Http request method condition - dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "http_request_methods", [])) > 0 - ] + dynamic "http_request_method" { + for_each = try([condition.value.http_request_method], []) - content { - http_request_method { - values = condition.value["http_request_methods"] + content { + values = http_request_method.value.values + } } - } - } - # Query string condition - dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "query_strings", [])) > 0 - ] + dynamic "path_pattern" { + for_each = try([condition.value.path_pattern], []) + + content { + values = path_pattern.value.values + } + } - content { dynamic "query_string" { - for_each = condition.value["query_strings"] + for_each = try([condition.value.query_string], []) content { - key = lookup(query_string.value, "key", null) - value = query_string.value["value"] + key = try(query_string.value.key, null) + value = query_string.value.value } } - } - } - # Source IP address condition - dynamic "condition" { - for_each = [ - for condition_rule in var.http_tcp_listener_rules[count.index].conditions : - condition_rule - if length(lookup(condition_rule, "source_ips", [])) > 0 - ] + dynamic "source_ip" { + for_each = try([condition.value.source_ip], []) - content { - source_ip { - values = condition.value["source_ips"] + content { + values = source_ip.value.values + } } } } - tags = merge( - var.tags, - var.http_tcp_listener_rules_tags, - lookup(var.http_tcp_listener_rules[count.index], "tags", {}), - ) + tags = merge(local.tags, try(each.value.tags, {})) } -resource "aws_lb_listener" "frontend_http_tcp" { - count = local.create_lb ? length(var.http_tcp_listeners) : 0 - - load_balancer_arn = aws_lb.this[0].arn - - port = var.http_tcp_listeners[count.index]["port"] - protocol = var.http_tcp_listeners[count.index]["protocol"] - - dynamic "default_action" { - for_each = length(keys(var.http_tcp_listeners[count.index])) == 0 ? [] : [var.http_tcp_listeners[count.index]] - - # Defaults to forward action if action_type not specified - content { - type = lookup(default_action.value, "action_type", "forward") - target_group_arn = contains([null, "", "forward"], lookup(default_action.value, "action_type", "")) ? aws_lb_target_group.main[lookup(default_action.value, "target_group_index", count.index)].id : null - - dynamic "redirect" { - for_each = length(keys(lookup(default_action.value, "redirect", {}))) == 0 ? [] : [lookup(default_action.value, "redirect", {})] +################################################################################ +# Certificate(s) +################################################################################ - content { - path = lookup(redirect.value, "path", null) - host = lookup(redirect.value, "host", null) - port = lookup(redirect.value, "port", null) - protocol = lookup(redirect.value, "protocol", null) - query = lookup(redirect.value, "query", null) - status_code = redirect.value["status_code"] - } +locals { + # Take the list of `additional_certificate_arns` from the listener and create + # a map entry that maps each certificate to the listener key. This map of maps + # is then used to create the certificate resources. + additional_certs = merge(values({ + for listener_key, listener_values in var.listeners : listener_key => + { + # This will cause certs to be detached and reattached if certificate_arns + # towards the front of the list are updated/removed. However, we need to have + # unique keys on the resulting map and we can't have computed values (i.e. cert ARN) + # in the key so we are using the array index as part of the key. + for idx, cert_arn in lookup(listener_values, "additional_certificate_arns", []) : + "${listener_key}/${idx}" => { + listener_key = listener_key + certificate_arn = cert_arn } + } if length(lookup(listener_values, "additional_certificate_arns", [])) > 0 + })...) +} - dynamic "fixed_response" { - for_each = length(keys(lookup(default_action.value, "fixed_response", {}))) == 0 ? [] : [lookup(default_action.value, "fixed_response", {})] +resource "aws_lb_listener_certificate" "this" { + for_each = { for k, v in local.additional_certs : k => v if local.create } - content { - content_type = fixed_response.value["content_type"] - message_body = lookup(fixed_response.value, "message_body", null) - status_code = lookup(fixed_response.value, "status_code", null) - } - } + listener_arn = aws_lb_listener.this[each.value.listener_key].arn + certificate_arn = each.value.certificate_arn +} - dynamic "forward" { - for_each = length(keys(lookup(default_action.value, "forward", {}))) == 0 ? [] : [lookup(default_action.value, "forward", {})] +################################################################################ +# Target Group(s) +################################################################################ - content { - dynamic "target_group" { - for_each = forward.value["target_groups"] +resource "aws_lb_target_group" "this" { + for_each = { for k, v in var.target_groups : k => v if local.create } - content { - arn = aws_lb_target_group.main[target_group.value["target_group_index"]].id - weight = lookup(target_group.value, "weight", null) - } - } + connection_termination = try(each.value.connection_termination, null) + deregistration_delay = try(each.value.deregistration_delay, null) - dynamic "stickiness" { - for_each = length(keys(lookup(forward.value, "stickiness", {}))) == 0 ? [] : [lookup(forward.value, "stickiness", {})] + dynamic "health_check" { + for_each = try([each.value.health_check], []) - content { - enabled = lookup(stickiness.value, "enabled", false) - duration = lookup(stickiness.value, "duration", 60) - } - } - } - } + content { + enabled = try(health_check.value.enabled, null) + healthy_threshold = try(health_check.value.healthy_threshold, null) + interval = try(health_check.value.interval, null) + matcher = try(health_check.value.matcher, null) + path = try(health_check.value.path, null) + port = try(health_check.value.port, null) + protocol = try(health_check.value.protocol, null) + timeout = try(health_check.value.timeout, null) + unhealthy_threshold = try(health_check.value.unhealthy_threshold, null) } } - tags = merge( - var.tags, - var.http_tcp_listeners_tags, - lookup(var.http_tcp_listeners[count.index], "tags", {}), - ) -} + ip_address_type = try(each.value.ip_address_type, null) + lambda_multi_value_headers_enabled = try(each.value.lambda_multi_value_headers_enabled, null) + load_balancing_algorithm_type = try(each.value.load_balancing_algorithm_type, null) + load_balancing_cross_zone_enabled = try(each.value.load_balancing_cross_zone_enabled, null) + name = try(each.value.name, null) + name_prefix = try(each.value.name_prefix, null) + port = try(each.value.target_type, null) == "lambda" ? null : try(each.value.port, var.default_port) + preserve_client_ip = try(each.value.preserve_client_ip, null) + protocol = try(each.value.target_type, null) == "lambda" ? null : try(each.value.protocol, var.default_protocol) + protocol_version = try(each.value.protocol_version, null) + proxy_protocol_v2 = try(each.value.proxy_protocol_v2, null) + slow_start = try(each.value.slow_start, null) -resource "aws_lb_listener" "frontend_https" { - count = local.create_lb ? length(var.https_listeners) : 0 - - load_balancer_arn = aws_lb.this[0].arn + dynamic "stickiness" { + for_each = try([each.value.stickiness], []) - port = var.https_listeners[count.index]["port"] - protocol = lookup(var.https_listeners[count.index], "protocol", "HTTPS") - certificate_arn = var.https_listeners[count.index]["certificate_arn"] - ssl_policy = lookup(var.https_listeners[count.index], "ssl_policy", var.listener_ssl_policy_default) - alpn_policy = lookup(var.https_listeners[count.index], "alpn_policy", null) + content { + cookie_duration = try(stickiness.value.cookie_duration, null) + cookie_name = try(stickiness.value.cookie_name, null) + enabled = try(stickiness.value.enabled, true) + type = var.load_balancer_type == "network" ? "source_ip" : stickiness.value.type + } + } - dynamic "default_action" { - for_each = length(keys(var.https_listeners[count.index])) == 0 ? [] : [var.https_listeners[count.index]] + dynamic "target_failover" { + for_each = try(each.value.target_failover, []) - # Defaults to forward action if action_type not specified content { - type = lookup(default_action.value, "action_type", "forward") - target_group_arn = contains([null, "", "forward"], lookup(default_action.value, "action_type", "")) ? aws_lb_target_group.main[lookup(default_action.value, "target_group_index", count.index)].id : null + on_deregistration = target_failover.value.on_deregistration + on_unhealthy = target_failover.value.on_unhealthy + } + } - dynamic "redirect" { - for_each = length(keys(lookup(default_action.value, "redirect", {}))) == 0 ? [] : [lookup(default_action.value, "redirect", {})] + target_type = try(each.value.target_type, null) + vpc_id = try(each.value.vpc_id, var.vpc_id) - content { - path = lookup(redirect.value, "path", null) - host = lookup(redirect.value, "host", null) - port = lookup(redirect.value, "port", null) - protocol = lookup(redirect.value, "protocol", null) - query = lookup(redirect.value, "query", null) - status_code = redirect.value["status_code"] - } - } + tags = merge(local.tags, try(each.value.tags, {})) - dynamic "fixed_response" { - for_each = length(keys(lookup(default_action.value, "fixed_response", {}))) == 0 ? [] : [lookup(default_action.value, "fixed_response", {})] + lifecycle { + create_before_destroy = true + } +} - content { - content_type = fixed_response.value["content_type"] - message_body = lookup(fixed_response.value, "message_body", null) - status_code = lookup(fixed_response.value, "status_code", null) - } - } +################################################################################ +# Target Group Attachment +################################################################################ - # Authentication actions only available with HTTPS listeners - dynamic "authenticate_cognito" { - for_each = length(keys(lookup(default_action.value, "authenticate_cognito", {}))) == 0 ? [] : [lookup(default_action.value, "authenticate_cognito", {})] +resource "aws_lb_target_group_attachment" "this" { + for_each = { for k, v in var.target_groups : k => v if local.create && lookup(v, "create_attachment", true) } - content { - # Max 10 extra params - authentication_request_extra_params = lookup(authenticate_cognito.value, "authentication_request_extra_params", null) - on_unauthenticated_request = lookup(authenticate_cognito.value, "on_authenticated_request", null) - scope = lookup(authenticate_cognito.value, "scope", null) - session_cookie_name = lookup(authenticate_cognito.value, "session_cookie_name", null) - session_timeout = lookup(authenticate_cognito.value, "session_timeout", null) - user_pool_arn = authenticate_cognito.value["user_pool_arn"] - user_pool_client_id = authenticate_cognito.value["user_pool_client_id"] - user_pool_domain = authenticate_cognito.value["user_pool_domain"] - } - } + target_group_arn = aws_lb_target_group.this[each.key].arn + target_id = each.value.target_id + port = try(each.value.target_type, null) == "lambda" ? null : try(each.value.port, var.default_port) + availability_zone = try(each.value.availability_zone, null) - dynamic "authenticate_oidc" { - for_each = length(keys(lookup(default_action.value, "authenticate_oidc", {}))) == 0 ? [] : [lookup(default_action.value, "authenticate_oidc", {})] + depends_on = [aws_lambda_permission.this] +} - content { - # Max 10 extra params - authentication_request_extra_params = lookup(authenticate_oidc.value, "authentication_request_extra_params", null) - authorization_endpoint = authenticate_oidc.value["authorization_endpoint"] - client_id = authenticate_oidc.value["client_id"] - client_secret = authenticate_oidc.value["client_secret"] - issuer = authenticate_oidc.value["issuer"] - on_unauthenticated_request = lookup(authenticate_oidc.value, "on_unauthenticated_request", null) - scope = lookup(authenticate_oidc.value, "scope", null) - session_cookie_name = lookup(authenticate_oidc.value, "session_cookie_name", null) - session_timeout = lookup(authenticate_oidc.value, "session_timeout", null) - token_endpoint = authenticate_oidc.value["token_endpoint"] - user_info_endpoint = authenticate_oidc.value["user_info_endpoint"] - } - } - } - } +################################################################################ +# Lambda Permission +################################################################################ - dynamic "default_action" { - for_each = contains(["authenticate-oidc", "authenticate-cognito"], lookup(var.https_listeners[count.index], "action_type", {})) ? [var.https_listeners[count.index]] : [] - content { - type = "forward" - target_group_arn = aws_lb_target_group.main[lookup(default_action.value, "target_group_index", count.index)].id - } +# Filter out the attachments for lambda functions. The ALB target group needs +# permission to forward a request on to # the specified lambda function. +# This filtered list is used to create those permission resources. # To get the +# lambda_function_name, the 6th index is taken from the function ARN format below +# arn:aws:lambda:::function:my-function-name: +locals { + lambda_target_groups = { + for k, v in var.target_groups : + (k) => merge(v, { lambda_function_name = split(":", v.target_id)[6] }) + if try(v.attach_lambda_permission, false) } - - tags = merge( - var.tags, - var.https_listeners_tags, - lookup(var.https_listeners[count.index], "tags", {}), - ) } -resource "aws_lb_listener_certificate" "https_listener" { - count = local.create_lb ? length(var.extra_ssl_certs) : 0 +resource "aws_lambda_permission" "this" { + for_each = { for k, v in local.lambda_target_groups : k => v if local.create } + + function_name = each.value.lambda_function_name + qualifier = try(each.value.lambda_qualifier, null) - listener_arn = aws_lb_listener.frontend_https[var.extra_ssl_certs[count.index]["https_listener_index"]].arn - certificate_arn = var.extra_ssl_certs[count.index]["certificate_arn"] + statement_id = try(each.value.lambda_statement_id, "AllowExecutionFromLb") + action = try(each.value.lambda_action, "lambda:InvokeFunction") + principal = try(each.value.lambda_principal, "elasticloadbalancing.${data.aws_partition.current.dns_suffix}") + source_arn = aws_lb_target_group.this[each.key].arn + source_account = try(each.value.lambda_source_account, null) + event_source_token = try(each.value.lambda_event_source_token, null) } ################################################################################ @@ -800,7 +543,7 @@ resource "aws_lb_listener_certificate" "https_listener" { ################################################################################ locals { - create_security_group = local.create_lb && var.create_security_group && var.load_balancer_type == "application" + create_security_group = local.create && var.create_security_group security_group_name = try(coalesce(var.security_group_name, var.name, var.name_prefix), "") } @@ -809,41 +552,79 @@ resource "aws_security_group" "this" { name = var.security_group_use_name_prefix ? null : local.security_group_name name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null - description = var.security_group_description + description = coalesce(var.security_group_description, "Security group for ${local.security_group_name} ${var.load_balancer_type} load balancer") vpc_id = var.vpc_id - tags = merge( - var.tags, - var.security_group_tags, - { "Name" = local.security_group_name }, - ) + tags = merge(local.tags, var.security_group_tags) lifecycle { create_before_destroy = true } } -resource "aws_security_group_rule" "this" { - for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } +resource "aws_vpc_security_group_egress_rule" "this" { + for_each = { for k, v in var.security_group_egress_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + ip_protocol = try(each.value.ip_protocol, "tcp") + + # Optional + cidr_ipv4 = lookup(each.value, "cidr_ipv4", null) + cidr_ipv6 = lookup(each.value, "cidr_ipv6", null) + description = try(each.value.description, null) + from_port = try(each.value.from_port, null) + prefix_list_id = lookup(each.value, "prefix_list_id", null) + referenced_security_group_id = lookup(each.value, "referenced_security_group_id", null) + to_port = try(each.value.to_port, null) + + tags = merge(local.tags, var.security_group_tags, try(each.value.tags, {})) +} + +resource "aws_vpc_security_group_ingress_rule" "this" { + for_each = { for k, v in var.security_group_ingress_rules : k => v if local.create_security_group } # Required security_group_id = aws_security_group.this[0].id - protocol = each.value.protocol - from_port = each.value.from_port - to_port = each.value.to_port - type = each.value.type + ip_protocol = try(each.value.ip_protocol, "tcp") # Optional - description = lookup(each.value, "description", null) - cidr_blocks = lookup(each.value, "cidr_blocks", null) - ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) - prefix_list_ids = lookup(each.value, "prefix_list_ids", null) - self = lookup(each.value, "self", null) - source_security_group_id = lookup(each.value, "source_security_group_id", null) + cidr_ipv4 = lookup(each.value, "cidr_ipv4", null) + cidr_ipv6 = lookup(each.value, "cidr_ipv6", null) + description = try(each.value.description, null) + from_port = try(each.value.from_port, null) + prefix_list_id = lookup(each.value, "prefix_list_id", null) + referenced_security_group_id = lookup(each.value, "referenced_security_group_id", null) + to_port = try(each.value.to_port, null) + + tags = merge(local.tags, var.security_group_tags, try(each.value.tags, {})) +} + +################################################################################ +# Route53 Record(s) +################################################################################ + +resource "aws_route53_record" "this" { + for_each = { for k, v in var.route53_records : k => v if var.create } + + zone_id = each.value.zone_id + name = try(each.value.name, each.key) + type = each.value.type + + alias { + name = aws_lb.this[0].dns_name + zone_id = aws_lb.this[0].zone_id + evaluate_target_health = true + } } +################################################################################ +# WAF +################################################################################ + resource "aws_wafv2_web_acl_association" "this" { - count = var.web_acl_arn != null ? 1 : 0 + count = var.associate_web_acl ? 1 : 0 + resource_arn = aws_lb.this[0].arn web_acl_arn = var.web_acl_arn } diff --git a/outputs.tf b/outputs.tf index 816edc6a..b3044268 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,66 +1,53 @@ -output "lb_id" { - description = "The ID and ARN of the load balancer we created" - value = try(aws_lb.this[0].id, "") -} +################################################################################ +# Load Balancer +################################################################################ -output "lb_arn" { +output "id" { description = "The ID and ARN of the load balancer we created" - value = try(aws_lb.this[0].arn, "") + value = try(aws_lb.this[0].id, null) } -output "lb_dns_name" { - description = "The DNS name of the load balancer" - value = try(aws_lb.this[0].dns_name, "") +output "arn" { + description = "The ID and ARN of the load balancer we created" + value = try(aws_lb.this[0].arn, null) } -output "lb_arn_suffix" { +output "arn_suffix" { description = "ARN suffix of our load balancer - can be used with CloudWatch" - value = try(aws_lb.this[0].arn_suffix, "") + value = try(aws_lb.this[0].arn_suffix, null) } -output "lb_zone_id" { - description = "The zone_id of the load balancer to assist with creating DNS records" - value = try(aws_lb.this[0].zone_id, "") -} - -output "http_tcp_listener_arns" { - description = "The ARN of the TCP and HTTP load balancer listeners created" - value = aws_lb_listener.frontend_http_tcp[*].arn -} - -output "http_tcp_listener_ids" { - description = "The IDs of the TCP and HTTP load balancer listeners created" - value = aws_lb_listener.frontend_http_tcp[*].id +output "dns_name" { + description = "The DNS name of the load balancer" + value = try(aws_lb.this[0].dns_name, null) } -output "https_listener_arns" { - description = "The ARNs of the HTTPS load balancer listeners created" - value = aws_lb_listener.frontend_https[*].arn +output "zone_id" { + description = "The zone_id of the load balancer to assist with creating DNS records" + value = try(aws_lb.this[0].zone_id, null) } -output "https_listener_ids" { - description = "The IDs of the load balancer listeners created" - value = aws_lb_listener.frontend_https[*].id -} +################################################################################ +# Listener(s) +################################################################################ -output "target_group_arns" { - description = "ARNs of the target groups. Useful for passing to your Auto Scaling group" - value = aws_lb_target_group.main[*].arn +output "listeners" { + description = "Map of listeners created and their attributes" + value = aws_lb_listener.this } -output "target_group_arn_suffixes" { - description = "ARN suffixes of our target groups - can be used with CloudWatch" - value = aws_lb_target_group.main[*].arn_suffix +output "listener_rules" { + description = "Map of listeners rules created and their attributes" + value = aws_lb_listener_rule.this } -output "target_group_names" { - description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group" - value = aws_lb_target_group.main[*].name -} +################################################################################ +# Target Group(s) +################################################################################ -output "target_group_attachments" { - description = "ARNs of the target group attachment IDs" - value = { for k, v in aws_lb_target_group_attachment.this : k => v.id } +output "target_groups" { + description = "Map of target groups created and their attributes" + value = aws_lb_target_group.this } ################################################################################ @@ -76,3 +63,12 @@ output "security_group_id" { description = "ID of the security group" value = try(aws_security_group.this[0].id, null) } + +################################################################################ +# Route53 Record(s) +################################################################################ + +output "route53_records" { + description = "The Route53 records created and attached to the load balancer" + value = aws_route53_record.this +} diff --git a/variables.tf b/variables.tf index 1849ab7f..d72ca30b 100644 --- a/variables.tf +++ b/variables.tf @@ -1,241 +1,181 @@ -variable "create_lb" { - description = "Controls if the Load Balancer should be created" +variable "create" { + description = "Controls if resources should be created (affects nearly all resources)" type = bool default = true } -variable "drop_invalid_header_fields" { - description = "Indicates whether invalid header fields are dropped in application load balancers. Defaults to false." - type = bool - default = false +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} } -variable "preserve_host_header" { - description = "Indicates whether Host header should be preserve and forward to targets without any change. Defaults to false." - type = bool - default = false -} +################################################################################ +# Load Balancer +################################################################################ -variable "enable_deletion_protection" { - description = "If true, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to false." - type = bool - default = false +variable "access_logs" { + description = "Map containing access logging configuration for load balancer" + type = map(string) + default = {} } -variable "enable_http2" { - description = "Indicates whether HTTP/2 is enabled in application load balancers." - type = bool - default = true +variable "customer_owned_ipv4_pool" { + description = "The ID of the customer owned ipv4 pool to use for this load balancer" + type = string + default = null } -variable "enable_cross_zone_load_balancing" { - description = "Indicates whether cross zone load balancing should be enabled in application load balancers." - type = bool - default = false +variable "desync_mitigation_mode" { + description = "Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest`" + type = string + default = null } -variable "enable_tls_version_and_cipher_suite_headers" { - description = "Indicates whether the two headers (x-amzn-tls-version and x-amzn-tls-cipher-suite), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target." +variable "drop_invalid_header_fields" { + description = "Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (`true`) or routed to targets (`false`). The default is `true`. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application`" type = bool - default = false + default = true } -variable "enable_xff_client_port" { - description = "Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in application load balancers." +variable "enable_cross_zone_load_balancing" { + description = "If `true`, cross-zone load balancing of the load balancer will be enabled. For application load balancer this feature is always enabled (`true`) and cannot be disabled. Defaults to `true`" type = bool default = true } -variable "extra_ssl_certs" { - description = "A list of maps describing any extra SSL certificates to apply to the HTTPS listeners. Required key/values: certificate_arn, https_listener_index (the index of the listener within https_listeners which the cert applies toward)." - type = list(map(string)) - default = [] +variable "enable_deletion_protection" { + description = "If `true`, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to `true`" + type = bool + default = true } -variable "https_listeners" { - description = "A list of maps describing the HTTPS listeners for this ALB. Required key/values: port, certificate_arn. Optional key/values: ssl_policy (defaults to ELBSecurityPolicy-2016-08), target_group_index (defaults to https_listeners[count.index])" - type = any - default = [] +variable "enable_http2" { + description = "Indicates whether HTTP/2 is enabled in application load balancers. Defaults to `true`" + type = bool + default = null } -variable "http_tcp_listeners" { - description = "A list of maps describing the HTTP listeners or TCP ports for this ALB. Required key/values: port, protocol. Optional key/values: target_group_index (defaults to http_tcp_listeners[count.index])" - type = any - default = [] +variable "enable_tls_version_and_cipher_suite_headers" { + description = "Indicates whether the two headers (`x-amzn-tls-version` and `x-amzn-tls-cipher-suite`), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. Only valid for Load Balancers of type `application`. Defaults to `false`" + type = bool + default = null } -variable "https_listener_rules" { - description = "A list of maps describing the Listener Rules for this ALB. Required key/values: actions, conditions. Optional key/values: priority, https_listener_index (default to https_listeners[count.index])" - type = any - default = [] +variable "enable_waf_fail_open" { + description = "Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. Defaults to `false`" + type = bool + default = null } -variable "http_tcp_listener_rules" { - description = "A list of maps describing the Listener Rules for this ALB. Required key/values: actions, conditions. Optional key/values: priority, http_tcp_listener_index (default to http_tcp_listeners[count.index])" - type = any - default = [] +variable "enable_xff_client_port" { + description = "Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in `application` load balancers. Defaults to `false`" + type = bool + default = null } variable "idle_timeout" { - description = "The time in seconds that the connection is allowed to be idle." + description = "The time in seconds that the connection is allowed to be idle. Only valid for Load Balancers of type `application`. Default: `60`" type = number - default = 60 -} - -variable "ip_address_type" { - description = "The type of IP addresses used by the subnets for your load balancer. The possible values are ipv4 and dualstack." - type = string - default = "ipv4" -} - -variable "listener_ssl_policy_default" { - description = "The security policy if using HTTPS externally on the load balancer. [See](https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html)." - type = string - default = "ELBSecurityPolicy-2016-08" + default = null } variable "internal" { - description = "Boolean determining if the load balancer is internal or externally facing." + description = "If true, the LB will be internal. Defaults to `false`" type = bool - default = false + default = null } -variable "load_balancer_create_timeout" { - description = "Timeout value when creating the ALB." +variable "ip_address_type" { + description = "The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack`" type = string - default = "10m" + default = null } -variable "load_balancer_delete_timeout" { - description = "Timeout value when deleting the ALB." +variable "load_balancer_type" { + description = "The type of load balancer to create. Possible values are `application`, `gateway`, or `network`. The default value is `application`" type = string - default = "10m" + default = "application" } variable "name" { - description = "The resource name and Name tag of the load balancer." + description = "The name of the LB. This name must be unique within your AWS account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen" type = string default = null } variable "name_prefix" { - description = "The resource name prefix and Name tag of the load balancer. Cannot be longer than 6 characters" + description = "Creates a unique name beginning with the specified prefix. Conflicts with `name`" type = string default = null } -variable "load_balancer_type" { - description = "The type of load balancer to create. Possible values are application or network." - type = string - default = "application" -} - -variable "load_balancer_update_timeout" { - description = "Timeout value when updating the ALB." - type = string - default = "10m" -} - -variable "access_logs" { - description = "Map containing access logging configuration for load balancer." - type = map(string) - default = {} +variable "preserve_host_header" { + description = "Indicates whether the Application Load Balancer should preserve the Host header in the HTTP request and send it to the target without any change. Defaults to `false`" + type = bool + default = null } -variable "subnets" { - description = "A list of subnets to associate with the load balancer. e.g. ['subnet-1a2b3c4d','subnet-1a2b3c4e','subnet-1a2b3c4f']" +variable "security_groups" { + description = "A list of security group IDs to assign to the LB" type = list(string) - default = null + default = [] } variable "subnet_mapping" { - description = "A list of subnet mapping blocks describing subnets to attach to network load balancer" + description = "A list of subnet mapping blocks describing subnets to attach to load balancer" type = list(map(string)) default = [] } -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} +variable "subnets" { + description = "A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type `network`. Changing this value for load balancers of type `network` will force a recreation of the resource" + type = list(string) + default = [] } -variable "lb_tags" { - description = "A map of tags to add to load balancer" - type = map(string) - default = {} +variable "xff_header_processing_mode" { + description = "Determines how the load balancer modifies the X-Forwarded-For header in the HTTP request before sending the request to the target. The possible values are `append`, `preserve`, and `remove`. Only valid for Load Balancers of type `application`. The default is `append`" + type = string + default = null } -variable "target_group_tags" { - description = "A map of tags to add to all target groups" +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the load balancer" type = map(string) default = {} } -variable "https_listener_rules_tags" { - description = "A map of tags to add to all https listener rules" - type = map(string) - default = {} -} +################################################################################ +# Listener(s) +################################################################################ -variable "http_tcp_listener_rules_tags" { - description = "A map of tags to add to all http listener rules" - type = map(string) - default = {} +variable "default_port" { + description = "Default port used across the listener and target group" + type = number + default = 80 } -variable "https_listeners_tags" { - description = "A map of tags to add to all https listeners" - type = map(string) - default = {} +variable "default_protocol" { + description = "Default protocol used across the listener and target group" + type = string + default = "HTTP" } -variable "http_tcp_listeners_tags" { - description = "A map of tags to add to all http listeners" - type = map(string) +variable "listeners" { + description = "Map of listener configurations to create" + type = any default = {} } -variable "security_groups" { - description = "The security groups to attach to the load balancer. e.g. [\"sg-edcd9784\",\"sg-edcd9785\"]" - type = list(string) - default = [] -} +################################################################################ +# Target Group +################################################################################ variable "target_groups" { - description = "A list of maps containing key/value pairs that define the target groups to be created. Order of these maps is important and the index of these are to be referenced in listener definitions. Required key/values: name, backend_protocol, backend_port" + description = "Map of target group configurations to create" type = any - default = [] -} - -variable "vpc_id" { - description = "VPC id where the load balancer and other resources will be deployed." - type = string - default = null -} - -variable "enable_waf_fail_open" { - description = "Indicates whether to route requests to targets if lb fails to forward the request to AWS WAF" - type = bool - default = false -} - -variable "desync_mitigation_mode" { - description = "Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync." - type = string - default = "defensive" -} - -variable "xff_header_processing_mode" { - description = "Determines how the load balancer modifies the X-Forwarded-For header in the HTTP request before sending the request to the target." - type = string - default = "append" -} - -variable "putin_khuylo" { - description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" - type = bool - default = true + default = {} } ################################################################################ @@ -266,8 +206,20 @@ variable "security_group_description" { default = null } -variable "security_group_rules" { - description = "Security group rules to add to the security group created" +variable "vpc_id" { + description = "Identifier of the VPC where the security group will be created" + type = string + default = null +} + +variable "security_group_ingress_rules" { + description = "Security group ingress rules to add to the security group created" + type = any + default = {} +} + +variable "security_group_egress_rules" { + description = "Security group egress rules to add to the security group created" type = any default = {} } @@ -278,8 +230,34 @@ variable "security_group_tags" { default = {} } +################################################################################ +# Route53 Record(s) +################################################################################ + +variable "route53_records" { + description = "Map of Route53 records to create. Each record map should contain `zone_id`, `name`, and `type`" + type = any + default = {} +} + +################################################################################ +# WAF +################################################################################ + +variable "associate_web_acl" { + description = "Indicates whether a Web Application Firewall (WAF) ACL should be associated with the load balancer" + type = bool + default = false +} + variable "web_acl_arn" { - description = "WAF ARN to associate this LB with." + description = "Web Application Firewall (WAF) ARN of the resource to associate with the load balancer" type = string default = null } + +variable "putin_khuylo" { + description = "Do you agree that Putin doesn't respect Ukrainian sovereignty and territorial integrity? More info: https://en.wikipedia.org/wiki/Putin_khuylo!" + type = bool + default = true +} diff --git a/versions.tf b/versions.tf index 33eb30cc..4e31bbf0 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.59" + version = ">= 5.13" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 1c78bb65..da23c5a8 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -3,51 +3,44 @@ module "wrapper" { for_each = var.items - create_lb = try(each.value.create_lb, var.defaults.create_lb, true) - drop_invalid_header_fields = try(each.value.drop_invalid_header_fields, var.defaults.drop_invalid_header_fields, false) - preserve_host_header = try(each.value.preserve_host_header, var.defaults.preserve_host_header, false) - enable_deletion_protection = try(each.value.enable_deletion_protection, var.defaults.enable_deletion_protection, false) - enable_http2 = try(each.value.enable_http2, var.defaults.enable_http2, true) - enable_cross_zone_load_balancing = try(each.value.enable_cross_zone_load_balancing, var.defaults.enable_cross_zone_load_balancing, false) - enable_tls_version_and_cipher_suite_headers = try(each.value.enable_tls_version_and_cipher_suite_headers, var.defaults.enable_tls_version_and_cipher_suite_headers, false) - enable_xff_client_port = try(each.value.enable_xff_client_port, var.defaults.enable_xff_client_port, true) - extra_ssl_certs = try(each.value.extra_ssl_certs, var.defaults.extra_ssl_certs, []) - https_listeners = try(each.value.https_listeners, var.defaults.https_listeners, []) - http_tcp_listeners = try(each.value.http_tcp_listeners, var.defaults.http_tcp_listeners, []) - https_listener_rules = try(each.value.https_listener_rules, var.defaults.https_listener_rules, []) - http_tcp_listener_rules = try(each.value.http_tcp_listener_rules, var.defaults.http_tcp_listener_rules, []) - idle_timeout = try(each.value.idle_timeout, var.defaults.idle_timeout, 60) - ip_address_type = try(each.value.ip_address_type, var.defaults.ip_address_type, "ipv4") - listener_ssl_policy_default = try(each.value.listener_ssl_policy_default, var.defaults.listener_ssl_policy_default, "ELBSecurityPolicy-2016-08") - internal = try(each.value.internal, var.defaults.internal, false) - load_balancer_create_timeout = try(each.value.load_balancer_create_timeout, var.defaults.load_balancer_create_timeout, "10m") - load_balancer_delete_timeout = try(each.value.load_balancer_delete_timeout, var.defaults.load_balancer_delete_timeout, "10m") + access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) + associate_web_acl = try(each.value.associate_web_acl, var.defaults.associate_web_acl, false) + create = try(each.value.create, var.defaults.create, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + customer_owned_ipv4_pool = try(each.value.customer_owned_ipv4_pool, var.defaults.customer_owned_ipv4_pool, null) + default_port = try(each.value.default_port, var.defaults.default_port, 80) + default_protocol = try(each.value.default_protocol, var.defaults.default_protocol, "HTTP") + desync_mitigation_mode = try(each.value.desync_mitigation_mode, var.defaults.desync_mitigation_mode, null) + drop_invalid_header_fields = try(each.value.drop_invalid_header_fields, var.defaults.drop_invalid_header_fields, true) + enable_cross_zone_load_balancing = try(each.value.enable_cross_zone_load_balancing, var.defaults.enable_cross_zone_load_balancing, true) + enable_deletion_protection = try(each.value.enable_deletion_protection, var.defaults.enable_deletion_protection, true) + enable_http2 = try(each.value.enable_http2, var.defaults.enable_http2, null) + enable_tls_version_and_cipher_suite_headers = try(each.value.enable_tls_version_and_cipher_suite_headers, var.defaults.enable_tls_version_and_cipher_suite_headers, null) + enable_waf_fail_open = try(each.value.enable_waf_fail_open, var.defaults.enable_waf_fail_open, null) + enable_xff_client_port = try(each.value.enable_xff_client_port, var.defaults.enable_xff_client_port, null) + idle_timeout = try(each.value.idle_timeout, var.defaults.idle_timeout, null) + internal = try(each.value.internal, var.defaults.internal, null) + ip_address_type = try(each.value.ip_address_type, var.defaults.ip_address_type, null) + listeners = try(each.value.listeners, var.defaults.listeners, {}) + load_balancer_type = try(each.value.load_balancer_type, var.defaults.load_balancer_type, "application") name = try(each.value.name, var.defaults.name, null) name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) - load_balancer_type = try(each.value.load_balancer_type, var.defaults.load_balancer_type, "application") - load_balancer_update_timeout = try(each.value.load_balancer_update_timeout, var.defaults.load_balancer_update_timeout, "10m") - access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) - subnets = try(each.value.subnets, var.defaults.subnets, null) - subnet_mapping = try(each.value.subnet_mapping, var.defaults.subnet_mapping, []) - tags = try(each.value.tags, var.defaults.tags, {}) - lb_tags = try(each.value.lb_tags, var.defaults.lb_tags, {}) - target_group_tags = try(each.value.target_group_tags, var.defaults.target_group_tags, {}) - https_listener_rules_tags = try(each.value.https_listener_rules_tags, var.defaults.https_listener_rules_tags, {}) - http_tcp_listener_rules_tags = try(each.value.http_tcp_listener_rules_tags, var.defaults.http_tcp_listener_rules_tags, {}) - https_listeners_tags = try(each.value.https_listeners_tags, var.defaults.https_listeners_tags, {}) - http_tcp_listeners_tags = try(each.value.http_tcp_listeners_tags, var.defaults.http_tcp_listeners_tags, {}) - security_groups = try(each.value.security_groups, var.defaults.security_groups, []) - target_groups = try(each.value.target_groups, var.defaults.target_groups, []) - vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) - enable_waf_fail_open = try(each.value.enable_waf_fail_open, var.defaults.enable_waf_fail_open, false) - desync_mitigation_mode = try(each.value.desync_mitigation_mode, var.defaults.desync_mitigation_mode, "defensive") - xff_header_processing_mode = try(each.value.xff_header_processing_mode, var.defaults.xff_header_processing_mode, "append") + preserve_host_header = try(each.value.preserve_host_header, var.defaults.preserve_host_header, null) putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) - create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) - security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) - security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + route53_records = try(each.value.route53_records, var.defaults.route53_records, {}) security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) - security_group_rules = try(each.value.security_group_rules, var.defaults.security_group_rules, {}) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, {}) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + security_groups = try(each.value.security_groups, var.defaults.security_groups, []) + subnet_mapping = try(each.value.subnet_mapping, var.defaults.subnet_mapping, []) + subnets = try(each.value.subnets, var.defaults.subnets, []) + tags = try(each.value.tags, var.defaults.tags, {}) + target_groups = try(each.value.target_groups, var.defaults.target_groups, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) web_acl_arn = try(each.value.web_acl_arn, var.defaults.web_acl_arn, null) + xff_header_processing_mode = try(each.value.xff_header_processing_mode, var.defaults.xff_header_processing_mode, null) } From 222bae0825c4fabf6f91f4652511fa5953bc9c73 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 27 Oct 2023 15:52:51 +0000 Subject: [PATCH 06/28] chore(release): version 9.0.0 [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [9.0.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.7.0...v9.0.0) (2023-10-27) ### âš  BREAKING CHANGES * Refactor module to use maps instead of lists (#305) ### Features * Refactor module to use maps instead of lists ([#305](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/305)) ([39d5627](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/39d5627d51251c0ccbe9d404cf1ea9ccb4f5b3b7)) --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec0cee4..3d80d5b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. +## [9.0.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.7.0...v9.0.0) (2023-10-27) + + +### âš  BREAKING CHANGES + +* Refactor module to use maps instead of lists (#305) + +### Features + +* Refactor module to use maps instead of lists ([#305](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/305)) ([39d5627](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/39d5627d51251c0ccbe9d404cf1ea9ccb4f5b3b7)) + ## [8.7.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.6.1...v8.7.0) (2023-06-16) From f3eca50775ec89273e3e5a349df80f7169a1407e Mon Sep 17 00:00:00 2001 From: ido123ziv <52109442+ido123ziv@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:54:16 -0400 Subject: [PATCH 07/28] docs: Adding patterns for common usage (#313) Co-authored-by: ido.ziv Co-authored-by: Bryant Biggs --- README.md | 2 + docs/patterns.md | 200 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 201 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e83fa49..a40d6c88 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,8 @@ module "alb" { - [Complete Application Load Balancer](https://github.com/terraform-aws-modules/terraform-aws-alb/tree/master/examples/complete-alb) - [Complete Network Load Balancer](https://github.com/terraform-aws-modules/terraform-aws-alb/tree/master/examples/complete-nlb) +See [patterns.md](https://github.com/terraform-aws-modules/terraform-aws-alb/blob/master/docs/patterns.md) for additional configuration snippets for common usage patterns. + ## Requirements diff --git a/docs/patterns.md b/docs/patterns.md index 2a205004..36820c74 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -2,4 +2,202 @@ Various usage patterns are prescribed below. - +## Listeners + +### Redirect HTTP to HTTPS + +The configuration snippet below creates a listener that automatically redirects HTTP/80 requests to HTTPS/443. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + ex-http-https-redirect = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } + } +} +``` + +### Fixed Response + +The configuration snippet below creates a listener with a fixed response of `200`. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + ex-fixed-response = { + port = 80 + protocol = "HTTP" + fixed_response = { + content_type = "text/plain" + message_body = "Fixed message" + status_code = "200" + } + } + } +} +``` + +## Target Groups + +### Instance Target Group + +The configuration snippet below creates a target group that targets instances. An example listener is shown to demonstrate how a listener or listener rule can forward traffic to this target group using the target group key of `ex-instance` (this name can be any name that users wish to use). + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + ex-https = { + port = 443 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06" + certificate_arn = module.acm.acm_certificate_arn + additional_certificate_arns = [module.wildcard_cert.acm_certificate_arn] + + forward = { + # The value of the `target_group_key` is the key used in the `target_groups` map below + target_group_key = "ex-instance" + } + } + } + + target_groups = { + # This key name is used by the listener/listener rules to know which target to forward traffic to + ex-instance = { + name_prefix = "h1" + backend_protocol = "HTTP" + backend_port = 80 + target_type = "instance" + deregistration_delay = 10 + load_balancing_cross_zone_enabled = true + } + } +} +``` + +### Lambda Target Group + +The configuration snippet below creates two Lambda based target groups. It also demonstrates how users attach permissions to the Lambda function to allow ALB to invoke the function, or they can let ALB attach the necessary permissions to invoke the Lambda function. The listeners specified will split traffic between the two functions, with 60% of traffic going to the Lambda function with invocation permissions, and 40% of traffic going to the Lambda function without invocation permissions. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + ex-http-weighted-target = { + port = 80 + protocol = "HTTP" + weighted_forward = { + target_groups = [ + { + target_group_key = "ex-lambda-with-trigger" + weight = 60 + }, + { + target_group_key = "ex-lambda-without-trigger" + weight = 40 + } + ] + } + } + } + + target_groups = { + ex-lambda-with-trigger = { + name_prefix = "l1-" + target_type = "lambda" + lambda_multi_value_headers_enabled = true + target_id = module.lambda_with_allowed_triggers.lambda_function_arn + } + + ex-lambda-without-trigger = { + name_prefix = "l2-" + target_type = "lambda" + target_id = module.lambda_without_allowed_triggers.lambda_function_arn + attach_lambda_permission = true + } + } +} + +module "lambda_with_allowed_triggers" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 6.0" + + # Truncated for brevity ... + + allowed_triggers = { + AllowExecutionFromELB = { + service = "elasticloadbalancing" + source_arn = module.alb.target_groups["ex-lambda-with-trigger"].arn + } + } +} + +module "lambda_without_allowed_triggers" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 6.0" + + # Truncated for brevity ... + + # Allowed triggers will be managed by ALB module + allowed_triggers = {} +} +``` + +### Target Group without Attachment + +The configuration snippet below creates a target group but it does not attach it to anything at this time. This is commonly used with Amazon ECS where ECS will attach the IPs of the ECS Tasks to the target group. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + target_groups = { + ex-ip = { + backend_protocol = "HTTP" + backend_port = 80 + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } +} +``` From c79324eee6e8930bb2c5f046e4494cd967e7c6a8 Mon Sep 17 00:00:00 2001 From: Melissa Greenbaum <69476188+magreenbaum@users.noreply.github.com> Date: Mon, 30 Oct 2023 10:49:35 -0400 Subject: [PATCH 08/28] feat: Add support for disabling connection termination for unhealthy targets and AZ DNS affinity (#315) Co-authored-by: magreenbaum --- README.md | 5 +++-- examples/complete-alb/README.md | 4 ++-- examples/complete-alb/versions.tf | 2 +- examples/complete-nlb/README.md | 4 ++-- examples/complete-nlb/main.tf | 8 ++++++-- examples/complete-nlb/versions.tf | 2 +- main.tf | 8 ++++++++ variables.tf | 6 ++++++ versions.tf | 2 +- wrappers/main.tf | 1 + 10 files changed, 31 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a40d6c88..5f204e5a 100644 --- a/README.md +++ b/README.md @@ -348,13 +348,13 @@ See [patterns.md](https://github.com/terraform-aws-modules/terraform-aws-alb/blo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.13 | +| [aws](#requirement\_aws) | >= 5.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.13 | +| [aws](#provider\_aws) | >= 5.23 | ## Modules @@ -390,6 +390,7 @@ No modules. | [default\_port](#input\_default\_port) | Default port used across the listener and target group | `number` | `80` | no | | [default\_protocol](#input\_default\_protocol) | Default protocol used across the listener and target group | `string` | `"HTTP"` | no | | [desync\_mitigation\_mode](#input\_desync\_mitigation\_mode) | Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest` | `string` | `null` | no | +| [dns\_record\_client\_routing\_policy](#input\_dns\_record\_client\_routing\_policy) | Indicates how traffic is distributed among the load balancer Availability Zones. Possible values are any\_availability\_zone (default), availability\_zone\_affinity, or partial\_availability\_zone\_affinity. Only valid for network type load balancers. | `string` | `null` | no | | [drop\_invalid\_header\_fields](#input\_drop\_invalid\_header\_fields) | Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (`true`) or routed to targets (`false`). The default is `true`. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application` | `bool` | `true` | no | | [enable\_cross\_zone\_load\_balancing](#input\_enable\_cross\_zone\_load\_balancing) | If `true`, cross-zone load balancing of the load balancer will be enabled. For application load balancer this feature is always enabled (`true`) and cannot be disabled. Defaults to `true` | `bool` | `true` | no | | [enable\_deletion\_protection](#input\_enable\_deletion\_protection) | If `true`, deletion of the load balancer will be disabled via the AWS API. This will prevent Terraform from deleting the load balancer. Defaults to `true` | `bool` | `true` | no | diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index 08e30b4d..1e93422d 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.13 | +| [aws](#requirement\_aws) | >= 5.23 | | [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.13 | +| [aws](#provider\_aws) | >= 5.23 | | [null](#provider\_null) | >= 2.0 | ## Modules diff --git a/examples/complete-alb/versions.tf b/examples/complete-alb/versions.tf index 807bfb0d..7a23a05f 100644 --- a/examples/complete-alb/versions.tf +++ b/examples/complete-alb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.13" + version = ">= 5.23" } null = { source = "hashicorp/null" diff --git a/examples/complete-nlb/README.md b/examples/complete-nlb/README.md index 28040a9c..3c7efe31 100644 --- a/examples/complete-nlb/README.md +++ b/examples/complete-nlb/README.md @@ -20,13 +20,13 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.13 | +| [aws](#requirement\_aws) | >= 5.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.13 | +| [aws](#provider\_aws) | >= 5.23 | ## Modules diff --git a/examples/complete-nlb/main.tf b/examples/complete-nlb/main.tf index 2340bdcf..f5f3964e 100644 --- a/examples/complete-nlb/main.tf +++ b/examples/complete-nlb/main.tf @@ -27,8 +27,9 @@ module "nlb" { name = local.name - load_balancer_type = "network" - vpc_id = module.vpc.vpc_id + load_balancer_type = "network" + vpc_id = module.vpc.vpc_id + dns_record_client_routing_policy = "availability_zone_affinity" # https://github.com/hashicorp/terraform-provider-aws/issues/17281 # subnets = module.vpc.private_subnets @@ -158,6 +159,9 @@ module "nlb" { port = 84 target_type = "instance" target_id = aws_instance.this.id + target_health_state = { + enable_unhealthy_connection_termination = false + } } } diff --git a/examples/complete-nlb/versions.tf b/examples/complete-nlb/versions.tf index 4e31bbf0..a1705fa3 100644 --- a/examples/complete-nlb/versions.tf +++ b/examples/complete-nlb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.13" + version = ">= 5.23" } } } diff --git a/main.tf b/main.tf index b64a95e1..5123909a 100644 --- a/main.tf +++ b/main.tf @@ -24,6 +24,7 @@ resource "aws_lb" "this" { customer_owned_ipv4_pool = var.customer_owned_ipv4_pool desync_mitigation_mode = var.desync_mitigation_mode + dns_record_client_routing_policy = var.dns_record_client_routing_policy drop_invalid_header_fields = var.drop_invalid_header_fields enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing enable_deletion_protection = var.enable_deletion_protection @@ -482,6 +483,13 @@ resource "aws_lb_target_group" "this" { } } + dynamic "target_health_state" { + for_each = try([each.value.target_health_state], []) + content { + enable_unhealthy_connection_termination = try(target_health_state.value.enable_unhealthy_connection_termination, true) + } + } + target_type = try(each.value.target_type, null) vpc_id = try(each.value.vpc_id, var.vpc_id) diff --git a/variables.tf b/variables.tf index d72ca30b..d69c99d2 100644 --- a/variables.tf +++ b/variables.tf @@ -32,6 +32,12 @@ variable "desync_mitigation_mode" { default = null } +variable "dns_record_client_routing_policy" { + description = "Indicates how traffic is distributed among the load balancer Availability Zones. Possible values are any_availability_zone (default), availability_zone_affinity, or partial_availability_zone_affinity. Only valid for network type load balancers." + type = string + default = null +} + variable "drop_invalid_header_fields" { description = "Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (`true`) or routed to targets (`false`). The default is `true`. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application`" type = bool diff --git a/versions.tf b/versions.tf index 4e31bbf0..a1705fa3 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.13" + version = ">= 5.23" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index da23c5a8..6f902050 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -11,6 +11,7 @@ module "wrapper" { default_port = try(each.value.default_port, var.defaults.default_port, 80) default_protocol = try(each.value.default_protocol, var.defaults.default_protocol, "HTTP") desync_mitigation_mode = try(each.value.desync_mitigation_mode, var.defaults.desync_mitigation_mode, null) + dns_record_client_routing_policy = try(each.value.dns_record_client_routing_policy, var.defaults.dns_record_client_routing_policy, null) drop_invalid_header_fields = try(each.value.drop_invalid_header_fields, var.defaults.drop_invalid_header_fields, true) enable_cross_zone_load_balancing = try(each.value.enable_cross_zone_load_balancing, var.defaults.enable_cross_zone_load_balancing, true) enable_deletion_protection = try(each.value.enable_deletion_protection, var.defaults.enable_deletion_protection, true) From 3289f9ff8ea2d16a3e39c67e582f4fab236ca5b9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 30 Oct 2023 14:49:59 +0000 Subject: [PATCH 09/28] chore(release): version 9.1.0 [skip ci] ## [9.1.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.0.0...v9.1.0) (2023-10-30) ### Features * Add support for disabling connection termination for unhealthy targets and AZ DNS affinity ([#315](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/315)) ([c79324e](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/c79324eee6e8930bb2c5f046e4494cd967e7c6a8)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d80d5b7..232b6cba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.1.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.0.0...v9.1.0) (2023-10-30) + + +### Features + +* Add support for disabling connection termination for unhealthy targets and AZ DNS affinity ([#315](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/315)) ([c79324e](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/c79324eee6e8930bb2c5f046e4494cd967e7c6a8)) + ## [9.0.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v8.7.0...v9.0.0) (2023-10-27) From 72bfb355a549ea5a79ca7eb16fb55bf6a0deca9a Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 3 Nov 2023 11:12:16 -0400 Subject: [PATCH 10/28] docs: Add usage patterns for authenticated listeners from Atlantis module docs (#319) --- docs/patterns.md | 132 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 8 deletions(-) diff --git a/docs/patterns.md b/docs/patterns.md index 36820c74..3ceb0db8 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -15,7 +15,7 @@ module "alb" { # Truncated for brevity ... listeners = { - ex-http-https-redirect = { + http_https_redirect = { port = 80 protocol = "HTTP" redirect = { @@ -39,7 +39,7 @@ module "alb" { # Truncated for brevity ... listeners = { - ex-fixed-response = { + fixed_response = { port = 80 protocol = "HTTP" fixed_response = { @@ -52,6 +52,122 @@ module "alb" { } ``` +### Auth0 authenticated HTTPS Listener + +The configuration snippet below creates an HTTPS listener that utilizes [Auth0](https://www.auth0.com) to secure access. Read more in [this post](https://medium.com/@sandrinodm/securing-your-applications-with-aws-alb-built-in-authentication-and-auth0-310ad84c8595). + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + https_auth0 = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" + + authenticate_oidc = { + issuer = "https://youruser.eu.auth0.com/" + token_endpoint = "https://youruser.eu.auth0.com/oauth/token" + user_info_endpoint = "https://youruser.eu.auth0.com/userinfo" + authorization_endpoint = "https://youruser.eu.auth0.com/authorize" + authentication_request_extra_params = {} + client_id = "clientid" + client_secret = "secret123" # a data source would be good here + } + } + } +} +``` + +### Okta authenticated HTTPS Listener + +The configuration snippet below creates an HTTPS listener that utilizes [Okta](https://www.okta.com/) to secure access. Read more in [this post](https://medium.com/swlh/aws-alb-authentication-with-okta-oidc-using-terraform-902cd8289db4). + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + https_okta = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" + + authenticate_oidc = { + issuer = "https://dev-42069.okta.com/" + token_endpoint = "https://dev-42069.okta.com/oauth2/v1/token" + user_info_endpoint = "https://dev-42069.okta.com/oauth2/v1/userinfo" + authorization_endpoint = "https://dev-42069.okta.com/oauth2/v1/authorize" + authentication_request_extra_params = {} + client_id = "clientid" + client_secret = "secret123" # a data source would be good here + } + } + } +} +``` + +### Google authenticated HTTPS Listener + +The configuration snippet below creates an HTTPS listener that utilizes Google to secure access. See the [iap_client resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iap_client) in the Google provider if you want to create this configuration in Terraform. Remember to set your google consent screen to internal to only allow users from your own domain. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + https_google = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" + + authenticate_oidc = { + issuer = "https://accounts.google.com" + token_endpoint = "https://oauth2.googleapis.com/token" + user_info_endpoint = "https://openidconnect.googleapis.com/v1/userinfo" + authorization_endpoint = "https://accounts.google.com/o/oauth2/v2/auth" + authentication_request_extra_params = {} + client_id = "google_client_id" + client_secret = "google_client_secret" + } + } + } +} +``` + +### Amazon Cognito authenticated HTTPS Listener + +The configuration snippet below creates an HTTPS listener that utilizes [Amazon Cognito](https://aws.amazon.com/cognito/) to secure access. See the [iap_client resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iap_client) in the Google provider if you want to create this configuration in Terraform. Remember to set your google consent screen to internal to only allow users from your own domain. + +```hcl +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + listeners = { + https_cognito = { + port = 443 + protocol = "HTTPS" + certificate_arn = "arn:aws:acm:eu-west-1:135367859851:certificate/70e008e1-c0e1-4c7e-9670-7bb5bd4f5a84" + + authenticate_cognito = { + user_pool_arn = "arn:aws:cognito-idp:eu-west-1:1234567890:userpool/eu-west-1_aBcDeFG" + user_pool_client_id = "clientid123" + user_pool_domain = "sso.your-corp.com" + } + } + } +} +``` + ## Target Groups ### Instance Target Group @@ -65,7 +181,7 @@ module "alb" { # Truncated for brevity ... listeners = { - ex-https = { + ex_https = { port = 443 protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06" @@ -81,7 +197,7 @@ module "alb" { target_groups = { # This key name is used by the listener/listener rules to know which target to forward traffic to - ex-instance = { + ex_instance = { name_prefix = "h1" backend_protocol = "HTTP" backend_port = 80 @@ -104,7 +220,7 @@ module "alb" { # Truncated for brevity ... listeners = { - ex-http-weighted-target = { + http_weighted_target = { port = 80 protocol = "HTTP" weighted_forward = { @@ -123,14 +239,14 @@ module "alb" { } target_groups = { - ex-lambda-with-trigger = { + lambda_with_trigger = { name_prefix = "l1-" target_type = "lambda" lambda_multi_value_headers_enabled = true target_id = module.lambda_with_allowed_triggers.lambda_function_arn } - ex-lambda-without-trigger = { + lambda_without_trigger = { name_prefix = "l2-" target_type = "lambda" target_id = module.lambda_without_allowed_triggers.lambda_function_arn @@ -175,7 +291,7 @@ module "alb" { # Truncated for brevity ... target_groups = { - ex-ip = { + ex_ip = { backend_protocol = "HTTP" backend_port = 80 target_type = "ip" From e3586dd0769e9aead190b980a041405f258fb1cd Mon Sep 17 00:00:00 2001 From: "Anders K. Pettersen" Date: Fri, 3 Nov 2023 21:34:53 +0100 Subject: [PATCH 11/28] docs: Correct key names for protocol and port in target groups (#320) Co-authored-by: Anders K. Pettersen --- README.md | 20 ++++++++++---------- UPGRADE-9.0.md | 8 ++++---- docs/patterns.md | 8 ++++---- examples/complete-alb/main.tf | 4 ++-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5f204e5a..97054593 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,8 @@ module "alb" { target_groups = { ex-instance = { name_prefix = "h1" - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "instance" } } @@ -219,10 +219,10 @@ module "alb" { target_groups = { instance = { - name_prefix = "default" - backend_protocol = "HTTPS" - backend_port = 443 - target_type = "instance" + name_prefix = "default" + protocol = "HTTPS" + port = 443 + target_type = "instance" } } } @@ -306,10 +306,10 @@ module "nlb" { target_groups = { ex-target = { - name_prefix = "pref-" - backend_protocol = "TCP" - backend_port = 80 - target_type = "ip" + name_prefix = "pref-" + protocol = "TCP" + port = 80 + target_type = "ip" } } diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index fc1234da..c1bceb4e 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -410,8 +410,8 @@ module "alb" { target_groups = [ { name_prefix = "h1" - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "instance" health_check = { @@ -793,8 +793,8 @@ module "alb" { target_groups = { instance = { name_prefix = "h1" - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "instance" health_check = { diff --git a/docs/patterns.md b/docs/patterns.md index 3ceb0db8..9ebd320e 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -199,8 +199,8 @@ module "alb" { # This key name is used by the listener/listener rules to know which target to forward traffic to ex_instance = { name_prefix = "h1" - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "instance" deregistration_delay = 10 load_balancing_cross_zone_enabled = true @@ -292,8 +292,8 @@ module "alb" { target_groups = { ex_ip = { - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "ip" deregistration_delay = 5 load_balancing_cross_zone_enabled = true diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 099dc8ae..21279f5e 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -348,8 +348,8 @@ module "alb" { target_groups = { ex-instance = { name_prefix = "h1" - backend_protocol = "HTTP" - backend_port = 80 + protocol = "HTTP" + port = 80 target_type = "instance" deregistration_delay = 10 load_balancing_cross_zone_enabled = false From d6715c71bb5f1f858915f13366ae152038e299e2 Mon Sep 17 00:00:00 2001 From: mnival <1595998+mnival@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:00:29 +0100 Subject: [PATCH 12/28] feat: Add ignore changes on tags to `elasticbeanstalk:shared-elb-environment-count` (#324) --- main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.tf b/main.tf index 5123909a..e08de0f3 100644 --- a/main.tf +++ b/main.tf @@ -61,6 +61,12 @@ resource "aws_lb" "this" { update = try(var.timeouts.update, null) delete = try(var.timeouts.delete, null) } + + lifecycle { + ignore_changes = [ + tags["elasticbeanstalk:shared-elb-environment-count"] + ] + } } ################################################################################ From 7ed6f047d37510f60c866efc984757fc6b660007 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 13 Nov 2023 13:00:55 +0000 Subject: [PATCH 13/28] chore(release): version 9.2.0 [skip ci] ## [9.2.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.1.0...v9.2.0) (2023-11-13) ### Features * Add ignore changes on tags to `elasticbeanstalk:shared-elb-environment-count` ([#324](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/324)) ([d6715c7](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/d6715c71bb5f1f858915f13366ae152038e299e2)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 232b6cba..d0c209b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.2.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.1.0...v9.2.0) (2023-11-13) + + +### Features + +* Add ignore changes on tags to `elasticbeanstalk:shared-elb-environment-count` ([#324](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/324)) ([d6715c7](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/d6715c71bb5f1f858915f13366ae152038e299e2)) + ## [9.1.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.0.0...v9.1.0) (2023-10-30) From 6b4073c873642c4965d543084aa2508d56cc7609 Mon Sep 17 00:00:00 2001 From: Rishav Dhar <19497993+rdhar@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:28:16 +0000 Subject: [PATCH 14/28] docs: Link from examples to patterns (#326) --- examples/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/README.md b/examples/README.md index f417c0ad..86e65aba 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,3 +6,5 @@ Please note - the examples provided serve two primary means: 2. A means of testing/validating module changes Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. + +Additional examples of typical usage patterns are [documented here](/docs/patterns.md). From a1a54c5b0a26919eda7bdd50da6b9eed5fedcc1c Mon Sep 17 00:00:00 2001 From: Rishav Dhar <19497993+rdhar@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:32:25 +0000 Subject: [PATCH 15/28] docs: Add multiple target groups with for loop pattern (#327) Co-authored-by: Bryant Biggs --- docs/patterns.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/patterns.md b/docs/patterns.md index 9ebd320e..2cabe5dd 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -317,3 +317,41 @@ module "alb" { } } ``` + +### Multiple Target Groups with For Loop + +The configuration snippet below creates two target groups using a for loop. This is useful to provision multiple different target groups with similar configurations at scale. The for loop will iterate over the `local.services` map definition and create corresponding `target_groups` map with the same key names and associated values. + +```hcl +local { + services = { + blue = { + path = "/" + port = 80 + } + green = { + path = "/" + port = 80 + } + } +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + + # Truncated for brevity ... + + target_groups = { + for key, value in local.services : key => { + name = key + port = value.port + target_type = "ip" + create_attachment = false + + health_check = { + path = value.path + } + } + } +} +``` From 33feec8bb5a0d71bd03097c5e640e064bb03259e Mon Sep 17 00:00:00 2001 From: Ricardo Leal Date: Fri, 22 Dec 2023 01:21:13 +0000 Subject: [PATCH 16/28] feat: Add option enforce_security_group_inbound_rules_on_private_link_traffic (#332) Co-authored-by: Bryant Biggs --- README.md | 6 ++- examples/complete-alb/README.md | 4 +- examples/complete-alb/versions.tf | 2 +- examples/complete-nlb/README.md | 4 +- examples/complete-nlb/main.tf | 1 + examples/complete-nlb/versions.tf | 2 +- main.tf | 37 +++++++------- variables.tf | 6 +++ versions.tf | 2 +- wrappers/main.tf | 83 ++++++++++++++++--------------- 10 files changed, 79 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 97054593..1612868b 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,7 @@ module "nlb" { subnets = ["subnet-abcde012", "subnet-bcde012a"] # Security Group + enforce_security_group_inbound_rules_on_private_link_traffic = "on" security_group_ingress_rules = { all_http = { from_port = 80 @@ -348,13 +349,13 @@ See [patterns.md](https://github.com/terraform-aws-modules/terraform-aws-alb/blo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.23 | +| [aws](#requirement\_aws) | >= 5.31 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.23 | +| [aws](#provider\_aws) | >= 5.31 | ## Modules @@ -398,6 +399,7 @@ No modules. | [enable\_tls\_version\_and\_cipher\_suite\_headers](#input\_enable\_tls\_version\_and\_cipher\_suite\_headers) | Indicates whether the two headers (`x-amzn-tls-version` and `x-amzn-tls-cipher-suite`), which contain information about the negotiated TLS version and cipher suite, are added to the client request before sending it to the target. Only valid for Load Balancers of type `application`. Defaults to `false` | `bool` | `null` | no | | [enable\_waf\_fail\_open](#input\_enable\_waf\_fail\_open) | Indicates whether to allow a WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF. Defaults to `false` | `bool` | `null` | no | | [enable\_xff\_client\_port](#input\_enable\_xff\_client\_port) | Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer in `application` load balancers. Defaults to `false` | `bool` | `null` | no | +| [enforce\_security\_group\_inbound\_rules\_on\_private\_link\_traffic](#input\_enforce\_security\_group\_inbound\_rules\_on\_private\_link\_traffic) | Indicates whether inbound security group rules are enforced for traffic originating from a PrivateLink. Only valid for Load Balancers of type network. The possible values are on and off. | `string` | `null` | no | | [idle\_timeout](#input\_idle\_timeout) | The time in seconds that the connection is allowed to be idle. Only valid for Load Balancers of type `application`. Default: `60` | `number` | `null` | no | | [internal](#input\_internal) | If true, the LB will be internal. Defaults to `false` | `bool` | `null` | no | | [ip\_address\_type](#input\_ip\_address\_type) | The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack` | `string` | `null` | no | diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index 1e93422d..b11c268b 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.23 | +| [aws](#requirement\_aws) | >= 5.31 | | [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.23 | +| [aws](#provider\_aws) | >= 5.31 | | [null](#provider\_null) | >= 2.0 | ## Modules diff --git a/examples/complete-alb/versions.tf b/examples/complete-alb/versions.tf index 7a23a05f..7d29031e 100644 --- a/examples/complete-alb/versions.tf +++ b/examples/complete-alb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.23" + version = ">= 5.31" } null = { source = "hashicorp/null" diff --git a/examples/complete-nlb/README.md b/examples/complete-nlb/README.md index 3c7efe31..ecdad360 100644 --- a/examples/complete-nlb/README.md +++ b/examples/complete-nlb/README.md @@ -20,13 +20,13 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.23 | +| [aws](#requirement\_aws) | >= 5.31 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.23 | +| [aws](#provider\_aws) | >= 5.31 | ## Modules diff --git a/examples/complete-nlb/main.tf b/examples/complete-nlb/main.tf index f5f3964e..8de943e4 100644 --- a/examples/complete-nlb/main.tf +++ b/examples/complete-nlb/main.tf @@ -46,6 +46,7 @@ module "nlb" { enable_deletion_protection = false # Security Group + enforce_security_group_inbound_rules_on_private_link_traffic = "off" security_group_ingress_rules = { all_tcp = { from_port = 80 diff --git a/examples/complete-nlb/versions.tf b/examples/complete-nlb/versions.tf index a1705fa3..1b260a1d 100644 --- a/examples/complete-nlb/versions.tf +++ b/examples/complete-nlb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.23" + version = ">= 5.31" } } } diff --git a/main.tf b/main.tf index e08de0f3..b3761300 100644 --- a/main.tf +++ b/main.tf @@ -22,24 +22,25 @@ resource "aws_lb" "this" { } } - customer_owned_ipv4_pool = var.customer_owned_ipv4_pool - desync_mitigation_mode = var.desync_mitigation_mode - dns_record_client_routing_policy = var.dns_record_client_routing_policy - drop_invalid_header_fields = var.drop_invalid_header_fields - enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing - enable_deletion_protection = var.enable_deletion_protection - enable_http2 = var.enable_http2 - enable_tls_version_and_cipher_suite_headers = var.enable_tls_version_and_cipher_suite_headers - enable_waf_fail_open = var.enable_waf_fail_open - enable_xff_client_port = var.enable_xff_client_port - idle_timeout = var.idle_timeout - internal = var.internal - ip_address_type = var.ip_address_type - load_balancer_type = var.load_balancer_type - name = var.name - name_prefix = var.name_prefix - preserve_host_header = var.preserve_host_header - security_groups = var.create_security_group ? concat([aws_security_group.this[0].id], var.security_groups) : var.security_groups + customer_owned_ipv4_pool = var.customer_owned_ipv4_pool + desync_mitigation_mode = var.desync_mitigation_mode + dns_record_client_routing_policy = var.dns_record_client_routing_policy + drop_invalid_header_fields = var.drop_invalid_header_fields + enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing + enable_deletion_protection = var.enable_deletion_protection + enable_http2 = var.enable_http2 + enable_tls_version_and_cipher_suite_headers = var.enable_tls_version_and_cipher_suite_headers + enable_waf_fail_open = var.enable_waf_fail_open + enable_xff_client_port = var.enable_xff_client_port + enforce_security_group_inbound_rules_on_private_link_traffic = var.enforce_security_group_inbound_rules_on_private_link_traffic + idle_timeout = var.idle_timeout + internal = var.internal + ip_address_type = var.ip_address_type + load_balancer_type = var.load_balancer_type + name = var.name + name_prefix = var.name_prefix + preserve_host_header = var.preserve_host_header + security_groups = var.create_security_group ? concat([aws_security_group.this[0].id], var.security_groups) : var.security_groups dynamic "subnet_mapping" { for_each = var.subnet_mapping diff --git a/variables.tf b/variables.tf index d69c99d2..7a272f75 100644 --- a/variables.tf +++ b/variables.tf @@ -104,6 +104,12 @@ variable "load_balancer_type" { default = "application" } +variable "enforce_security_group_inbound_rules_on_private_link_traffic" { + description = "Indicates whether inbound security group rules are enforced for traffic originating from a PrivateLink. Only valid for Load Balancers of type network. The possible values are on and off." + type = string + default = null +} + variable "name" { description = "The name of the LB. This name must be unique within your AWS account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen" type = string diff --git a/versions.tf b/versions.tf index a1705fa3..1b260a1d 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.23" + version = ">= 5.31" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 6f902050..63626912 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -3,45 +3,46 @@ module "wrapper" { for_each = var.items - access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) - associate_web_acl = try(each.value.associate_web_acl, var.defaults.associate_web_acl, false) - create = try(each.value.create, var.defaults.create, true) - create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) - customer_owned_ipv4_pool = try(each.value.customer_owned_ipv4_pool, var.defaults.customer_owned_ipv4_pool, null) - default_port = try(each.value.default_port, var.defaults.default_port, 80) - default_protocol = try(each.value.default_protocol, var.defaults.default_protocol, "HTTP") - desync_mitigation_mode = try(each.value.desync_mitigation_mode, var.defaults.desync_mitigation_mode, null) - dns_record_client_routing_policy = try(each.value.dns_record_client_routing_policy, var.defaults.dns_record_client_routing_policy, null) - drop_invalid_header_fields = try(each.value.drop_invalid_header_fields, var.defaults.drop_invalid_header_fields, true) - enable_cross_zone_load_balancing = try(each.value.enable_cross_zone_load_balancing, var.defaults.enable_cross_zone_load_balancing, true) - enable_deletion_protection = try(each.value.enable_deletion_protection, var.defaults.enable_deletion_protection, true) - enable_http2 = try(each.value.enable_http2, var.defaults.enable_http2, null) - enable_tls_version_and_cipher_suite_headers = try(each.value.enable_tls_version_and_cipher_suite_headers, var.defaults.enable_tls_version_and_cipher_suite_headers, null) - enable_waf_fail_open = try(each.value.enable_waf_fail_open, var.defaults.enable_waf_fail_open, null) - enable_xff_client_port = try(each.value.enable_xff_client_port, var.defaults.enable_xff_client_port, null) - idle_timeout = try(each.value.idle_timeout, var.defaults.idle_timeout, null) - internal = try(each.value.internal, var.defaults.internal, null) - ip_address_type = try(each.value.ip_address_type, var.defaults.ip_address_type, null) - listeners = try(each.value.listeners, var.defaults.listeners, {}) - load_balancer_type = try(each.value.load_balancer_type, var.defaults.load_balancer_type, "application") - name = try(each.value.name, var.defaults.name, null) - name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) - preserve_host_header = try(each.value.preserve_host_header, var.defaults.preserve_host_header, null) - putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) - route53_records = try(each.value.route53_records, var.defaults.route53_records, {}) - security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) - security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, {}) - security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) - security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) - security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) - security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) - security_groups = try(each.value.security_groups, var.defaults.security_groups, []) - subnet_mapping = try(each.value.subnet_mapping, var.defaults.subnet_mapping, []) - subnets = try(each.value.subnets, var.defaults.subnets, []) - tags = try(each.value.tags, var.defaults.tags, {}) - target_groups = try(each.value.target_groups, var.defaults.target_groups, {}) - timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) - vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) - web_acl_arn = try(each.value.web_acl_arn, var.defaults.web_acl_arn, null) - xff_header_processing_mode = try(each.value.xff_header_processing_mode, var.defaults.xff_header_processing_mode, null) + access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) + associate_web_acl = try(each.value.associate_web_acl, var.defaults.associate_web_acl, false) + create = try(each.value.create, var.defaults.create, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + customer_owned_ipv4_pool = try(each.value.customer_owned_ipv4_pool, var.defaults.customer_owned_ipv4_pool, null) + default_port = try(each.value.default_port, var.defaults.default_port, 80) + default_protocol = try(each.value.default_protocol, var.defaults.default_protocol, "HTTP") + desync_mitigation_mode = try(each.value.desync_mitigation_mode, var.defaults.desync_mitigation_mode, null) + dns_record_client_routing_policy = try(each.value.dns_record_client_routing_policy, var.defaults.dns_record_client_routing_policy, null) + drop_invalid_header_fields = try(each.value.drop_invalid_header_fields, var.defaults.drop_invalid_header_fields, true) + enable_cross_zone_load_balancing = try(each.value.enable_cross_zone_load_balancing, var.defaults.enable_cross_zone_load_balancing, true) + enable_deletion_protection = try(each.value.enable_deletion_protection, var.defaults.enable_deletion_protection, true) + enable_http2 = try(each.value.enable_http2, var.defaults.enable_http2, null) + enable_tls_version_and_cipher_suite_headers = try(each.value.enable_tls_version_and_cipher_suite_headers, var.defaults.enable_tls_version_and_cipher_suite_headers, null) + enable_waf_fail_open = try(each.value.enable_waf_fail_open, var.defaults.enable_waf_fail_open, null) + enable_xff_client_port = try(each.value.enable_xff_client_port, var.defaults.enable_xff_client_port, null) + enforce_security_group_inbound_rules_on_private_link_traffic = try(each.value.enforce_security_group_inbound_rules_on_private_link_traffic, var.defaults.enforce_security_group_inbound_rules_on_private_link_traffic, null) + idle_timeout = try(each.value.idle_timeout, var.defaults.idle_timeout, null) + internal = try(each.value.internal, var.defaults.internal, null) + ip_address_type = try(each.value.ip_address_type, var.defaults.ip_address_type, null) + listeners = try(each.value.listeners, var.defaults.listeners, {}) + load_balancer_type = try(each.value.load_balancer_type, var.defaults.load_balancer_type, "application") + name = try(each.value.name, var.defaults.name, null) + name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) + preserve_host_header = try(each.value.preserve_host_header, var.defaults.preserve_host_header, null) + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) + route53_records = try(each.value.route53_records, var.defaults.route53_records, {}) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, {}) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + security_groups = try(each.value.security_groups, var.defaults.security_groups, []) + subnet_mapping = try(each.value.subnet_mapping, var.defaults.subnet_mapping, []) + subnets = try(each.value.subnets, var.defaults.subnets, []) + tags = try(each.value.tags, var.defaults.tags, {}) + target_groups = try(each.value.target_groups, var.defaults.target_groups, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) + web_acl_arn = try(each.value.web_acl_arn, var.defaults.web_acl_arn, null) + xff_header_processing_mode = try(each.value.xff_header_processing_mode, var.defaults.xff_header_processing_mode, null) } From 7339d02f5aabeefd8416bc8a02dade90a7fb2be3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 22 Dec 2023 01:21:37 +0000 Subject: [PATCH 17/28] chore(release): version 9.3.0 [skip ci] ## [9.3.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.2.0...v9.3.0) (2023-12-22) ### Features * Add option enforce_security_group_inbound_rules_on_private_link_traffic ([#332](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/332)) ([33feec8](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/33feec8bb5a0d71bd03097c5e640e064bb03259e)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0c209b5..dc02693e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.3.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.2.0...v9.3.0) (2023-12-22) + + +### Features + +* Add option enforce_security_group_inbound_rules_on_private_link_traffic ([#332](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/332)) ([33feec8](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/33feec8bb5a0d71bd03097c5e640e064bb03259e)) + ## [9.2.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.1.0...v9.2.0) (2023-11-13) From 3167d653eadf414f31fa74cdd7e453cff7513f33 Mon Sep 17 00:00:00 2001 From: Melissa Greenbaum <69476188+magreenbaum@users.noreply.github.com> Date: Sat, 23 Dec 2023 07:23:27 -0500 Subject: [PATCH 18/28] feat: ALB Connection logging (#334) Co-authored-by: Bryant Biggs --- README.md | 1 + examples/complete-alb/main.tf | 7 +++++++ main.tf | 9 +++++++++ variables.tf | 6 ++++++ wrappers/main.tf | 1 + 5 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 1612868b..da67971f 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,7 @@ No modules. |------|-------------|------|---------|:--------:| | [access\_logs](#input\_access\_logs) | Map containing access logging configuration for load balancer | `map(string)` | `{}` | no | | [associate\_web\_acl](#input\_associate\_web\_acl) | Indicates whether a Web Application Firewall (WAF) ACL should be associated with the load balancer | `bool` | `false` | no | +| [connection\_logs](#input\_connection\_logs) | Map containing access logging configuration for load balancer | `map(string)` | `{}` | no | | [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | | [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | | [customer\_owned\_ipv4\_pool](#input\_customer\_owned\_ipv4\_pool) | The ID of the customer owned ipv4 pool to use for this load balancer | `string` | `null` | no | diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 21279f5e..960082ab 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -58,6 +58,13 @@ module "alb" { access_logs = { bucket = module.log_bucket.s3_bucket_id + prefix = "access-logs" + } + + connection_logs = { + bucket = module.log_bucket.s3_bucket_id + enabled = true + prefix = "connection-logs" } listeners = { diff --git a/main.tf b/main.tf index b3761300..3c0ef501 100644 --- a/main.tf +++ b/main.tf @@ -22,6 +22,15 @@ resource "aws_lb" "this" { } } + dynamic "connection_logs" { + for_each = length(var.connection_logs) > 0 ? [var.connection_logs] : [] + content { + bucket = connection_logs.value.bucket + enabled = try(connection_logs.value.enabled, true) + prefix = try(connection_logs.value.prefix, null) + } + } + customer_owned_ipv4_pool = var.customer_owned_ipv4_pool desync_mitigation_mode = var.desync_mitigation_mode dns_record_client_routing_policy = var.dns_record_client_routing_policy diff --git a/variables.tf b/variables.tf index 7a272f75..4a010ebc 100644 --- a/variables.tf +++ b/variables.tf @@ -20,6 +20,12 @@ variable "access_logs" { default = {} } +variable "connection_logs" { + description = "Map containing access logging configuration for load balancer" + type = map(string) + default = {} +} + variable "customer_owned_ipv4_pool" { description = "The ID of the customer owned ipv4 pool to use for this load balancer" type = string diff --git a/wrappers/main.tf b/wrappers/main.tf index 63626912..dd4f0807 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -5,6 +5,7 @@ module "wrapper" { access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) associate_web_acl = try(each.value.associate_web_acl, var.defaults.associate_web_acl, false) + connection_logs = try(each.value.connection_logs, var.defaults.connection_logs, {}) create = try(each.value.create, var.defaults.create, true) create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) customer_owned_ipv4_pool = try(each.value.customer_owned_ipv4_pool, var.defaults.customer_owned_ipv4_pool, null) From ca4c3321c4ce9efa09c7e4ed9d67a2bbc7f262a5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 23 Dec 2023 12:23:56 +0000 Subject: [PATCH 19/28] chore(release): version 9.4.0 [skip ci] ## [9.4.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.3.0...v9.4.0) (2023-12-23) ### Features * ALB Connection logging ([#334](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/334)) ([3167d65](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/3167d653eadf414f31fa74cdd7e453cff7513f33)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc02693e..7408be0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.4.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.3.0...v9.4.0) (2023-12-23) + + +### Features + +* ALB Connection logging ([#334](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/334)) ([3167d65](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/3167d653eadf414f31fa74cdd7e453cff7513f33)) + ## [9.3.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.2.0...v9.3.0) (2023-12-22) From 9ff4ea856a9fed0921d1aad362771ac25fec7ed3 Mon Sep 17 00:00:00 2001 From: chas0amx <53621461+chas0amx@users.noreply.github.com> Date: Tue, 26 Dec 2023 12:42:23 +0200 Subject: [PATCH 20/28] chore: Fixed typo in example in README.md (#335) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da67971f..9deac739 100644 --- a/README.md +++ b/README.md @@ -298,7 +298,7 @@ module "nlb" { ex-tls = { port = 84 protocol = "TLS" - certificate_arn = certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" + certificate_arn = "arn:aws:iam::123456789012:server-certificate/test_cert-123456789012" forward = { target_group_key = "ex-target" } From a28fe0b4bda318a271c5eefb1ceec9dbafc3369d Mon Sep 17 00:00:00 2001 From: Andy Li Date: Fri, 12 Jan 2024 13:36:24 +0000 Subject: [PATCH 21/28] fix: Change `subnets` default value to `null` (#341) --- README.md | 2 +- variables.tf | 2 +- wrappers/main.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9deac739..f920d370 100644 --- a/README.md +++ b/README.md @@ -419,7 +419,7 @@ No modules. | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | | [security\_groups](#input\_security\_groups) | A list of security group IDs to assign to the LB | `list(string)` | `[]` | no | | [subnet\_mapping](#input\_subnet\_mapping) | A list of subnet mapping blocks describing subnets to attach to load balancer | `list(map(string))` | `[]` | no | -| [subnets](#input\_subnets) | A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type `network`. Changing this value for load balancers of type `network` will force a recreation of the resource | `list(string)` | `[]` | no | +| [subnets](#input\_subnets) | A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type `network`. Changing this value for load balancers of type `network` will force a recreation of the resource | `list(string)` | `null` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | | [target\_groups](#input\_target\_groups) | Map of target group configurations to create | `any` | `{}` | no | | [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the load balancer | `map(string)` | `{}` | no | diff --git a/variables.tf b/variables.tf index 4a010ebc..9cf497d5 100644 --- a/variables.tf +++ b/variables.tf @@ -149,7 +149,7 @@ variable "subnet_mapping" { variable "subnets" { description = "A list of subnet IDs to attach to the LB. Subnets cannot be updated for Load Balancers of type `network`. Changing this value for load balancers of type `network` will force a recreation of the resource" type = list(string) - default = [] + default = null } variable "xff_header_processing_mode" { diff --git a/wrappers/main.tf b/wrappers/main.tf index dd4f0807..34342212 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -39,7 +39,7 @@ module "wrapper" { security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) security_groups = try(each.value.security_groups, var.defaults.security_groups, []) subnet_mapping = try(each.value.subnet_mapping, var.defaults.subnet_mapping, []) - subnets = try(each.value.subnets, var.defaults.subnets, []) + subnets = try(each.value.subnets, var.defaults.subnets, null) tags = try(each.value.tags, var.defaults.tags, {}) target_groups = try(each.value.target_groups, var.defaults.target_groups, {}) timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) From eb1a918687b6867aa5bf8f3703da64c5539af677 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Jan 2024 13:36:50 +0000 Subject: [PATCH 22/28] chore(release): version 9.4.1 [skip ci] ### [9.4.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.0...v9.4.1) (2024-01-12) ### Bug Fixes * Change `subnets` default value to `null` ([#341](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/341)) ([a28fe0b](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/a28fe0b4bda318a271c5eefb1ceec9dbafc3369d)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7408be0a..7874c76e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +### [9.4.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.0...v9.4.1) (2024-01-12) + + +### Bug Fixes + +* Change `subnets` default value to `null` ([#341](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/341)) ([a28fe0b](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/a28fe0b4bda318a271c5eefb1ceec9dbafc3369d)) + ## [9.4.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.3.0...v9.4.0) (2023-12-23) From 9f03c7ade3de494363f9d5b3fb17932b53d3f426 Mon Sep 17 00:00:00 2001 From: Melissa Greenbaum <69476188+magreenbaum@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:10:34 -0500 Subject: [PATCH 23/28] feat: Added support for ALB trust store (#344) --- .gitignore | 6 + README.md | 4 +- examples/complete-alb/README.md | 4 +- examples/complete-alb/versions.tf | 2 +- examples/complete-nlb/README.md | 4 +- examples/complete-nlb/versions.tf | 2 +- examples/mutual-auth-alb/README.md | 89 ++++++ examples/mutual-auth-alb/ca.conf | 15 + examples/mutual-auth-alb/main.tf | 387 ++++++++++++++++++++++++++ examples/mutual-auth-alb/outputs.tf | 82 ++++++ examples/mutual-auth-alb/variables.tf | 5 + examples/mutual-auth-alb/versions.tf | 18 ++ main.tf | 9 + modules/lb_trust_store/README.md | 75 +++++ modules/lb_trust_store/main.tf | 19 ++ modules/lb_trust_store/outputs.tf | 24 ++ modules/lb_trust_store/variables.tf | 53 ++++ modules/lb_trust_store/versions.tf | 10 + versions.tf | 2 +- wrappers/lb_trust_store/README.md | 100 +++++++ wrappers/lb_trust_store/main.tf | 15 + wrappers/lb_trust_store/outputs.tf | 5 + wrappers/lb_trust_store/variables.tf | 11 + wrappers/lb_trust_store/versions.tf | 3 + 24 files changed, 935 insertions(+), 9 deletions(-) create mode 100644 examples/mutual-auth-alb/README.md create mode 100644 examples/mutual-auth-alb/ca.conf create mode 100644 examples/mutual-auth-alb/main.tf create mode 100644 examples/mutual-auth-alb/outputs.tf create mode 100644 examples/mutual-auth-alb/variables.tf create mode 100644 examples/mutual-auth-alb/versions.tf create mode 100644 modules/lb_trust_store/README.md create mode 100644 modules/lb_trust_store/main.tf create mode 100644 modules/lb_trust_store/outputs.tf create mode 100644 modules/lb_trust_store/variables.tf create mode 100644 modules/lb_trust_store/versions.tf create mode 100644 wrappers/lb_trust_store/README.md create mode 100644 wrappers/lb_trust_store/main.tf create mode 100644 wrappers/lb_trust_store/outputs.tf create mode 100644 wrappers/lb_trust_store/variables.tf create mode 100644 wrappers/lb_trust_store/versions.tf diff --git a/.gitignore b/.gitignore index cf9db9f8..e6a5c1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ terraform.rc # Zip archive *.zip + +# Ignore cert generation files +*.pem +*.key +*.csr +**/cert_files/* diff --git a/README.md b/README.md index f920d370..176477ae 100644 --- a/README.md +++ b/README.md @@ -349,13 +349,13 @@ See [patterns.md](https://github.com/terraform-aws-modules/terraform-aws-alb/blo | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.31 | +| [aws](#requirement\_aws) | >= 5.33 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.31 | +| [aws](#provider\_aws) | >= 5.33 | ## Modules diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index b11c268b..8845abe7 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -20,14 +20,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.31 | +| [aws](#requirement\_aws) | >= 5.33 | | [null](#requirement\_null) | >= 2.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.31 | +| [aws](#provider\_aws) | >= 5.33 | | [null](#provider\_null) | >= 2.0 | ## Modules diff --git a/examples/complete-alb/versions.tf b/examples/complete-alb/versions.tf index 7d29031e..9dc28a0e 100644 --- a/examples/complete-alb/versions.tf +++ b/examples/complete-alb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.31" + version = ">= 5.33" } null = { source = "hashicorp/null" diff --git a/examples/complete-nlb/README.md b/examples/complete-nlb/README.md index ecdad360..24edae7b 100644 --- a/examples/complete-nlb/README.md +++ b/examples/complete-nlb/README.md @@ -20,13 +20,13 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 5.31 | +| [aws](#requirement\_aws) | >= 5.33 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.31 | +| [aws](#provider\_aws) | >= 5.33 | ## Modules diff --git a/examples/complete-nlb/versions.tf b/examples/complete-nlb/versions.tf index 1b260a1d..992f1b9a 100644 --- a/examples/complete-nlb/versions.tf +++ b/examples/complete-nlb/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.31" + version = ">= 5.33" } } } diff --git a/examples/mutual-auth-alb/README.md b/examples/mutual-auth-alb/README.md new file mode 100644 index 00000000..675e9429 --- /dev/null +++ b/examples/mutual-auth-alb/README.md @@ -0,0 +1,89 @@ +# Mutual Authentication ALB Example + +Configuration in this directory creates an Application Load Balancer, a self-signed CA bundle, and load balancer trust store for mutual authentication. +https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.33 | +| [null](#requirement\_null) | >= 2.0 | +| [tls](#requirement\_tls) | >= 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.33 | +| [null](#provider\_null) | >= 2.0 | +| [tls](#provider\_tls) | >= 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [acm](#module\_acm) | terraform-aws-modules/acm/aws | ~> 4.0 | +| [alb](#module\_alb) | ../../ | n/a | +| [ca\_cert\_object](#module\_ca\_cert\_object) | terraform-aws-modules/s3-bucket/aws//modules/object | n/a | +| [certificate\_bucket](#module\_certificate\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [crl\_object](#module\_crl\_object) | terraform-aws-modules/s3-bucket/aws//modules/object | n/a | +| [log\_bucket](#module\_log\_bucket) | terraform-aws-modules/s3-bucket/aws | ~> 3.0 | +| [trust\_store](#module\_trust\_store) | ../../modules/lb_trust_store | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | +| [null_resource.generate_crl](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | +| [tls_cert_request.my_client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/cert_request) | resource | +| [tls_cert_request.my_client_revoked](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/cert_request) | resource | +| [tls_locally_signed_cert.my_client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/locally_signed_cert) | resource | +| [tls_locally_signed_cert.my_client_revoked](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/locally_signed_cert) | resource | +| [tls_private_key.my_client](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [tls_private_key.my_client_revoked](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [tls_private_key.root_ca](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource | +| [tls_self_signed_cert.root_ca](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_route53_zone.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +| [aws_ssm_parameter.al2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [domain\_name](#input\_domain\_name) | The domain name for which the certificate should be issued | `string` | `"terraform-aws-modules.modules.tf"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ID and ARN of the load balancer we created | +| [arn\_suffix](#output\_arn\_suffix) | ARN suffix of our load balancer - can be used with CloudWatch | +| [dns\_name](#output\_dns\_name) | The DNS name of the load balancer | +| [id](#output\_id) | The ID and ARN of the load balancer we created | +| [listener\_rules](#output\_listener\_rules) | Map of listeners rules created and their attributes | +| [listeners](#output\_listeners) | Map of listeners created and their attributes | +| [route53\_records](#output\_route53\_records) | The Route53 records created and attached to the load balancer | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [target\_groups](#output\_target\_groups) | Map of target groups created and their attributes | +| [trust\_store](#output\_trust\_store) | Map of trust store attributes | +| [zone\_id](#output\_zone\_id) | The zone\_id of the load balancer to assist with creating DNS records | + diff --git a/examples/mutual-auth-alb/ca.conf b/examples/mutual-auth-alb/ca.conf new file mode 100644 index 00000000..fa4ed255 --- /dev/null +++ b/examples/mutual-auth-alb/ca.conf @@ -0,0 +1,15 @@ +# This file is for example purposes only +[ca] +default_ca = root_ca + +[root_ca] +dir = ./cert_files/ +new_certs_dir = $dir +database = $dir/crl_index +serial = $dir/cert_serial +default_md = sha256 +crlnumber = $dir/crl_number +default_crl_days = 365 + +[crl_ext] +authorityKeyIdentifier = keyid:always diff --git a/examples/mutual-auth-alb/main.tf b/examples/mutual-auth-alb/main.tf new file mode 100644 index 00000000..2370d26c --- /dev/null +++ b/examples/mutual-auth-alb/main.tf @@ -0,0 +1,387 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-alb" + } + + script = < cert_files/cert_serial +echo 01 > cert_files/crl_number +touch cert_files/crl_index + +# generate crl +openssl ca -gencrl -keyfile <(echo "${tls_private_key.root_ca.private_key_pem}") -cert <(echo "${tls_self_signed_cert.root_ca.cert_pem}") -out cert_files/crl.pem -config ca.conf + +# revoke a client cert +openssl ca -revoke <(echo "${tls_locally_signed_cert.my_client_revoked.cert_pem}") -keyfile <(echo "${tls_private_key.root_ca.private_key_pem}") -cert <(echo "${tls_self_signed_cert.root_ca.cert_pem}") -config ca.conf + +# regenerate crl after revoking a cert +openssl ca -gencrl -keyfile <(echo "${tls_private_key.root_ca.private_key_pem}") -cert <(echo "${tls_self_signed_cert.root_ca.cert_pem}") -out cert_files/crl.pem -config ca.conf + +EOF +} + +module "alb" { + source = "../../" + + name = local.name + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_https = { + from_port = 443 + to_port = 443 + ip_protocol = "tcp" + description = "HTTPS web traffic" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + access_logs = { + bucket = module.log_bucket.s3_bucket_id + prefix = "access-logs" + } + + connection_logs = { + bucket = module.log_bucket.s3_bucket_id + enabled = true + prefix = "connection-logs" + } + + listeners = { + ex-https = { + port = 443 + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06" + certificate_arn = module.acm.acm_certificate_arn + mutual_authentication = { + mode = "verify" + trust_store_arn = module.trust_store.trust_store_arn + } + + forward = { + target_group_key = "ex-instance" + } + + rules = { + ex-fixed-response = { + priority = 3 + actions = [ + { + type = "fixed-response" + content_type = "text/plain" + status_code = 200 + message_body = "This is a fixed response" + } + ] + conditions = [{ + http_header = { + http_header_name = "x-Gimme-Fixed-Response" + values = ["yes", "please", "right now"] + } + }] + } + } + } + } + + target_groups = { + ex-instance = { + name_prefix = "h1" + protocol = "HTTP" + port = 80 + target_type = "instance" + deregistration_delay = 10 + load_balancing_cross_zone_enabled = false + + health_check = { + enabled = true + interval = 30 + path = "/healthz" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + protocol = "HTTP" + matcher = "200-399" + } + + protocol_version = "HTTP1" + target_id = aws_instance.this.id + port = 80 + tags = { + InstanceTargetGroupTag = "baz" + } + } + } + + # Route53 Record(s) + route53_records = { + A = { + name = local.name + type = "A" + zone_id = data.aws_route53_zone.this.id + } + AAAA = { + name = local.name + type = "AAAA" + zone_id = data.aws_route53_zone.this.id + } + } +} + +module "trust_store" { + source = "../../modules/lb_trust_store" + + name = "${local.name}-trust-store" + ca_certificates_bundle_s3_bucket = module.certificate_bucket.s3_bucket_id + ca_certificates_bundle_s3_key = "ca_cert/RootCA.pem" + create_trust_store_revocation = true + revocation_lists = { + crl_1 = { + revocations_s3_bucket = module.certificate_bucket.s3_bucket_id + revocations_s3_key = "crl/crl.pem" + } + } + + depends_on = [ + module.ca_cert_object, + module.crl_object, + ] +} + +################################################################################ +# Supporting resources +################################################################################ + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + tags = local.tags +} + +data "aws_ssm_parameter" "al2" { + name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" +} + +resource "aws_instance" "this" { + ami = data.aws_ssm_parameter.al2.value + instance_type = "t3.nano" + subnet_id = element(module.vpc.private_subnets, 0) +} + +module "log_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket_prefix = "${local.name}-logs-" + acl = "log-delivery-write" + + # For example only + force_destroy = true + + control_object_ownership = true + object_ownership = "ObjectWriter" + + attach_elb_log_delivery_policy = true # Required for ALB logs + attach_lb_log_delivery_policy = true # Required for ALB/NLB logs + + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + + tags = local.tags +} + +data "aws_route53_zone" "this" { + name = var.domain_name +} + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 4.0" + + domain_name = "*.${var.domain_name}" + zone_id = data.aws_route53_zone.this.id +} + +resource "null_resource" "generate_crl" { + provisioner "local-exec" { + command = local.script + interpreter = ["/bin/bash", "-c"] + } + + depends_on = [ + tls_locally_signed_cert.my_client_revoked + ] +} + +module "certificate_bucket" { + source = "terraform-aws-modules/s3-bucket/aws" + version = "~> 3.0" + + bucket_prefix = "${local.name}-certificate-" + force_destroy = true + + tags = local.tags +} + +module "ca_cert_object" { + source = "terraform-aws-modules/s3-bucket/aws//modules/object" + + bucket = module.certificate_bucket.s3_bucket_id + key = "ca_cert/RootCA.pem" + + content = tls_self_signed_cert.root_ca.cert_pem + + tags = local.tags +} + +module "crl_object" { + source = "terraform-aws-modules/s3-bucket/aws//modules/object" + + bucket = module.certificate_bucket.s3_bucket_id + key = "crl/crl.pem" + + file_source = "${path.module}/cert_files/crl.pem" + + tags = local.tags + + depends_on = [null_resource.generate_crl] +} + +################################################################################ +# Client/Server Certificates +################################################################################ + +# Root CA +resource "tls_private_key" "root_ca" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "tls_self_signed_cert" "root_ca" { + private_key_pem = tls_private_key.root_ca.private_key_pem + + validity_period_hours = 43800 + + allowed_uses = [ + "digital_signature", + "cert_signing", + "crl_signing", + ] + + is_ca_certificate = true + dns_names = [var.domain_name, "*.${var.domain_name}"] + + subject { + country = "IE" + province = "Dublin" + locality = "Dublin" + common_name = var.domain_name + organization = "terraform-aws-modules" + } +} + +# client cert +resource "tls_private_key" "my_client" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "tls_cert_request" "my_client" { + private_key_pem = tls_private_key.my_client.private_key_pem + + subject { + country = "IE" + province = "Dublin" + locality = "Dublin" + common_name = "my-client.${var.domain_name}" + organization = "terraform-aws-modules" + } +} + +resource "tls_locally_signed_cert" "my_client" { + cert_request_pem = tls_cert_request.my_client.cert_request_pem + ca_private_key_pem = tls_private_key.root_ca.private_key_pem + ca_cert_pem = tls_self_signed_cert.root_ca.cert_pem + + validity_period_hours = 12 + set_subject_key_id = true + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + "client_auth" + ] +} + +# client cert to be revoked +resource "tls_private_key" "my_client_revoked" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "tls_cert_request" "my_client_revoked" { + private_key_pem = tls_private_key.my_client_revoked.private_key_pem + + subject { + country = "IE" + province = "Dublin" + locality = "Dublin" + common_name = "my-client-revoked.${var.domain_name}" + organization = "terraform-aws-modules" + } +} + +resource "tls_locally_signed_cert" "my_client_revoked" { + cert_request_pem = tls_cert_request.my_client_revoked.cert_request_pem + ca_private_key_pem = tls_private_key.root_ca.private_key_pem + ca_cert_pem = tls_self_signed_cert.root_ca.cert_pem + + validity_period_hours = 12 + set_subject_key_id = true + + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + "client_auth" + ] +} diff --git a/examples/mutual-auth-alb/outputs.tf b/examples/mutual-auth-alb/outputs.tf new file mode 100644 index 00000000..c1028790 --- /dev/null +++ b/examples/mutual-auth-alb/outputs.tf @@ -0,0 +1,82 @@ +################################################################################ +# Trust Store +################################################################################ +output "trust_store" { + description = "Map of trust store attributes" + value = module.trust_store +} + +################################################################################ +# Load Balancer +################################################################################ + +output "id" { + description = "The ID and ARN of the load balancer we created" + value = module.alb.id +} + +output "arn" { + description = "The ID and ARN of the load balancer we created" + value = module.alb.arn +} + +output "arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch" + value = module.alb.arn_suffix +} + +output "dns_name" { + description = "The DNS name of the load balancer" + value = module.alb.dns_name +} + +output "zone_id" { + description = "The zone_id of the load balancer to assist with creating DNS records" + value = module.alb.zone_id +} + +################################################################################ +# Listener(s) +################################################################################ + +output "listeners" { + description = "Map of listeners created and their attributes" + value = module.alb.listeners +} + +output "listener_rules" { + description = "Map of listeners rules created and their attributes" + value = module.alb.listener_rules +} + +################################################################################ +# Target Group(s) +################################################################################ + +output "target_groups" { + description = "Map of target groups created and their attributes" + value = module.alb.target_groups +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.alb.security_group_arn +} + +output "security_group_id" { + description = "ID of the security group" + value = module.alb.security_group_id +} + +################################################################################ +# Route53 Record(s) +################################################################################ + +output "route53_records" { + description = "The Route53 records created and attached to the load balancer" + value = module.alb.route53_records +} diff --git a/examples/mutual-auth-alb/variables.tf b/examples/mutual-auth-alb/variables.tf new file mode 100644 index 00000000..a5e6aabe --- /dev/null +++ b/examples/mutual-auth-alb/variables.tf @@ -0,0 +1,5 @@ +variable "domain_name" { + description = "The domain name for which the certificate should be issued" + type = string + default = "terraform-aws-modules.modules.tf" +} diff --git a/examples/mutual-auth-alb/versions.tf b/examples/mutual-auth-alb/versions.tf new file mode 100644 index 00000000..1fd6946f --- /dev/null +++ b/examples/mutual-auth-alb/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.33" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + tls = { + source = "hashicorp/tls" + version = ">= 4.0" + } + } +} diff --git a/main.tf b/main.tf index 3c0ef501..9dca1efd 100644 --- a/main.tf +++ b/main.tf @@ -205,6 +205,15 @@ resource "aws_lb_listener" "this" { } } + dynamic "mutual_authentication" { + for_each = try([each.value.mutual_authentication], []) + content { + mode = mutual_authentication.value.mode + trust_store_arn = try(mutual_authentication.value.trust_store_arn, null) + ignore_client_certificate_expiry = try(mutual_authentication.value.ignore_client_certificate_expiry, null) + } + } + load_balancer_arn = aws_lb.this[0].arn port = try(each.value.port, var.default_port) protocol = try(each.value.protocol, var.default_protocol) diff --git a/modules/lb_trust_store/README.md b/modules/lb_trust_store/README.md new file mode 100644 index 00000000..c60eb491 --- /dev/null +++ b/modules/lb_trust_store/README.md @@ -0,0 +1,75 @@ +# lb_trust_store + +Terraform module which creates an ALB trust store and trust store revocation list resources. + +## Usage +``` +module "trust_store" { + source = "terraform-aws-modules/alb/aws//modules/lb_trust_store" + + name = "my-trust-store" + ca_certificates_bundle_s3_bucket = "my-cert-bucket" + ca_certificates_bundle_s3_key = "ca_cert/RootCA.pem" + create_trust_store_revocation = true + revocation_lists = { + crl_1 = { + revocations_s3_bucket = "my-cert-bucket" + revocations_s3_key = "crl/crl_1.pem" + } + crl_2 = { + revocations_s3_bucket = "my-cert-bucket" + revocations_s3_key = "crl/crl_2.pem" + } + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.33 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.33 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_lb_trust_store.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_trust_store) | resource | +| [aws_lb_trust_store_revocation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_trust_store_revocation) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [ca\_certificates\_bundle\_s3\_bucket](#input\_ca\_certificates\_bundle\_s3\_bucket) | S3 bucket name holding the client certificate CA bundle. | `string` | `null` | no | +| [ca\_certificates\_bundle\_s3\_key](#input\_ca\_certificates\_bundle\_s3\_key) | S3 object key holding the client certificate CA bundle. | `string` | `null` | no | +| [ca\_certificates\_bundle\_s3\_object\_version](#input\_ca\_certificates\_bundle\_s3\_object\_version) | Version ID of CA bundle S3 bucket object, if versioned, defaults to latest if omitted. | `string` | `null` | no | +| [create](#input\_create) | Controls if resources should be created. | `bool` | `true` | no | +| [create\_trust\_store\_revocation](#input\_create\_trust\_store\_revocation) | Whether to create a trust store revocation for use with an application load balancer. | `bool` | `false` | no | +| [name](#input\_name) | Name of the trust store. If omitted, Terraform will assign a random, unique name. This name must be unique per region, per account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen. | `string` | `null` | no | +| [name\_prefix](#input\_name\_prefix) | Creates a unique name beginning with the specified prefix. Conflicts with `name`. Cannot be longer than 6 characters. | `string` | `null` | no | +| [revocation\_lists](#input\_revocation\_lists) | Map of revocation list configurations. | `any` | `{}` | no | +| [tags](#input\_tags) | Map of tags to assign to the resource. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [revocation\_lists](#output\_revocation\_lists) | Map of revocation lists and their attributes. | +| [trust\_store\_arn](#output\_trust\_store\_arn) | ARN of the trust store (matches `id`). | +| [trust\_store\_arn\_suffix](#output\_trust\_store\_arn\_suffix) | ARN suffix for use with cloudwatch metrics. | +| [trust\_store\_id](#output\_trust\_store\_id) | ARN of the trust store (matches `arn`). | +| [trust\_store\_name](#output\_trust\_store\_name) | Name of the trust store. | + diff --git a/modules/lb_trust_store/main.tf b/modules/lb_trust_store/main.tf new file mode 100644 index 00000000..1009aeed --- /dev/null +++ b/modules/lb_trust_store/main.tf @@ -0,0 +1,19 @@ +resource "aws_lb_trust_store" "this" { + count = var.create ? 1 : 0 + + ca_certificates_bundle_s3_bucket = var.ca_certificates_bundle_s3_bucket + ca_certificates_bundle_s3_key = var.ca_certificates_bundle_s3_key + ca_certificates_bundle_s3_object_version = var.ca_certificates_bundle_s3_object_version + name_prefix = var.name_prefix != null ? "${var.name_prefix}-" : null + name = var.name != null ? var.name : null + tags = var.tags +} + +resource "aws_lb_trust_store_revocation" "this" { + for_each = { for k, v in var.revocation_lists : k => v if var.create && var.create_trust_store_revocation } + + trust_store_arn = aws_lb_trust_store.this[0].arn + revocations_s3_bucket = each.value.revocations_s3_bucket + revocations_s3_key = each.value.revocations_s3_key + revocations_s3_object_version = try(each.value.revocations_s3_object_version, null) +} diff --git a/modules/lb_trust_store/outputs.tf b/modules/lb_trust_store/outputs.tf new file mode 100644 index 00000000..16de3cd5 --- /dev/null +++ b/modules/lb_trust_store/outputs.tf @@ -0,0 +1,24 @@ +output "trust_store_arn_suffix" { + description = "ARN suffix for use with cloudwatch metrics." + value = try(aws_lb_trust_store.this[0].arn_suffix, null) +} + +output "trust_store_arn" { + description = "ARN of the trust store (matches `id`)." + value = try(aws_lb_trust_store.this[0].arn, null) +} + +output "trust_store_id" { + description = "ARN of the trust store (matches `arn`)." + value = try(aws_lb_trust_store.this[0].id, null) +} + +output "trust_store_name" { + description = "Name of the trust store." + value = try(aws_lb_trust_store.this[0].name, null) +} + +output "revocation_lists" { + description = "Map of revocation lists and their attributes." + value = try(aws_lb_trust_store_revocation.this, null) +} diff --git a/modules/lb_trust_store/variables.tf b/modules/lb_trust_store/variables.tf new file mode 100644 index 00000000..4f6fb582 --- /dev/null +++ b/modules/lb_trust_store/variables.tf @@ -0,0 +1,53 @@ +variable "create" { + description = "Controls if resources should be created." + type = bool + default = true +} + +variable "ca_certificates_bundle_s3_bucket" { + description = "S3 bucket name holding the client certificate CA bundle." + type = string + default = null +} + +variable "ca_certificates_bundle_s3_key" { + description = "S3 object key holding the client certificate CA bundle." + type = string + default = null +} + +variable "ca_certificates_bundle_s3_object_version" { + description = "Version ID of CA bundle S3 bucket object, if versioned, defaults to latest if omitted." + type = string + default = null +} + +variable "name" { + description = "Name of the trust store. If omitted, Terraform will assign a random, unique name. This name must be unique per region, per account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen." + type = string + default = null +} + +variable "name_prefix" { + description = "Creates a unique name beginning with the specified prefix. Conflicts with `name`. Cannot be longer than 6 characters." + type = string + default = null +} + +variable "create_trust_store_revocation" { + description = "Whether to create a trust store revocation for use with an application load balancer." + type = bool + default = false +} + +variable "revocation_lists" { + description = "Map of revocation list configurations." + type = any + default = {} +} + +variable "tags" { + description = "Map of tags to assign to the resource." + type = map(string) + default = {} +} diff --git a/modules/lb_trust_store/versions.tf b/modules/lb_trust_store/versions.tf new file mode 100644 index 00000000..992f1b9a --- /dev/null +++ b/modules/lb_trust_store/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.33" + } + } +} diff --git a/versions.tf b/versions.tf index 1b260a1d..992f1b9a 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 5.31" + version = ">= 5.33" } } } diff --git a/wrappers/lb_trust_store/README.md b/wrappers/lb_trust_store/README.md new file mode 100644 index 00000000..4d51624e --- /dev/null +++ b/wrappers/lb_trust_store/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/lb_trust_store` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/alb/aws//wrappers/lb_trust_store" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-alb.git//wrappers/lb_trust_store?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/alb/aws//wrappers/lb_trust_store" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/lb_trust_store/main.tf b/wrappers/lb_trust_store/main.tf new file mode 100644 index 00000000..5cccc4ca --- /dev/null +++ b/wrappers/lb_trust_store/main.tf @@ -0,0 +1,15 @@ +module "wrapper" { + source = "../../modules/lb_trust_store" + + for_each = var.items + + ca_certificates_bundle_s3_bucket = try(each.value.ca_certificates_bundle_s3_bucket, var.defaults.ca_certificates_bundle_s3_bucket, null) + ca_certificates_bundle_s3_key = try(each.value.ca_certificates_bundle_s3_key, var.defaults.ca_certificates_bundle_s3_key, null) + ca_certificates_bundle_s3_object_version = try(each.value.ca_certificates_bundle_s3_object_version, var.defaults.ca_certificates_bundle_s3_object_version, null) + create = try(each.value.create, var.defaults.create, true) + create_trust_store_revocation = try(each.value.create_trust_store_revocation, var.defaults.create_trust_store_revocation, false) + name = try(each.value.name, var.defaults.name, null) + name_prefix = try(each.value.name_prefix, var.defaults.name_prefix, null) + revocation_lists = try(each.value.revocation_lists, var.defaults.revocation_lists, {}) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/wrappers/lb_trust_store/outputs.tf b/wrappers/lb_trust_store/outputs.tf new file mode 100644 index 00000000..ec6da5f4 --- /dev/null +++ b/wrappers/lb_trust_store/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/lb_trust_store/variables.tf b/wrappers/lb_trust_store/variables.tf new file mode 100644 index 00000000..a6ea0962 --- /dev/null +++ b/wrappers/lb_trust_store/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/lb_trust_store/versions.tf b/wrappers/lb_trust_store/versions.tf new file mode 100644 index 00000000..51cad108 --- /dev/null +++ b/wrappers/lb_trust_store/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} From 40d92a8b56dd308516066db834105c5ee8465327 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 19 Jan 2024 14:10:58 +0000 Subject: [PATCH 24/28] chore(release): version 9.5.0 [skip ci] ## [9.5.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.1...v9.5.0) (2024-01-19) ### Features * Added support for ALB trust store ([#344](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/344)) ([9f03c7a](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/9f03c7ade3de494363f9d5b3fb17932b53d3f426)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7874c76e..1f97f0a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.5.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.1...v9.5.0) (2024-01-19) + + +### Features + +* Added support for ALB trust store ([#344](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/344)) ([9f03c7a](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/9f03c7ade3de494363f9d5b3fb17932b53d3f426)) + ### [9.4.1](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.0...v9.4.1) (2024-01-12) From 1e3d5fb89c419c9b56c4728e935278e79b4527cd Mon Sep 17 00:00:00 2001 From: KofoworolaOgunleye Date: Wed, 7 Feb 2024 14:59:04 +0000 Subject: [PATCH 25/28] feat: Add support for `load_balancing_anomaly_mitigation` (#349) --- examples/complete-alb/main.tf | 2 ++ main.tf | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 960082ab..27e47cf3 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -359,6 +359,8 @@ module "alb" { port = 80 target_type = "instance" deregistration_delay = 10 + load_balancing_algorithm_type = "weighted_random" + load_balancing_anomaly_mitigation = "on" load_balancing_cross_zone_enabled = false health_check = { diff --git a/main.tf b/main.tf index 9dca1efd..144437ec 100644 --- a/main.tf +++ b/main.tf @@ -478,6 +478,7 @@ resource "aws_lb_target_group" "this" { ip_address_type = try(each.value.ip_address_type, null) lambda_multi_value_headers_enabled = try(each.value.lambda_multi_value_headers_enabled, null) load_balancing_algorithm_type = try(each.value.load_balancing_algorithm_type, null) + load_balancing_anomaly_mitigation = try(each.value.load_balancing_anomaly_mitigation, null) load_balancing_cross_zone_enabled = try(each.value.load_balancing_cross_zone_enabled, null) name = try(each.value.name, null) name_prefix = try(each.value.name_prefix, null) From 3e9c6cbaf4c1d858c3bbee6f086f0c8ef17522ab Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 7 Feb 2024 14:59:27 +0000 Subject: [PATCH 26/28] chore(release): version 9.6.0 [skip ci] ## [9.6.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.5.0...v9.6.0) (2024-02-07) ### Features * Add support for `load_balancing_anomaly_mitigation` ([#349](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/349)) ([1e3d5fb](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/1e3d5fb89c419c9b56c4728e935278e79b4527cd)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f97f0a5..2c4e8faa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.6.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.5.0...v9.6.0) (2024-02-07) + + +### Features + +* Add support for `load_balancing_anomaly_mitigation` ([#349](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/349)) ([1e3d5fb](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/1e3d5fb89c419c9b56c4728e935278e79b4527cd)) + ## [9.5.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.4.1...v9.5.0) (2024-01-19) From b53ce700f487884c8add1618993f3843aa458c10 Mon Sep 17 00:00:00 2001 From: Joshua Sizer Date: Sun, 11 Feb 2024 09:21:20 -0500 Subject: [PATCH 27/28] feat: Additional target group attachments (#351) Co-authored-by: Bryant Biggs --- .pre-commit-config.yaml | 3 ++- README.md | 2 ++ UPGRADE-5.0.md | 6 +++--- examples/complete-alb/README.md | 1 + examples/complete-alb/main.tf | 15 +++++++++++++++ main.tf | 11 +++++++++++ variables.tf | 6 ++++++ wrappers/main.tf | 1 + 8 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e809a4e4..b1bc94eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.83.5 + rev: v1.86.0 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each @@ -28,3 +28,4 @@ repos: hooks: - id: check-merge-conflict - id: end-of-file-fixer + - id: trailing-whitespace diff --git a/README.md b/README.md index 176477ae..a6649de5 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,7 @@ No modules. | [aws_lb_listener_certificate.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_certificate) | resource | | [aws_lb_listener_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener_rule) | resource | | [aws_lb_target_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group) | resource | +| [aws_lb_target_group_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource | | [aws_lb_target_group_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group_attachment) | resource | | [aws_route53_record.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | @@ -384,6 +385,7 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [access\_logs](#input\_access\_logs) | Map containing access logging configuration for load balancer | `map(string)` | `{}` | no | +| [additional\_target\_group\_attachments](#input\_additional\_target\_group\_attachments) | Map of additional target group attachments to create. Use `target_group_key` to attach to the target group created in `target_groups` | `any` | `{}` | no | | [associate\_web\_acl](#input\_associate\_web\_acl) | Indicates whether a Web Application Firewall (WAF) ACL should be associated with the load balancer | `bool` | `false` | no | | [connection\_logs](#input\_connection\_logs) | Map containing access logging configuration for load balancer | `map(string)` | `{}` | no | | [create](#input\_create) | Controls if resources should be created (affects nearly all resources) | `bool` | `true` | no | diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 7ce0dcff..75955336 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -18,9 +18,9 @@ If you found a bug, please open an issue in this repository. - aws_lb_listener.frontend_http_tcp_no_logs - aws_lb_listener.frontend_https_no_logs - aws_lb_listener_certificate.https_listener_no_logs - + If you've been using ALB without access logs enabled then you need to run `terraform state mv` to rename resources to new names: - + - aws_lb.this - aws_lb_target_group.main - aws_lb_listener.frontend_http_tcp @@ -28,7 +28,7 @@ If you found a bug, please open an issue in this repository. - aws_lb_listener_certificate.https_listener For example, this command will rename ALB resource: `terraform state mv aws_lb.application_no_logs aws_lb.this` - + 2. Removed variable `target_groups_count`, `http_tcp_listeners_count`, `extra_ssl_certs_count`, `http_tcp_listeners_count`. 3. Removed variable `target_groups_defaults`. Instead, all `health_check` and `stickiness` settings should be implicit for each target group. diff --git a/examples/complete-alb/README.md b/examples/complete-alb/README.md index 8845abe7..48b80e39 100644 --- a/examples/complete-alb/README.md +++ b/examples/complete-alb/README.md @@ -50,6 +50,7 @@ Note that this example may create resources which cost money. Run `terraform des | [aws_cognito_user_pool.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool) | resource | | [aws_cognito_user_pool_client.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_client) | resource | | [aws_cognito_user_pool_domain.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cognito_user_pool_domain) | resource | +| [aws_instance.other](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [null_resource.download_package](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource | | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | diff --git a/examples/complete-alb/main.tf b/examples/complete-alb/main.tf index 27e47cf3..169f8f86 100644 --- a/examples/complete-alb/main.tf +++ b/examples/complete-alb/main.tf @@ -398,6 +398,15 @@ module "alb" { } } + additional_target_group_attachments = { + ex-instance-other = { + target_group_key = "ex-instance" + target_type = "instance" + target_id = aws_instance.other.id + port = "80" + } + } + # Route53 Record(s) route53_records = { A = { @@ -530,6 +539,12 @@ resource "aws_instance" "this" { subnet_id = element(module.vpc.private_subnets, 0) } +resource "aws_instance" "other" { + ami = data.aws_ssm_parameter.al2.value + instance_type = "t3.nano" + subnet_id = element(module.vpc.private_subnets, 0) +} + ################################################################## # AWS Cognito User Pool ################################################################## diff --git a/main.tf b/main.tf index 144437ec..a18b5ad3 100644 --- a/main.tf +++ b/main.tf @@ -541,6 +541,17 @@ resource "aws_lb_target_group_attachment" "this" { depends_on = [aws_lambda_permission.this] } +resource "aws_lb_target_group_attachment" "additional" { + for_each = { for k, v in var.additional_target_group_attachments : k => v if local.create } + + target_group_arn = aws_lb_target_group.this[each.value.target_group_key].arn + target_id = each.value.target_id + port = try(each.value.target_type, null) == "lambda" ? null : try(each.value.port, var.default_port) + availability_zone = try(each.value.availability_zone, null) + + depends_on = [aws_lambda_permission.this] +} + ################################################################################ # Lambda Permission ################################################################################ diff --git a/variables.tf b/variables.tf index 9cf497d5..07c9403e 100644 --- a/variables.tf +++ b/variables.tf @@ -196,6 +196,12 @@ variable "target_groups" { default = {} } +variable "additional_target_group_attachments" { + description = "Map of additional target group attachments to create. Use `target_group_key` to attach to the target group created in `target_groups`" + type = any + default = {} +} + ################################################################################ # Security Group ################################################################################ diff --git a/wrappers/main.tf b/wrappers/main.tf index 34342212..f277c221 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -4,6 +4,7 @@ module "wrapper" { for_each = var.items access_logs = try(each.value.access_logs, var.defaults.access_logs, {}) + additional_target_group_attachments = try(each.value.additional_target_group_attachments, var.defaults.additional_target_group_attachments, {}) associate_web_acl = try(each.value.associate_web_acl, var.defaults.associate_web_acl, false) connection_logs = try(each.value.connection_logs, var.defaults.connection_logs, {}) create = try(each.value.create, var.defaults.create, true) From 9f1ee3d9a66a761d411acb9cac7e11a26c117d3b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 11 Feb 2024 14:21:45 +0000 Subject: [PATCH 28/28] chore(release): version 9.7.0 [skip ci] ## [9.7.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.6.0...v9.7.0) (2024-02-11) ### Features * Additional target group attachments ([#351](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/351)) ([b53ce70](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b53ce700f487884c8add1618993f3843aa458c10)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c4e8faa..f04ad035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [9.7.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.6.0...v9.7.0) (2024-02-11) + + +### Features + +* Additional target group attachments ([#351](https://github.com/terraform-aws-modules/terraform-aws-alb/issues/351)) ([b53ce70](https://github.com/terraform-aws-modules/terraform-aws-alb/commit/b53ce700f487884c8add1618993f3843aa458c10)) + ## [9.6.0](https://github.com/terraform-aws-modules/terraform-aws-alb/compare/v9.5.0...v9.6.0) (2024-02-07)