diff --git a/terraform/modules/acm_certificate/data.tf b/terraform/modules/acm_certificate/data.tf index 5c5e3ec3..fbee94a3 100644 --- a/terraform/modules/acm_certificate/data.tf +++ b/terraform/modules/acm_certificate/data.tf @@ -1,5 +1,5 @@ locals { - hosted_zone_base_internal = "${var.platform.env}.${var.platform.app}.cmscloud.internal" + hosted_zone_base_internal = "${var.platform.env}.${var.platform.app}.internal.cms.gov" hosted_zone_base_zscaler = "${var.platform.env}.${var.platform.app}.cmscloud.local" } diff --git a/terraform/modules/acm_certificate/variables.tf b/terraform/modules/acm_certificate/variables.tf index 7a64f5d4..6c93cc29 100644 --- a/terraform/modules/acm_certificate/variables.tf +++ b/terraform/modules/acm_certificate/variables.tf @@ -12,7 +12,7 @@ variable "platform" { } # ------------------------------------------------------- -# Internal endpoint (VPC-only, cmscloud.internal) +# Internal endpoint (VPC-only, <>.internal.cms.gov) # ------------------------------------------------------- variable "enable_internal_endpoint" { type = bool diff --git a/terraform/modules/alb/README.md b/terraform/modules/alb/README.md new file mode 100644 index 00000000..e69de29b diff --git a/terraform/modules/alb/main.tf b/terraform/modules/alb/main.tf new file mode 100644 index 00000000..12e91ccf --- /dev/null +++ b/terraform/modules/alb/main.tf @@ -0,0 +1,80 @@ +locals { + alb_name = var.name_override != null ? var.name_override : "${var.platform.app}-${var.platform.env}-${var.platform.service}-alb" + + # Use explicitly provided subnets, or fall back to the platform's private subnets + subnet_ids = var.subnet_ids != null ? var.subnet_ids : [for s in var.platform.private_subnets : s.id] +} + +# ------------------------------------------------------- +# Application Load Balancer +# ------------------------------------------------------- +resource "aws_lb" "this" { + name = local.alb_name + internal = var.internal + load_balancer_type = "application" + subnets = local.subnet_ids + security_groups = var.security_group_ids + + tags = { Name = local.alb_name } +} + +# ------------------------------------------------------- +# HTTPS Listener (port 443) +# Default action is a 404 fixed response — apps can attach +# their own listener rules with path/host conditions. +# ------------------------------------------------------- +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.this.arn + port = 443 + protocol = "HTTPS" + ssl_policy = var.ssl_policy + certificate_arn = var.acm_certificate_arn + + default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "Not Found" + status_code = "404" + } + } +} + +# ------------------------------------------------------- +# HTTP Listener (port 80) redirect +# ------------------------------------------------------- +resource "aws_lb_listener" "http_redirect" { + count = var.enable_http_redirect ? 1 : 0 + + load_balancer_arn = aws_lb.this.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "redirect" + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } +} + +resource "aws_lb_listener" "extra_https" { + for_each = tomap(var.extra_listeners) + + load_balancer_arn = aws_lb.this.arn + port = each.value.port + protocol = "HTTPS" + ssl_policy = coalesce(each.value.ssl_policy, var.ssl_policy) + certificate_arn = coalesce(each.value.acm_certificate_arn, var.acm_certificate_arn) + + default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "Not Found" + status_code = "404" + } + } +} diff --git a/terraform/modules/alb/outputs.tf b/terraform/modules/alb/outputs.tf new file mode 100644 index 00000000..4c80e702 --- /dev/null +++ b/terraform/modules/alb/outputs.tf @@ -0,0 +1,39 @@ +output "alb_arn" { + description = "ARN of the ALB." + value = aws_lb.this.arn +} + +output "alb_dns_name" { + description = "DNS name of the ALB — use this for Route 53 alias records." + value = aws_lb.this.dns_name +} + +output "alb_zone_id" { + description = "Hosted zone ID of the ALB — required for Route 53 alias records." + value = aws_lb.this.zone_id +} + +output "https_listener_arn" { + description = "ARN of the HTTPS:443 listener. Listener can be used in downstream modules." + value = aws_lb_listener.https.arn +} + +output "all_listener_arns" { + description = <<-EOT + Map of (string) to listener ARNs, including the default 443 listener. + Use this in ecs-service module calls: + alb_listener_arn = module.my_alb.all_listener_arns["9900"] + EOT + value = merge( + { "443" = aws_lb_listener.https.arn }, + { + for port, listener in aws_lb_listener.extra_https : + port => listener.arn + } + ) +} + +output "internal" { + description = "Whether the ALB is internal (private) or internet-facing." + value = var.internal +} diff --git a/terraform/modules/alb/variables.tf b/terraform/modules/alb/variables.tf new file mode 100644 index 00000000..d99efa7d --- /dev/null +++ b/terraform/modules/alb/variables.tf @@ -0,0 +1,77 @@ +# ------------------------------------------------------- +# Platform / Core +# ------------------------------------------------------- +variable "platform" { + description = "Object representing the CDAP platform module." + type = object({ + app = string + env = string + primary_region = object({ name = string }) + private_subnets = map(object({ id = string })) + service = string + vpc_id = string + }) +} + +variable "name_override" { + type = string + default = null + description = "Override for the ALB name. Defaults to '---alb'." +} + +# ------------------------------------------------------- +# Networking +# ------------------------------------------------------- +variable "subnet_ids" { + type = list(string) + default = null + description = "Subnet IDs to place the ALB in. Defaults to the platform's private subnets." +} + +variable "security_group_ids" { + type = list(string) + default = [] + description = "Security group IDs to attach to the ALB." +} + +# ------------------------------------------------------- +# ALB Visibility +# ------------------------------------------------------- +variable "internal" { + type = bool + default = true + description = "true = private (internal) ALB; false = public (internet-facing) ALB." +} + +# ------------------------------------------------------- +# TLS / ACM +# ------------------------------------------------------- +variable "acm_certificate_arn" { + type = string + description = "ARN of the ACM certificate (public or private CA) for the HTTPS listener." +} + +variable "ssl_policy" { + type = string + default = "ELBSecurityPolicy-TLS13-1-2-2021-06" + description = "TLS security policy. Default enforces TLS 1.2+ per CMS/FISMA requirements." +} + +# ------------------------------------------------------- +# Listeners +# ------------------------------------------------------- +variable "enable_http_redirect" { + type = bool + default = true + description = "When true, adds an HTTP:80 listener that redirects all traffic to HTTPS:443." +} + +variable "extra_listeners" { + description = "Additional HTTPS listeners beyond the default 443" + type = list(object({ + port = number + acm_certificate_arn = string + ssl_policy = optional(string) + })) + default = [] +} diff --git a/terraform/services/hosted_zones/README.md b/terraform/services/hosted_zones/README.md index 4fcb44e3..adb07787 100644 --- a/terraform/services/hosted_zones/README.md +++ b/terraform/services/hosted_zones/README.md @@ -1,3 +1,5 @@ +To manage domain resolution at this path, contact the Akamai team. +