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:
180
terraform/modules/rds/main.tf
Normal file
180
terraform/modules/rds/main.tf
Normal 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
|
||||
})
|
||||
}
|
||||
44
terraform/modules/rds/outputs.tf
Normal file
44
terraform/modules/rds/outputs.tf
Normal 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
|
||||
}
|
||||
133
terraform/modules/rds/variables.tf
Normal file
133
terraform/modules/rds/variables.tf
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user