################################################################################ # 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 }) }