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>
181 lines
5.5 KiB
HCL
181 lines
5.5 KiB
HCL
################################################################################
|
|
# 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
|
|
})
|
|
}
|