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:
SentryAgent.ai Developer
2026-03-29 06:25:14 +00:00
parent a504964e5f
commit 6913d62648
22 changed files with 4138 additions and 8 deletions

View 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
}

View 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
}

View 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"]
}