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,180 @@
################################################################################
# Module: rds
# Main — AWS RDS PostgreSQL 14
#
# - Multi-AZ for HA
# - Encryption at rest (AWS-managed KMS key)
# - No public access — VPC-internal only
# - Storage autoscaling up to max_allocated_storage
# - Enhanced monitoring and Performance Insights enabled by default
# - Access restricted to explicitly allowed security groups (app only)
################################################################################
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 — only the app SGs may connect on 5432
################################################################################
resource "aws_security_group" "rds" {
name = "${local.identifier}-rds-sg"
description = "Controls inbound access to RDS PostgreSQL — allow only app SG on 5432"
vpc_id = var.vpc_id
# No ingress rules defined here — added dynamically below to avoid circular deps.
egress {
description = "All outbound (RDS initiates no outbound connections; this satisfies AWS requirement)"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.common_tags, {
Name = "${local.identifier}-rds-sg"
})
}
resource "aws_security_group_rule" "rds_ingress_from_app" {
for_each = toset(var.allowed_security_group_ids)
type = "ingress"
description = "PostgreSQL from app security group"
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = each.value
security_group_id = aws_security_group.rds.id
}
################################################################################
# DB Subnet Group — must cover at least 2 AZs for Multi-AZ
################################################################################
resource "aws_db_subnet_group" "main" {
name = "${local.identifier}-db-subnet-group"
description = "Private subnets for AgentIdP RDS instance"
subnet_ids = var.subnet_ids
tags = merge(local.common_tags, {
Name = "${local.identifier}-db-subnet-group"
})
}
################################################################################
# DB Parameter Group — enforce SSL connections
################################################################################
resource "aws_db_parameter_group" "main" {
name = "${local.identifier}-pg14-params"
family = var.parameter_group_family
description = "AgentIdP custom parameter group — enforces SSL"
parameter {
name = "rds.force_ssl"
value = "1"
apply_method = "immediate"
}
parameter {
name = "log_connections"
value = "1"
apply_method = "immediate"
}
parameter {
name = "log_disconnections"
value = "1"
apply_method = "immediate"
}
parameter {
name = "log_min_duration_statement"
value = "1000"
apply_method = "immediate"
}
tags = local.common_tags
}
################################################################################
# RDS Instance
################################################################################
resource "aws_db_instance" "main" {
identifier = local.identifier
# Engine
engine = "postgres"
engine_version = "14"
instance_class = var.instance_class
# Storage
storage_type = "gp3"
allocated_storage = var.allocated_storage
max_allocated_storage = var.max_allocated_storage
storage_encrypted = true
# kms_key_id is omitted — defaults to the AWS-managed RDS KMS key.
# For customer-managed key, set kms_key_id to your CMK ARN.
# Database
db_name = var.db_name
username = var.db_username
password = var.db_password
# Network — VPC-internal only, no public endpoint
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false
multi_az = var.multi_az
port = 5432
# Parameter group
parameter_group_name = aws_db_parameter_group.main.name
# Backups
backup_retention_period = var.backup_retention_days
backup_window = var.backup_window
delete_automated_backups = false
copy_tags_to_snapshot = true
skip_final_snapshot = var.skip_final_snapshot
final_snapshot_identifier = var.skip_final_snapshot ? null : "${local.identifier}-final-snapshot"
# Maintenance
maintenance_window = var.maintenance_window
auto_minor_version_upgrade = true
apply_immediately = false
# Observability
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
performance_insights_enabled = var.performance_insights_enabled
performance_insights_retention_period = var.performance_insights_enabled ? var.performance_insights_retention_period : null
monitoring_interval = var.monitoring_interval
monitoring_role_arn = var.monitoring_interval > 0 ? var.monitoring_role_arn : null
# Protection
deletion_protection = var.deletion_protection
tags = merge(local.common_tags, {
Name = local.identifier
})
}

View File

