diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7eadbf5..8bd123e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -8,7 +8,7 @@ on: env: TERRAFORM_DOCS_VERSION: v0.16.0 - HCLEDIT_VERSION: 0.2.3 + HCLEDIT_VERSION: 0.2.6 jobs: collectInputs: @@ -41,7 +41,7 @@ jobs: - name: Terraform min/max versions id: minMax - uses: clowdhaus/terraform-min-max@v1.0.8 + uses: clowdhaus/terraform-min-max@v1.2.3 with: directory: ${{ matrix.directory }} @@ -81,7 +81,7 @@ jobs: - name: Terraform min/max versions id: minMax - uses: clowdhaus/terraform-min-max@v1.0.8 + uses: clowdhaus/terraform-min-max@v1.2.3 - name: Pre-commit Terraform ${{ steps.minMax.outputs.maxVersion }} uses: ./pre-commit diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3ff80e..74f3751 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.72.1 + rev: v1.76.0 hooks: - id: terraform_fmt - id: terraform_validate @@ -23,7 +23,7 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-merge-conflict - id: end-of-file-fixer diff --git a/directories/action.yml b/directories/action.yml index 751662e..81ad64f 100644 --- a/directories/action.yml +++ b/directories/action.yml @@ -12,5 +12,6 @@ runs: shell: bash id: search run: | - DIRS=$(python -c "import json; import glob; import re; print(json.dumps([x.replace('/versions.tf', '') for x in glob.glob('./**/versions.tf', recursive=True) if not re.match(r'^.+/_', x)]))") + ls -la + DIRS=$(python ./directories/directories.py) echo "directories=$DIRS" >> $GITHUB_OUTPUT diff --git a/directories/directories.py b/directories/directories.py new file mode 100644 index 0000000..0c2237c --- /dev/null +++ b/directories/directories.py @@ -0,0 +1,30 @@ +import json +import mmap +import os +import re +from glob import glob + + +def get_directories(): + """ + Return all Terraform (*.tf) files that contain the `required_version` + string, but does not contain `wrapper` in the path. + """ + + terraform_files = glob('./**/*.tf', recursive=True) + directories = [] + + for file in terraform_files: + file_size = os.stat(file) + + if file_size.st_size > 0: + with open(file, 'rb', 0) as rfile: + contents = mmap.mmap(rfile.fileno(), 0, access=mmap.ACCESS_READ) + if contents.find(b'required_version') != -1 and 'wrapper' not in file: + directories.append(file) + + return json.dumps([x.replace('/versions.tf', '') for x in directories if not re.match(r'^.+/_', x)]) + + +if __name__ == '__main__': + print(get_directories()) diff --git a/examples/0.13/main.tf b/examples/0.13/main.tf new file mode 100644 index 0000000..066066c --- /dev/null +++ b/examples/0.13/main.tf @@ -0,0 +1 @@ +locals {} diff --git a/examples/test/outputs.tf b/examples/0.13/outputs.tf similarity index 100% rename from examples/test/outputs.tf rename to examples/0.13/outputs.tf diff --git a/examples/test/variables.tf b/examples/0.13/variables.tf similarity index 100% rename from examples/test/variables.tf rename to examples/0.13/variables.tf diff --git a/examples/0.13/versions.tf b/examples/0.13/versions.tf new file mode 100644 index 0000000..8a73942 --- /dev/null +++ b/examples/0.13/versions.tf @@ -0,0 +1,14 @@ +provider "aws" { + region = "us-east-1" +} + +terraform { + required_version = ">= 0.14" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.0" + } + } +} diff --git a/examples/0.14/main.tf b/examples/0.14/main.tf new file mode 100644 index 0000000..066066c --- /dev/null +++ b/examples/0.14/main.tf @@ -0,0 +1 @@ +locals {} diff --git a/examples/0.14/outputs.tf b/examples/0.14/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/0.14/variables.tf b/examples/0.14/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/0.14/versions.tf b/examples/0.14/versions.tf new file mode 100644 index 0000000..322a5df --- /dev/null +++ b/examples/0.14/versions.tf @@ -0,0 +1,14 @@ +provider "aws" { + region = "us-east-1" +} + +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 2.0" + } + } +} diff --git a/examples/terraform-aws-alb/examples/complete-alb/main.tf b/examples/terraform-aws-alb/examples/complete-alb/main.tf new file mode 100644 index 0000000..d7b2317 --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-alb/main.tf @@ -0,0 +1,565 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + domain_name = "terraform-aws-modules.modules.tf" +} + +################################################################## +# Data sources to get VPC and subnets +################################################################## +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "all" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +resource "random_pet" "this" { + length = 2 +} + +data "aws_route53_zone" "this" { + name = local.domain_name +} + +module "security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "alb-sg-${random_pet.this.id}" + description = "Security group for example usage with ALB" + vpc_id = data.aws_vpc.default.id + + ingress_cidr_blocks = ["0.0.0.0/0"] + ingress_rules = ["http-80-tcp", "all-icmp"] + egress_rules = ["all-all"] +} + +#module "log_bucket" { +# source = "terraform-aws-modules/s3-bucket/aws" +# version = "~> 3.0" +# +# bucket = "logs-${random_pet.this.id}" +# acl = "log-delivery-write" +# force_destroy = true +# attach_elb_log_delivery_policy = true +#} + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 3.0" + + domain_name = local.domain_name # trimsuffix(data.aws_route53_zone.this.name, ".") + zone_id = data.aws_route53_zone.this.id +} + +module "wildcard_cert" { + source = "terraform-aws-modules/acm/aws" + version = "~> 3.0" + + domain_name = "*.${local.domain_name}" # trimsuffix(data.aws_route53_zone.this.name, ".") + zone_id = data.aws_route53_zone.this.id +} + +################################################################## +# AWS Cognito User Pool +################################################################## +resource "aws_cognito_user_pool" "this" { + name = "user-pool-${random_pet.this.id}" +} + +resource "aws_cognito_user_pool_client" "this" { + name = "user-pool-client-${random_pet.this.id}" + user_pool_id = aws_cognito_user_pool.this.id + generate_secret = true + allowed_oauth_flows = ["code", "implicit"] + callback_urls = ["https://${local.domain_name}/callback"] + allowed_oauth_scopes = ["email", "openid"] + allowed_oauth_flows_user_pool_client = true +} + +resource "aws_cognito_user_pool_domain" "this" { + domain = random_pet.this.id + user_pool_id = aws_cognito_user_pool.this.id +} + +################################################################## +# Application Load Balancer +################################################################## +module "alb" { + source = "../../" + + name = "complete-alb-${random_pet.this.id}" + + load_balancer_type = "application" + + vpc_id = data.aws_vpc.default.id + security_groups = [module.security_group.security_group_id] + subnets = data.aws_subnets.all.ids + + # # 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 = "redirect" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + }, + { + port = 82 + 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 + }, + # 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 + authenticate_cognito = { + authentication_request_extra_params = { + display = "page" + prompt = "login" + } + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${random_pet.this.id}" + 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://${local.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${local.domain_name}" + token_endpoint = "https://${local.domain_name}/token" + user_info_endpoint = "https://${local.domain_name}/user_info" + } + }, + ] + + extra_ssl_certs = [ + { + https_listener_index = 0 + certificate_arn = module.wildcard_cert.acm_certificate_arn + } + ] + + https_listener_rules = [ + { + https_listener_index = 0 + + actions = [ + { + type = "authenticate-cognito" + + on_unauthenticated_request = "authenticate" + session_cookie_name = "session-${random_pet.this.id}" + 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://${local.domain_name}/auth" + client_id = "client_id" + client_secret = "client_secret" + issuer = "https://${local.domain_name}" + token_endpoint = "https://${local.domain_name}/token" + user_info_endpoint = "https://${local.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 = [ + { + name_prefix = "h1" + backend_protocol = "HTTP" + backend_port = 80 + target_type = "instance" + deregistration_delay = 10 + 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 + }, + my_ec2_again = { + target_id = aws_instance.this.id + port = 8080 + } + } + tags = { + InstanceTargetGroupTag = "baz" + } + }, + { + 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" + } + + https_listener_rules_tags = { + MyLoadBalancerHTTPSListenerRule = "bar" + } + + https_listeners_tags = { + MyLoadBalancerHTTPSListener = "bar" + } + + http_tcp_listeners_tags = { + MyLoadBalancerTCPListener = "bar" + } +} + +######################### +# LB will not be created +######################### +module "lb_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" +} + +############################################# +# 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" + downloaded = "downloaded_package_${md5(local.package_url)}.zip" +} + +resource "null_resource" "download_package" { + triggers = { + downloaded = local.downloaded + } + + provisioner "local-exec" { + command = "curl -L -o ${local.downloaded} ${local.package_url}" + } +} + +module "lambda_with_allowed_triggers" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 3.0" + + function_name = "${random_pet.this.id}-with-allowed-triggers" + description = "My awesome lambda function (with allowed triggers)" + handler = "index.lambda_handler" + runtime = "python3.8" + + 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 + } + } + + depends_on = [null_resource.download_package] +} + +module "lambda_without_allowed_triggers" { + source = "terraform-aws-modules/lambda/aws" + version = "~> 3.0" + + function_name = "${random_pet.this.id}-without-allowed-triggers" + description = "My awesome lambda function (without allowed triggers)" + handler = "index.lambda_handler" + runtime = "python3.8" + + publish = true + + create_package = false + local_existing_package = local.downloaded + + # Allowed triggers will be managed by ALB module + allowed_triggers = {} + + depends_on = [null_resource.download_package] +} diff --git a/examples/terraform-aws-alb/examples/complete-alb/outputs.tf b/examples/terraform-aws-alb/examples/complete-alb/outputs.tf new file mode 100644 index 0000000..864b493 --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-alb/outputs.tf @@ -0,0 +1,64 @@ +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 +} + +output "lb_arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch." + value = module.alb.lb_arn_suffix +} + +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 "http_tcp_listener_arns" { + description = "The ARN of the TCP and HTTP load balancer listeners created." + value = module.alb.http_tcp_listener_arns +} + +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 "https_listener_arns" { + description = "The ARNs of the HTTPS load balancer listeners created." + value = module.alb.https_listener_arns +} + +output "https_listener_ids" { + description = "The IDs of the load balancer listeners created." + value = module.alb.https_listener_ids +} + +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 "target_group_arn_suffixes" { + description = "ARN suffixes of our target groups - can be used with CloudWatch." + value = module.alb.target_group_arn_suffixes +} + +output "target_group_names" { + description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group." + value = module.alb.target_group_names +} + +output "target_group_attachments" { + description = "ARNs of the target group attachment IDs." + value = module.alb.target_group_attachments +} diff --git a/examples/terraform-aws-alb/examples/complete-alb/variables.tf b/examples/terraform-aws-alb/examples/complete-alb/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/terraform-aws-alb/examples/complete-alb/versions.tf b/examples/terraform-aws-alb/examples/complete-alb/versions.tf new file mode 100644 index 0000000..e5fe0c8 --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-alb/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.27" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + null = { + source = "hashicorp/null" + version = ">= 2.0" + } + } +} diff --git a/examples/terraform-aws-alb/examples/complete-nlb/main.tf b/examples/terraform-aws-alb/examples/complete-nlb/main.tf new file mode 100644 index 0000000..e88059f --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-nlb/main.tf @@ -0,0 +1,153 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + domain_name = "terraform-aws-modules.modules.tf" +} + +################################################################## +# Data sources to get VPC and subnets +################################################################## +data "aws_vpc" "default" { + default = true +} + +data "aws_subnets" "all" { + filter { + name = "vpc-id" + values = [data.aws_vpc.default.id] + } +} + +resource "random_pet" "this" { + length = 2 +} + +data "aws_route53_zone" "this" { + name = local.domain_name +} + +# module "log_bucket" { +# source = "terraform-aws-modules/s3-bucket/aws" +# version = "~> 3.0" +# +# bucket = "logs-${random_pet.this.id}" +# acl = "log-delivery-write" +# force_destroy = true +# attach_elb_log_delivery_policy = true +# } + +module "acm" { + source = "terraform-aws-modules/acm/aws" + version = "~> 3.0" + + domain_name = local.domain_name # trimsuffix(data.aws_route53_zone.this.name, ".") + zone_id = data.aws_route53_zone.this.id +} + +resource "aws_eip" "this" { + count = length(data.aws_subnets.all.ids) + + vpc = true +} + +################################################################## +# Network Load Balancer with Elastic IPs attached +################################################################## +module "nlb" { + source = "../../" + + name = "complete-nlb-${random_pet.this.id}" + + load_balancer_type = "network" + + vpc_id = data.aws_vpc.default.id + + # Use `subnets` if you don't want to attach EIPs + # subnets = tolist(data.aws_subnet_ids.all.ids) + + # Use `subnet_mapping` to attach EIPs + subnet_mapping = [for i, eip in aws_eip.this : { allocation_id : eip.id, subnet_id : tolist(data.aws_subnets.all.ids)[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 + }, + { + port = 82 + protocol = "UDP" + target_group_index = 1 + }, + { + port = 83 + protocol = "TCP" + target_group_index = 2 + }, + ] + + # TLS + https_listeners = [ + { + port = 84 + protocol = "TLS" + certificate_arn = module.acm.acm_certificate_arn + target_group_index = 3 + }, + ] + + target_groups = [ + { + name_prefix = "tu1-" + backend_protocol = "TCP_UDP" + backend_port = 81 + target_type = "instance" + connection_termination = true + preserve_client_ip = true + stickiness = { + enabled = true + 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 + target_type = "ip" + deregistration_delay = 10 + health_check = { + enabled = true + interval = 30 + path = "/healthz" + port = "traffic-port" + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 6 + } + }, + { + name_prefix = "t2-" + backend_protocol = "TLS" + backend_port = 84 + target_type = "instance" + }, + ] +} diff --git a/examples/terraform-aws-alb/examples/complete-nlb/outputs.tf b/examples/terraform-aws-alb/examples/complete-nlb/outputs.tf new file mode 100644 index 0000000..40f57a9 --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-nlb/outputs.tf @@ -0,0 +1,59 @@ +output "lb_id" { + description = "The ID and ARN of the load balancer we created." + value = module.nlb.lb_id +} + +output "lb_arn" { + description = "The ID and ARN of the load balancer we created." + value = module.nlb.lb_arn +} + +output "lb_dns_name" { + description = "The DNS name of the load balancer." + value = module.nlb.lb_dns_name +} + +output "lb_arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch." + value = module.nlb.lb_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 "http_tcp_listener_arns" { + description = "The ARN of the TCP and HTTP load balancer listeners created." + value = module.nlb.http_tcp_listener_arns +} + +output "http_tcp_listener_ids" { + description = "The IDs of the TCP and HTTP load balancer listeners created." + value = module.nlb.http_tcp_listener_ids +} + +output "https_listener_arns" { + description = "The ARNs of the HTTPS load balancer listeners created." + value = module.nlb.https_listener_arns +} + +output "https_listener_ids" { + description = "The IDs of the load balancer listeners created." + value = module.nlb.https_listener_ids +} + +output "target_group_arns" { + description = "ARNs of the target groups. Useful for passing to your Auto Scaling group." + value = module.nlb.target_group_arns +} + +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 "target_group_names" { + description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group." + value = module.nlb.target_group_names +} diff --git a/examples/terraform-aws-alb/examples/complete-nlb/variables.tf b/examples/terraform-aws-alb/examples/complete-nlb/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/terraform-aws-alb/examples/complete-nlb/versions.tf b/examples/terraform-aws-alb/examples/complete-nlb/versions.tf new file mode 100644 index 0000000..021d385 --- /dev/null +++ b/examples/terraform-aws-alb/examples/complete-nlb/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.27" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/examples/terraform-aws-alb/main.tf b/examples/terraform-aws-alb/main.tf new file mode 100644 index 0000000..de2e061 --- /dev/null +++ b/examples/terraform-aws-alb/main.tf @@ -0,0 +1,765 @@ +locals { + create_lb = var.create_lb && var.putin_khuylo +} + +resource "aws_lb" "this" { + count = local.create_lb ? 1 : 0 + + name = var.name + name_prefix = var.name_prefix + + load_balancer_type = var.load_balancer_type + internal = var.internal + security_groups = var.security_groups + subnets = var.subnets + + idle_timeout = var.idle_timeout + enable_cross_zone_load_balancing = var.enable_cross_zone_load_balancing + enable_deletion_protection = var.enable_deletion_protection + enable_http2 = var.enable_http2 + ip_address_type = var.ip_address_type + drop_invalid_header_fields = var.drop_invalid_header_fields + enable_waf_fail_open = var.enable_waf_fail_open + desync_mitigation_mode = var.desync_mitigation_mode + + dynamic "access_logs" { + for_each = length(keys(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) + } + } + + 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) + } + } + + tags = merge( + { + Name = (var.name != null) ? var.name : var.name_prefix + }, + var.tags, + var.lb_tags, + ) + + timeouts { + create = var.load_balancer_create_timeout + update = var.load_balancer_update_timeout + delete = var.load_balancer_delete_timeout + } +} + +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 = lookup(var.target_groups[count.index], "backend_port", null) + protocol = lookup(var.target_groups[count.index], "backend_protocol", null) != null ? upper(lookup(var.target_groups[count.index], "backend_protocol")) : null + protocol_version = lookup(var.target_groups[count.index], "protocol_version", null) != null ? upper(lookup(var.target_groups[count.index], "protocol_version")) : null + target_type = lookup(var.target_groups[count.index], "target_type", null) + + connection_termination = lookup(var.target_groups[count.index], "connection_termination", null) + deregistration_delay = lookup(var.target_groups[count.index], "deregistration_delay", null) + slow_start = lookup(var.target_groups[count.index], "slow_start", null) + proxy_protocol_v2 = lookup(var.target_groups[count.index], "proxy_protocol_v2", false) + lambda_multi_value_headers_enabled = lookup(var.target_groups[count.index], "lambda_multi_value_headers_enabled", false) + load_balancing_algorithm_type = lookup(var.target_groups[count.index], "load_balancing_algorithm_type", null) + preserve_client_ip = lookup(var.target_groups[count.index], "preserve_client_ip", null) + ip_address_type = lookup(var.target_groups[count.index], "ip_address_type", null) + + dynamic "health_check" { + for_each = length(keys(lookup(var.target_groups[count.index], "health_check", {}))) == 0 ? [] : [lookup(var.target_groups[count.index], "health_check", {})] + + content { + enabled = lookup(health_check.value, "enabled", null) + interval = lookup(health_check.value, "interval", null) + path = lookup(health_check.value, "path", null) + port = lookup(health_check.value, "port", null) + healthy_threshold = lookup(health_check.value, "healthy_threshold", null) + unhealthy_threshold = lookup(health_check.value, "unhealthy_threshold", null) + timeout = lookup(health_check.value, "timeout", null) + protocol = lookup(health_check.value, "protocol", null) + matcher = lookup(health_check.value, "matcher", null) + } + } + + dynamic "stickiness" { + for_each = length(keys(lookup(var.target_groups[count.index], "stickiness", {}))) == 0 ? [] : [lookup(var.target_groups[count.index], "stickiness", {})] + + content { + enabled = lookup(stickiness.value, "enabled", null) + cookie_duration = lookup(stickiness.value, "cookie_duration", null) + type = lookup(stickiness.value, "type", null) + cookie_name = lookup(stickiness.value, "cookie_name", null) + } + } + + tags = merge( + var.tags, + var.target_group_tags, + lookup(var.target_groups[count.index], "tags", {}), + { + "Name" = lookup(var.target_groups[count.index], "name", lookup(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 + 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] +} + +resource "aws_lb_listener_rule" "https_listener_rule" { + count = local.create_lb ? length(var.https_listener_rules) : 0 + + 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) + + # 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" + ] + + 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"] + } + } + } + + # 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" + ] + + 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"] + } + } + } + + # 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"] + } + } + } + + # 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" + ] + + 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"] + } + } + } + + # forward actions + dynamic "action" { + for_each = [ + for action_rule in var.https_listener_rules[count.index].actions : + action_rule + if action_rule.type == "forward" + ] + + content { + type = action.value["type"] + target_group_arn = aws_lb_target_group.main[lookup(action.value, "target_group_index", count.index)].id + } + } + + # 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" + ] + + content { + type = "forward" + forward { + dynamic "target_group" { + for_each = action.value["target_groups"] + + content { + arn = aws_lb_target_group.main[target_group.value["target_group_index"]].id + weight = target_group.value["weight"] + } + } + dynamic "stickiness" { + for_each = [lookup(action.value, "stickiness", {})] + + content { + enabled = try(stickiness.value["enabled"], false) + duration = try(stickiness.value["duration"], 1) + } + } + } + } + } + + # 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"] + } + } + } + + # 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 + ] + + content { + host_header { + values = condition.value["host_headers"] + } + } + } + + # 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"] + + content { + 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.https_listener_rules[count.index].conditions : + condition_rule + if length(lookup(condition_rule, "http_request_methods", [])) > 0 + ] + + content { + http_request_method { + values = condition.value["http_request_methods"] + } + } + } + + # 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 + ] + + content { + dynamic "query_string" { + for_each = condition.value["query_strings"] + + content { + key = lookup(query_string.value, "key", null) + value = query_string.value["value"] + } + } + } + } + + # 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 + ] + + content { + source_ip { + values = condition.value["source_ips"] + } + } + } + + 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" + ] + + 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"] + } + } + } + + # 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" + ] + + 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"] + } + } + } + + # 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" + ] + + content { + type = action.value["type"] + target_group_arn = aws_lb_target_group.main[lookup(action.value, "target_group_index", count.index)].id + } + } + + # 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" + ] + + content { + type = "forward" + forward { + dynamic "target_group" { + for_each = action.value["target_groups"] + + content { + arn = aws_lb_target_group.main[target_group.value["target_group_index"]].id + weight = target_group.value["weight"] + } + } + dynamic "stickiness" { + for_each = [lookup(action.value, "stickiness", {})] + + content { + enabled = try(stickiness.value["enabled"], false) + duration = try(stickiness.value["duration"], 1) + } + } + } + } + } + + # 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 + ] + + content { + path_pattern { + values = condition.value["path_patterns"] + } + } + } + + # 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"] + } + } + } + + # 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"] + + content { + 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 + ] + + content { + http_request_method { + values = condition.value["http_request_methods"] + } + } + } + + # 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 + ] + + content { + dynamic "query_string" { + for_each = condition.value["query_strings"] + + content { + key = lookup(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 + ] + + content { + source_ip { + values = condition.value["source_ips"] + } + } + } + + tags = merge( + var.tags, + var.http_tcp_listener_rules_tags, + lookup(var.http_tcp_listener_rules[count.index], "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", {})] + + 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"] + } + } + + dynamic "fixed_response" { + for_each = length(keys(lookup(default_action.value, "fixed_response", {}))) == 0 ? [] : [lookup(default_action.value, "fixed_response", {})] + + 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) + } + } + } + } + + tags = merge( + var.tags, + var.http_tcp_listeners_tags, + lookup(var.http_tcp_listeners[count.index], "tags", {}), + ) +} + +resource "aws_lb_listener" "frontend_https" { + count = local.create_lb ? length(var.https_listeners) : 0 + + load_balancer_arn = aws_lb.this[0].arn + + 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) + + dynamic "default_action" { + for_each = length(keys(var.https_listeners[count.index])) == 0 ? [] : [var.https_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", {})] + + 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"] + } + } + + dynamic "fixed_response" { + for_each = length(keys(lookup(default_action.value, "fixed_response", {}))) == 0 ? [] : [lookup(default_action.value, "fixed_response", {})] + + 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) + } + } + + # 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", {})] + + 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"] + } + } + + dynamic "authenticate_oidc" { + for_each = length(keys(lookup(default_action.value, "authenticate_oidc", {}))) == 0 ? [] : [lookup(default_action.value, "authenticate_oidc", {})] + + 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"] + } + } + } + } + + 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 + } + } + + 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 + + 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"] +} diff --git a/examples/terraform-aws-alb/outputs.tf b/examples/terraform-aws-alb/outputs.tf new file mode 100644 index 0000000..32c9b6f --- /dev/null +++ b/examples/terraform-aws-alb/outputs.tf @@ -0,0 +1,64 @@ +output "lb_id" { + description = "The ID and ARN of the load balancer we created" + value = try(aws_lb.this[0].id, "") +} + +output "lb_arn" { + description = "The ID and ARN of the load balancer we created" + value = try(aws_lb.this[0].arn, "") +} + +output "lb_dns_name" { + description = "The DNS name of the load balancer" + value = try(aws_lb.this[0].dns_name, "") +} + +output "lb_arn_suffix" { + description = "ARN suffix of our load balancer - can be used with CloudWatch" + value = try(aws_lb.this[0].arn_suffix, "") +} + +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 "https_listener_arns" { + description = "The ARNs of the HTTPS load balancer listeners created" + value = aws_lb_listener.frontend_https[*].arn +} + +output "https_listener_ids" { + description = "The IDs of the load balancer listeners created" + value = aws_lb_listener.frontend_https[*].id +} + +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 "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 "target_group_names" { + description = "Name of the target group. Useful for passing to your CodeDeploy Deployment Group" + value = aws_lb_target_group.main[*].name +} + +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 } +} diff --git a/examples/terraform-aws-alb/variables.tf b/examples/terraform-aws-alb/variables.tf new file mode 100644 index 0000000..5ec54b2 --- /dev/null +++ b/examples/terraform-aws-alb/variables.tf @@ -0,0 +1,215 @@ +variable "create_lb" { + description = "Controls if the Load Balancer should be created" + 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 "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 "enable_http2" { + description = "Indicates whether HTTP/2 is enabled in application load balancers." + type = bool + default = true +} + +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 "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 "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 "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 "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 "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 "idle_timeout" { + description = "The time in seconds that the connection is allowed to be idle." + 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" +} + +variable "internal" { + description = "Boolean determining if the load balancer is internal or externally facing." + type = bool + default = false +} + +variable "load_balancer_create_timeout" { + description = "Timeout value when creating the ALB." + type = string + default = "10m" +} + +variable "load_balancer_delete_timeout" { + description = "Timeout value when deleting the ALB." + type = string + default = "10m" +} + +variable "name" { + description = "The resource name and Name tag of the load balancer." + 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" + 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 "subnets" { + description = "A list of subnets to associate with the load balancer. e.g. ['subnet-1a2b3c4d','subnet-1a2b3c4e','subnet-1a2b3c4f']" + type = list(string) + default = null +} + +variable "subnet_mapping" { + description = "A list of subnet mapping blocks describing subnets to attach to network load balancer" + type = list(map(string)) + default = [] +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +variable "lb_tags" { + description = "A map of tags to add to load balancer" + type = map(string) + default = {} +} + +variable "target_group_tags" { + description = "A map of tags to add to all target groups" + 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 = {} +} + +variable "http_tcp_listener_rules_tags" { + description = "A map of tags to add to all http listener rules" + type = map(string) + default = {} +} + +variable "https_listeners_tags" { + description = "A map of tags to add to all https listeners" + type = map(string) + default = {} +} + +variable "http_tcp_listeners_tags" { + description = "A map of tags to add to all http listeners" + type = map(string) + default = {} +} + +variable "security_groups" { + description = "The security groups to attach to the load balancer. e.g. [\"sg-edcd9784\",\"sg-edcd9785\"]" + type = list(string) + default = [] +} + +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" + 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 "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/examples/test/versions.tf b/examples/terraform-aws-alb/versions.tf similarity index 60% rename from examples/test/versions.tf rename to examples/terraform-aws-alb/versions.tf index 457a4cc..f59f81e 100644 --- a/examples/test/versions.tf +++ b/examples/terraform-aws-alb/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.71" + version = ">= 4.27" } } } diff --git a/examples/terraform-aws-alb/wrappers/main.tf b/examples/terraform-aws-alb/wrappers/main.tf new file mode 100644 index 0000000..a05a3cd --- /dev/null +++ b/examples/terraform-aws-alb/wrappers/main.tf @@ -0,0 +1,42 @@ +module "wrapper" { + source = "../" + + 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) + 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) + 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") + 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") + putin_khuylo = try(each.value.putin_khuylo, var.defaults.putin_khuylo, true) +} diff --git a/examples/terraform-aws-alb/wrappers/outputs.tf b/examples/terraform-aws-alb/wrappers/outputs.tf new file mode 100644 index 0000000..5da7c09 --- /dev/null +++ b/examples/terraform-aws-alb/wrappers/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/examples/terraform-aws-alb/wrappers/variables.tf b/examples/terraform-aws-alb/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/examples/terraform-aws-alb/wrappers/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/examples/terraform-aws-alb/wrappers/versions.tf b/examples/terraform-aws-alb/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/examples/terraform-aws-alb/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/examples/test/main.tf b/examples/test/main.tf deleted file mode 100644 index 270eff4..0000000 --- a/examples/test/main.tf +++ /dev/null @@ -1,9 +0,0 @@ -resource "aws_s3_bucket" "example" { - bucket = "my-tf-test-bucket" - acl = "private" - - tags = { - Name = "My bucket" - Environment = "Dev" - } -}