# =============================================================================
# Internal ALB — accessible only via AWS Client VPN
# =============================================================================
# Application Load Balancer (internal)
resource "aws_lb" "main" {
name = "${var.project_name}-${var.environment}-alb"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.private_subnet_ids
enable_deletion_protection = false
enable_http2 = true
enable_cross_zone_load_balancing = true
tags = merge(
var.tags,
{
Name = "${var.project_name}-${var.environment}-alb"
}
)
}
# Target Group
resource "aws_lb_target_group" "main" {
name = "${var.project_name}-${var.environment}-tg"
port = var.container_port
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 3
timeout = 5
interval = 30
path = var.health_check_path
protocol = "HTTP"
matcher = "200"
}
deregistration_delay = 30
# Sticky sessions needed for Streamable HTTP stateful mode — the Mcp-Session-Id
# is bound to a specific supergateway process, so requests within a session
# must hit the same task. Multiple sessions can coexist on each task.
stickiness {
type = "lb_cookie"
cookie_duration = 86400
enabled = true
}
tags = merge(
var.tags,
{
Name = "${var.project_name}-${var.environment}-tg"
}
)
}
# HTTP Listener — forward to target group (no cert)
resource "aws_lb_listener" "http_forward" {
count = var.certificate_arn == "" ? 1 : 0
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
# HTTP Listener — redirect to HTTPS (cert present)
resource "aws_lb_listener" "http_redirect" {
count = var.certificate_arn != "" ? 1 : 0
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
# HTTPS Listener (port 443) — passthrough to ECS (JWT validation in container)
# Set certificate_arn in terraform.tfvars to enable.
resource "aws_lb_listener" "https" {
count = var.certificate_arn != "" ? 1 : 0
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.main.arn
}
}
# Security Group for ALB — VPN + VPC access only
resource "aws_security_group" "alb" {
name_prefix = "${var.project_name}-${var.environment}-alb-"
description = "Security group for internal ALB (VPN access only)"
vpc_id = var.vpc_id
# HTTPS from VPN clients
ingress {
description = "HTTPS from VPN clients"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.vpn_client_cidr, data.aws_vpc.existing.cidr_block]
}
# HTTP from VPN clients (redirect or forward)
ingress {
description = "HTTP from VPN clients"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = [var.vpn_client_cidr, data.aws_vpc.existing.cidr_block]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "${var.project_name}-${var.environment}-alb-sg"
}
)
lifecycle {
create_before_destroy = true
}
}
# Security Group for ECS Tasks
resource "aws_security_group" "ecs_tasks" {
name_prefix = "${var.project_name}-${var.environment}-ecs-"
description = "Security group for ECS tasks"
vpc_id = var.vpc_id
ingress {
description = "Allow traffic from ALB"
from_port = var.container_port
to_port = var.container_port
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "${var.project_name}-${var.environment}-ecs-sg"
}
)
lifecycle {
create_before_destroy = true
}
}