@@ -0,0 +1,44 @@
################################################################################
# Module: rds
# Outputs
################################################################################
output "endpoint" {
description = "RDS instance endpoint hostname (without port). Use to construct DATABASE_URL."
value = aws_db_instance.main.address
}
output "port" {
description = "Port the RDS instance listens on (always 5432)."
value = aws_db_instance.main.port
}
output "db_name" {
description = "Name of the database created on the RDS instance."
value = aws_db_instance.main.db_name
}
output "db_username" {
description = "Master username for the RDS instance."
value = aws_db_instance.main.username
}
output "instance_id" {
description = "RDS instance identifier."
value = aws_db_instance.main.identifier
}
output "instance_arn" {
description = "ARN of the RDS instance."
value = aws_db_instance.main.arn
}
output "security_group_id" {
description = "Security group ID attached to the RDS instance. Use to add further ingress rules if needed."
value = aws_security_group.rds.id
}
output "db_subnet_group_name" {
description = "Name of the DB subnet group."
value = aws_db_subnet_group.main.name
}

View File

@@ -0,0 +1,133 @@
################################################################################
# Module: rds
# Variables — AWS RDS PostgreSQL 14
################################################################################
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 RDS subnet group and security group."
type = string
}
variable "subnet_ids" {
description = "List of private subnet IDs for the RDS DB subnet group. Must span at least 2 AZs for Multi-AZ."
type = list(string)
}
variable "allowed_security_group_ids" {
description = "List of security group IDs (e.g. ECS app SG) permitted to connect to RDS on port 5432."
type = list(string)
default = []
}
variable "db_name" {
description = "Name of the initial PostgreSQL database to create."
type = string
default = "sentryagent_idp"
}
variable "db_username" {
description = "Master username for the RDS instance."
type = string
default = "sentryagent"
}
variable "db_password" {
description = "Master password for the RDS instance. Store this in Secrets Manager; do not hardcode."
type = string
sensitive = true
}
variable "instance_class" {
description = "RDS instance class."
type = string
default = "db.t3.medium"
}
variable "allocated_storage" {
description = "Initial storage allocated in GiB."
type = number
default = 50
}
variable "max_allocated_storage" {
description = "Upper bound for RDS storage autoscaling in GiB. Set to 0 to disable autoscaling."
type = number
default = 500
}
variable "multi_az" {
description = "Enable Multi-AZ deployment for high availability."
type = bool
default = true
}
variable "backup_retention_days" {
description = "Number of days to retain automated backups. Must be >= 1 for Multi-AZ."
type = number
default = 7
}
variable "backup_window" {
description = "Preferred daily backup window in UTC (hh24:mi-hh24:mi)."
type = string
default = "03:00-04:00"
}
variable "maintenance_window" {
description = "Preferred weekly maintenance window (ddd:hh24:mi-ddd:hh24:mi in UTC)."
type = string
default = "sun:05:00-sun:06:00"
}
variable "deletion_protection" {
description = "Enable deletion protection. Set to false only when decommissioning."
type = bool
default = true
}
variable "skip_final_snapshot" {
description = "Whether to skip the final DB snapshot on destroy. Should be false in production."
type = bool
default = false
}
variable "performance_insights_enabled" {
description = "Enable RDS Performance Insights."
type = bool
default = true
}
variable "performance_insights_retention_period" {
description = "Performance Insights data retention in days. Free tier = 7; paid tiers = 731."
type = number
default = 7
}
variable "monitoring_interval" {
description = "Enhanced monitoring interval in seconds (0 to disable, valid: 1, 5, 10, 15, 30, 60)."
type = number
default = 60
}
variable "monitoring_role_arn" {
description = "IAM role ARN for RDS Enhanced Monitoring. Required when monitoring_interval > 0."
type = string
default = ""
}
variable "parameter_group_family" {
description = "DB parameter group family."
type = string
default = "postgres14"
}