feat(phase-2): workstream 8 — Multi-Region Terraform Deployment
AWS environment: - VPC (3-AZ, public + private subnets, NAT gateways, VPC endpoints for ECR/SM/CW) - ECS Fargate service (sentryagent/agentidp) — secrets from Secrets Manager - RDS PostgreSQL 14 (Multi-AZ, encrypted, VPC-internal, storage autoscaling) - ElastiCache Redis 7 (primary + replica, at-rest + in-transit encryption) - ALB with HTTPS/443, HTTP→HTTPS redirect, ACM certificate - Route 53 alias record GCP environment: - VPC + private services access + Serverless VPC connector - Cloud Run service — secrets from Secret Manager - Cloud SQL PostgreSQL 14 (private IP, no public endpoint) - Cloud Memorystore Redis 7 (VPC-internal, AUTH enabled) Shared: - 4 reusable modules: agentidp (dual AWS/GCP), rds, redis, lb - No hardcoded secrets; all sensitive vars marked sensitive=true - terraform.tfvars.example for both environments - docs/devops/deployment.md — AWS + GCP step-by-step walkthrough, rollback procedures Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
183
terraform/modules/lb/main.tf
Normal file
183
terraform/modules/lb/main.tf
Normal file
@@ -0,0 +1,183 @@
|
||||
################################################################################
|
||||
# Module: lb
|
||||
# Main — AWS Application Load Balancer
|
||||
#
|
||||
# - Internet-facing ALB in public subnets
|
||||
# - HTTPS listener (443) with ACM certificate, TLS 1.2+ enforced
|
||||
# - HTTP listener (80) redirects permanently to HTTPS — no plaintext traffic
|
||||
# - Target group pointing to ECS Fargate tasks on the app port
|
||||
# - Access logs optionally streamed to S3
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.40.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
identifier = "${var.project}-${var.environment}"
|
||||
|
||||
common_tags = {
|
||||
environment = var.environment
|
||||
project = var.project
|
||||
managed_by = "terraform"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Security Group — ALB allows inbound 80 + 443 from the internet
|
||||
################################################################################
|
||||
|
||||
resource "aws_security_group" "alb" {
|
||||
name = "${local.identifier}-alb-sg"
|
||||
description = "ALB security group — inbound 80/443 from internet, outbound to app"
|
||||
vpc_id = var.vpc_id
|
||||
|
||||
ingress {
|
||||
description = "HTTP from internet (redirected to HTTPS)"
|
||||
from_port = 80
|
||||
to_port = 80
|
||||
protocol = "tcp"
|
||||
cidr_blocks = var.allowed_ingress_cidrs
|
||||
}
|
||||
|
||||
ingress {
|
||||
description = "HTTPS from internet"
|
||||
from_port = 443
|
||||
to_port = 443
|
||||
protocol = "tcp"
|
||||
cidr_blocks = var.allowed_ingress_cidrs
|
||||
}
|
||||
|
||||
egress {
|
||||
description = "Forward to ECS app tasks"
|
||||
from_port = var.target_group_port
|
||||
to_port = var.target_group_port
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.identifier}-alb-sg"
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Application Load Balancer
|
||||
################################################################################
|
||||
|
||||
resource "aws_lb" "main" {
|
||||
name = "${local.identifier}-alb"
|
||||
internal = false
|
||||
load_balancer_type = "application"
|
||||
security_groups = [aws_security_group.alb.id]
|
||||
subnets = var.subnet_ids
|
||||
|
||||
idle_timeout = var.idle_timeout
|
||||
enable_deletion_protection = var.enable_deletion_protection
|
||||
|
||||
# HTTP/2 is enabled by default on ALB; leave it on for performance.
|
||||
enable_http2 = true
|
||||
|
||||
# Drop invalid header fields to harden against request smuggling.
|
||||
drop_invalid_header_fields = true
|
||||
|
||||
dynamic "access_logs" {
|
||||
for_each = var.access_logs_bucket != "" ? [1] : []
|
||||
content {
|
||||
bucket = var.access_logs_bucket
|
||||
prefix = var.access_logs_prefix
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.identifier}-alb"
|
||||
})
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Target Group — ECS Fargate tasks register here
|
||||
################################################################################
|
||||
|
||||
resource "aws_lb_target_group" "app" {
|
||||
name = "${local.identifier}-tg"
|
||||
port = var.target_group_port
|
||||
protocol = "HTTP"
|
||||
vpc_id = var.vpc_id
|
||||
target_type = "ip" # Required for Fargate (awsvpc network mode)
|
||||
|
||||
deregistration_delay = 30
|
||||
|
||||
health_check {
|
||||
enabled = true
|
||||
path = var.target_group_health_check_path
|
||||
port = "traffic-port"
|
||||
protocol = "HTTP"
|
||||
interval = var.target_group_health_check_interval
|
||||
timeout = var.target_group_health_check_timeout
|
||||
healthy_threshold = var.target_group_healthy_threshold
|
||||
unhealthy_threshold = var.target_group_unhealthy_threshold
|
||||
matcher = "200"
|
||||
}
|
||||
|
||||
stickiness {
|
||||
type = "lb_cookie"
|
||||
enabled = false # AgentIdP is stateless (JWT-based); no sticky sessions needed
|
||||
}
|
||||
|
||||
tags = merge(local.common_tags, {
|
||||
Name = "${local.identifier}-tg"
|
||||
})
|
||||
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HTTPS Listener (port 443) — primary listener
|
||||
################################################################################
|
||||
|
||||
resource "aws_lb_listener" "https" {
|
||||
load_balancer_arn = aws_lb.main.arn
|
||||
port = 443
|
||||
protocol = "HTTPS"
|
||||
ssl_policy = var.ssl_policy
|
||||
certificate_arn = var.certificate_arn
|
||||
|
||||
default_action {
|
||||
type = "forward"
|
||||
target_group_arn = aws_lb_target_group.app.arn
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# HTTP Listener (port 80) — permanent redirect to HTTPS
|
||||
################################################################################
|
||||
|
||||
resource "aws_lb_listener" "http_redirect" {
|
||||
load_balancer_arn = aws_lb.main.arn
|
||||
port = 80
|
||||
protocol = "HTTP"
|
||||
|
||||
default_action {
|
||||
type = "redirect"
|
||||
|
||||
redirect {
|
||||
port = "443"
|
||||
protocol = "HTTPS"
|
||||
status_code = "HTTP_301"
|
||||
}
|
||||
}
|
||||
|
||||
tags = local.common_tags
|
||||
}
|
||||
49
terraform/modules/lb/outputs.tf
Normal file
49
terraform/modules/lb/outputs.tf
Normal file
@@ -0,0 +1,49 @@
|
||||
################################################################################
|
||||
# Module: lb
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "alb_dns_name" {
|
||||
description = "DNS name of the Application Load Balancer. Create a CNAME or alias record in Route 53 pointing your domain here."
|
||||
value = aws_lb.main.dns_name
|
||||
}
|
||||
|
||||
output "alb_zone_id" {
|
||||
description = "Hosted zone ID of the ALB. Use with aws_route53_record alias records."
|
||||
value = aws_lb.main.zone_id
|
||||
}
|
||||
|
||||
output "alb_arn" {
|
||||
description = "ARN of the Application Load Balancer."
|
||||
value = aws_lb.main.arn
|
||||
}
|
||||
|
||||
output "alb_arn_suffix" {
|
||||
description = "ARN suffix of the ALB for use in CloudWatch metrics."
|
||||
value = aws_lb.main.arn_suffix
|
||||
}
|
||||
|
||||
output "target_group_arn" {
|
||||
description = "ARN of the target group. Pass to the agentidp module as aws_target_group_arn."
|
||||
value = aws_lb_target_group.app.arn
|
||||
}
|
||||
|
||||
output "target_group_arn_suffix" {
|
||||
description = "ARN suffix of the target group for use in CloudWatch metrics."
|
||||
value = aws_lb_target_group.app.arn_suffix
|
||||
}
|
||||
|
||||
output "https_listener_arn" {
|
||||
description = "ARN of the HTTPS listener."
|
||||
value = aws_lb_listener.https.arn
|
||||
}
|
||||
|
||||
output "http_redirect_listener_arn" {
|
||||
description = "ARN of the HTTP→HTTPS redirect listener."
|
||||
value = aws_lb_listener.http_redirect.arn
|
||||
}
|
||||
|
||||
output "alb_security_group_id" {
|
||||
description = "Security group ID of the ALB. Add this as an allowed source in the app task security group."
|
||||
value = aws_security_group.alb.id
|
||||
}
|
||||
102
terraform/modules/lb/variables.tf
Normal file
102
terraform/modules/lb/variables.tf
Normal file
@@ -0,0 +1,102 @@
|
||||
################################################################################
|
||||
# Module: lb
|
||||
# Variables — AWS Application Load Balancer
|
||||
################################################################################
|
||||
|
||||
variable "environment" {
|
||||
description = "Deployment environment label (e.g. production, staging)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "project" {
|
||||
description = "Project identifier used in resource names and tags."
|
||||
type = string
|
||||
default = "sentryagent-agentidp"
|
||||
}
|
||||
|
||||
variable "vpc_id" {
|
||||
description = "VPC ID in which to create the ALB and its security group."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "subnet_ids" {
|
||||
description = "List of public subnet IDs for the ALB. Must span at least 2 AZs."
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "certificate_arn" {
|
||||
description = "ARN of the ACM certificate to attach to the HTTPS listener (port 443)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "target_group_port" {
|
||||
description = "Port that ECS task containers listen on. Target group forwards traffic to this port."
|
||||
type = number
|
||||
default = 3000
|
||||
}
|
||||
|
||||
variable "target_group_health_check_path" {
|
||||
description = "HTTP path used by the ALB target group health check."
|
||||
type = string
|
||||
default = "/health"
|
||||
}
|
||||
|
||||
variable "target_group_health_check_interval" {
|
||||
description = "Interval in seconds between ALB health checks."
|
||||
type = number
|
||||
default = 30
|
||||
}
|
||||
|
||||
variable "target_group_health_check_timeout" {
|
||||
description = "Timeout in seconds for each ALB health check request."
|
||||
type = number
|
||||
default = 5
|
||||
}
|
||||
|
||||
variable "target_group_healthy_threshold" {
|
||||
description = "Number of consecutive successful health checks before marking a target healthy."
|
||||
type = number
|
||||
default = 2
|
||||
}
|
||||
|
||||
variable "target_group_unhealthy_threshold" {
|
||||
description = "Number of consecutive failed health checks before marking a target unhealthy."
|
||||
type = number
|
||||
default = 3
|
||||
}
|
||||
|
||||
variable "idle_timeout" {
|
||||
description = "ALB idle connection timeout in seconds."
|
||||
type = number
|
||||
default = 60
|
||||
}
|
||||
|
||||
variable "enable_deletion_protection" {
|
||||
description = "Prevent the ALB from being deleted via the AWS API."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "access_logs_bucket" {
|
||||
description = "S3 bucket name for ALB access logs. Leave empty to disable access logging."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "access_logs_prefix" {
|
||||
description = "S3 key prefix for ALB access log files."
|
||||
type = string
|
||||
default = "alb"
|
||||
}
|
||||
|
||||
variable "ssl_policy" {
|
||||
description = "SSL negotiation policy for the HTTPS listener. ELBSecurityPolicy-TLS13-1-2-2021-06 enforces TLS 1.2+ and TLS 1.3."
|
||||
type = string
|
||||
default = "ELBSecurityPolicy-TLS13-1-2-2021-06"
|
||||
}
|
||||
|
||||
variable "allowed_ingress_cidrs" {
|
||||
description = "CIDR blocks allowed to reach the ALB on port 80 and 443. Default allows public internet."
|
||||
type = list(string)
|
||||
default = ["0.0.0.0/0"]
|
||||
}
|
||||
Reference in New Issue
Block a user