Add Terraform AWS infrastructure and enhanced CI/CD pipeline (FRE-4574)

- Terraform modules: VPC, ECS Fargate, RDS PostgreSQL, ElastiCache Redis, S3, Secrets Manager, CloudWatch
- Multi-environment support: staging and production configs
- ECS auto-scaling: CPU-based scaling with configurable min/max
- CI/CD: pnpm caching, Docker Buildx, Trivy security scanning, Terraform plan on PR
- Deploy: ECS service updates with automatic rollback on health check failure
- Backup: automated RDS snapshots, S3 versioning, ElastiCache snapshots
- Monitoring: CloudWatch dashboards, CPU/memory/5xx alarms
- Rollback script for manual service rollback
- Infrastructure documentation with architecture overview
This commit is contained in:
Senior Engineer
2026-05-08 02:54:39 -04:00
committed by Michael Freno
parent baa216d62c
commit a0799c0647
19 changed files with 1902 additions and 45 deletions

View File

@@ -0,0 +1,183 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "cluster_name" {
description = "ECS cluster name"
type = string
}
variable "project_name" {
description = "Project name"
type = string
}
variable "rds_identifier" {
description = "RDS instance identifier"
type = string
}
variable "cache_endpoint" {
description = "ElastiCache endpoint"
type = string
}
resource "aws_cloudwatch_dashboard" "main" {
dashboard_name = "${var.project_name}-${var.environment}-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
properties = {
title = "ECS CPU Utilization"
metrics = [
["AWS/ECS", "CPUUtilization", "ClusterName", var.cluster_name]
]
view = "timeSeries"
stacked = false
region = "us-east-1"
period = 300
}
},
{
type = "metric"
properties = {
title = "ECS Memory Utilization"
metrics = [
["AWS/ECS", "MemoryUtilization", "ClusterName", var.cluster_name]
]
view = "timeSeries"
stacked = false
region = "us-east-1"
period = 300
}
},
{
type = "metric"
properties = {
title = "RDS CPU Utilization"
metrics = [
["AWS/RDS", "CPUUtilization", "DBInstanceIdentifier", var.rds_identifier]
]
view = "timeSeries"
stacked = false
region = "us-east-1"
period = 300
}
},
{
type = "metric"
properties = {
title = "ALB Request Count"
metrics = [
["AWS/ApplicationELB", "RequestCount", "LoadBalancer", "${var.cluster_name}-alb"]
]
view = "timeSeries"
stacked = false
region = "us-east-1"
period = 60
}
},
{
type = "metric"
properties = {
title = "ALB 5xx Errors"
metrics = [
["AWS/ApplicationELB", "HTTPCode_Elb_5XX_Count", "LoadBalancer", "${var.cluster_name}-alb"]
]
view = "timeSeries"
stacked = false
region = "us-east-1"
period = 60
}
}
]
})
}
resource "aws_cloudwatch_metric_alarm" "ecs_cpu_high" {
alarm_name = "${var.project_name}-${var.environment}-ecs-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = 300
statistic = "Average"
threshold = 80
alarm_description = "ECS CPU utilization above 80%"
dimensions = {
ClusterName = var.cluster_name
}
}
resource "aws_cloudwatch_metric_alarm" "ecs_memory_high" {
alarm_name = "${var.project_name}-${var.environment}-ecs-memory-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "MemoryUtilization"
namespace = "AWS/ECS"
period = 300
statistic = "Average"
threshold = 85
alarm_description = "ECS memory utilization above 85%"
dimensions = {
ClusterName = var.cluster_name
}
}
resource "aws_cloudwatch_metric_alarm" "alb_5xx" {
alarm_name = "${var.project_name}-${var.environment}-alb-5xx"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 3
metric_name = "HTTPCode_Elb_5XX_Count"
namespace = "AWS/ApplicationELB"
period = 60
statistic = "Sum"
threshold = 10
alarm_description = "ALB 5xx errors above 10 per minute"
dimensions = {
LoadBalancer = "${var.cluster_name}-alb"
}
}
resource "aws_cloudwatch_metric_alarm" "rds_cpu_high" {
alarm_name = "${var.project_name}-${var.environment}-rds-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 75
alarm_description = "RDS CPU utilization above 75%"
dimensions = {
DBInstanceIdentifier = var.rds_identifier
}
}
resource "aws_cloudwatch_metric_alarm" "rds_free_storage" {
alarm_name = "${var.project_name}-${var.environment}-rds-free-storage"
comparison_operator = "LessThanThreshold"
evaluation_periods = 2
metric_name = "FreeStorageSpace"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 524288000
alarm_description = "RDS free storage below 500MB"
dimensions = {
DBInstanceIdentifier = var.rds_identifier
}
}
output "dashboard_url" {
description = "CloudWatch dashboard URL"
value = "https://us-east-1.console.aws.amazon.com/cloudwatch/home#dashboards/dashboard/${var.project_name}-${var.environment}-dashboard"
}

355
infra/modules/ecs/main.tf Normal file
View File

@@ -0,0 +1,355 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "cluster_name" {
description = "ECS cluster name"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "subnet_ids" {
description = "Private subnet IDs"
type = list(string)
}
variable "security_group_ids" {
description = "Security group IDs"
type = list(string)
}
variable "services" {
description = "ECS services to deploy"
type = map(object({
cpu = number
memory = number
port = number
}))
}
variable "container_images" {
description = "Container image tags"
type = map(string)
}
variable "secrets_arn" {
description = "Secrets Manager ARN"
type = string
}
resource "aws_ecs_cluster" "main" {
name = var.cluster_name
settings {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = var.cluster_name
}
}
resource "aws_ecs_cluster_capacity_providers" "main" {
cluster_name = aws_ecs_cluster.main.name
capacity_providers = ["FARGATE"]
default_capacity_provider_strategy {
base = 1
weight = 100
capacity_provider = "FARGATE"
}
}
resource "aws_ecs_task_definition" "services" {
for_each = var.services
family = "${var.cluster_name}-${each.key}"
container_definitions = jsonencode([
{
name = each.key
image = "ghcr.io/shieldai/shieldai-${each.key}:${var.container_images[each.key]}"
cpu = each.cpu
memory = each.memory
essential = true
portMappings = [
{
containerPort = each.port
hostPort = each.port
protocol = "tcp"
}
]
environment = [
{
name = "NODE_ENV"
value = var.environment
},
{
name = "PORT"
value = tostring(each.port)
}
]
secrets = [
{
name = "DATABASE_URL"
valueFrom = "${var.secrets_arn}:DATABASE_URL::"
},
{
name = "REDIS_URL"
valueFrom = "${var.secrets_arn}:REDIS_URL::"
},
{
name = "HIBP_API_KEY"
valueFrom = "${var.secrets_arn}:HIBP_API_KEY::"
},
{
name = "RESEND_API_KEY"
valueFrom = "${var.secrets_arn}:RESEND_API_KEY::"
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/${var.cluster_name}-${each.key}"
"awslogs-region" = "us-east-1"
"awslogs-stream-prefix" = each.key
}
}
healthCheck = {
command = ["CMD-SHELL", "wget -q --spider http://localhost:${each.port}/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}
])
network_mode = "awsvpc"
memory = each.memory
cpu = each.cpu
requires_compatibilities = ["FARGATE"]
execution_role_arn = aws_iam_role.execution[each.key].arn
task_role_arn = aws_iam_role.task[each.key].arn
tags = {
Name = "${var.cluster_name}-${each.key}"
}
}
resource "aws_iam_role" "execution" {
for_each = var.services
name = "${var.cluster_name}-${each.key}-execution"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
]
}
resource "aws_iam_role" "task" {
for_each = var.services
name = "${var.cluster_name}-${each.key}-task"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/SecretsManagerReadOnly"
]
inline_policy {
name = "elasticache-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"elasticache:DescribeCacheClusters",
"elasticache:DescribeCacheSubnetGroups"
]
Resource = "*"
}
]
})
}
}
resource "aws_ecs_service" "services" {
for_each = var.services
name = "${var.cluster_name}-${each.key}"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.services[each.key].arn
desired_count = var.environment == "production" ? 3 : 1
launch_desired_count = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = var.security_group_ids
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.services[each.key].arn
container_name = each.key
container_port = each.port
}
auto_scaling {
max_capacity = var.environment == "production" ? 10 : 3
min_capacity = var.environment == "production" ? 2 : 1
}
tags = {
Name = "${var.cluster_name}-${each.key}"
Service = each.key
}
depends_on = [
aws_lb_listener.services
]
}
resource "aws_lb" "main" {
name = "${var.cluster_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = var.security_group_ids
subnets = var.subnet_ids
tags = {
Name = "${var.cluster_name}-alb"
}
}
resource "aws_lb_target_group" "services" {
for_each = var.services
name = "${var.cluster_name}-${each.key}-tg"
port = each.port
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 3
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
stickiness {
type = "lb_cookie"
cookie_duration = 86400
}
}
resource "aws_lb_listener" "services" {
for_each = var.services
load_balancer_arn = aws_lb.main.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.services[each.key].arn
}
}
resource "aws_appautoscaling_target" "services" {
for_each = var.services
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.services[each.key].name}"
scalable_dimension = "ecs:service:DesiredCount"
min_capacity = var.environment == "production" ? 2 : 1
max_capacity = var.environment == "production" ? 10 : 3
}
resource "aws_appautoscaling_policy" "cpu" {
for_each = var.services
name = "${var.cluster_name}-${each.key}-cpu-scaling"
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.services[each.key].name}"
scalable_dimension = "ecs:service:DesiredCount"
target_tracking_scaling_policy_configuration {
target_value = 70.0
scale_in_cooldown = 60
scale_out_cooldown = 30
customized_metric_specification {
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
statistic = "Average"
dimensions = [{ name = "ClusterName", value = aws_ecs_cluster.main.name }]
}
}
}
resource "aws_cloudwatch_log_group" "services" {
for_each = var.services
name = "/ecs/${var.cluster_name}-${each.key}"
retention_in_days = var.environment == "production" ? 30 : 7
tags = {
Name = "${var.cluster_name}-${each.key}-logs"
}
}
output "cluster_arn" {
description = "ECS cluster ARN"
value = aws_ecs_cluster.main.arn
}
output "alb_dns_name" {
description = "ALB DNS name"
value = aws_lb.main.dns_name
}

View File

@@ -0,0 +1,80 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "subnet_ids" {
description = "Private subnet IDs"
type = list(string)
}
variable "security_group_id" {
description = "ElastiCache security group ID"
type = string
}
variable "node_type" {
description = "Cache node type"
type = string
}
variable "num_nodes" {
description = "Number of cache nodes"
type = number
}
variable "project_name" {
description = "Project name"
type = string
}
resource "aws_elasticache_subnet_group" "main" {
name = "${var.project_name}-${var.environment}-redis-subnet"
subnet_ids = var.subnet_ids
tags = {
Name = "${var.project_name}-${var.environment}-redis-subnet"
}
}
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "${var.project_name}-${var.environment}-redis"
description = "${var.project_name} Redis cluster (${var.environment})"
node_type = var.node_type
num_cache_clusters = var.num_nodes
engine = "redis"
engine_version = "7.0"
transit_encryption_enabled = true
at_rest_encryption_enabled = true
port = 6379
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [var.security_group_id]
automatic_failover_enabled = var.environment == "production"
snapshot_retention_limit = var.environment == "production" ? 7 : 1
snapshot_window = "03:00-04:00"
tags = {
Name = "${var.project_name}-${var.environment}-redis"
}
}
output "cache_endpoint" {
description = "ElastiCache primary endpoint"
value = aws_elasticache_replication_group.main.primary_endpoint_address
}
output "reader_endpoint" {
description = "ElastiCache reader endpoint"
value = aws_elasticache_replication_group.main.reader_endpoint_address
}

132
infra/modules/rds/main.tf Normal file
View File

@@ -0,0 +1,132 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "subnet_ids" {
description = "Private subnet IDs"
type = list(string)
}
variable "security_group_id" {
description = "RDS security group ID"
type = string
}
variable "db_name" {
description = "Database name"
type = string
}
variable "db_instance_class" {
description = "RDS instance class"
type = string
}
variable "multi_az" {
description = "Multi-AZ deployment"
type = bool
}
variable "backup_retention" {
description = "Backup retention days"
type = number
}
variable "project_name" {
description = "Project name"
type = string
}
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-${var.environment}-db-subnet"
subnet_ids = var.subnet_ids
tags = {
Name = "${var.project_name}-${var.environment}-db-subnet"
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-${var.environment}-db"
engine = "postgres"
engine_version = "16.2"
instance_class = var.db_instance_class
allocated_storage = var.environment == "production" ? 100 : 20
db_name = var.db_name
username = "shieldai"
password = random_password.db_password.result
multi_az = var.multi_az
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [var.security_group_id]
backup_retention_period = var.backup_retention
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = "${var.project_name}-${var.environment}-final"
storage_encrypted = true
storage_type = "gp3"
iops = var.environment == "production" ? 3000 : 1000
deletion_protection = var.environment == "production"
copy_tags_to_snapshot = true
tags = {
Name = "${var.project_name}-${var.environment}-db"
}
}
resource "random_password" "db_password" {
length = 16
special = true
keepers = {
environment = var.environment
}
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = jsonencode({
username = "shieldai"
password = random_password.db_password.result
engine = "postgres"
host = aws_db_instance.main.address
port = aws_db_instance.main.port
})
}
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.project_name}-${var.environment}-db-password"
tags = {
Name = "${var.project_name}-${var.environment}-db-password"
}
}
output "db_endpoint" {
description = "RDS endpoint"
value = aws_db_instance.main.endpoint
sensitive = true
}
output "db_instance_identifier" {
description = "RDS instance identifier"
value = aws_db_instance.main.identifier
}
output "db_password_secret_arn" {
description = "DB password secret ARN"
value = aws_secretsmanager_secret.db_password.arn
}

108
infra/modules/s3/main.tf Normal file
View File

@@ -0,0 +1,108 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "project_name" {
description = "Project name"
type = string
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "${var.project_name}-${var.environment}-terraform-state"
tags = {
Name = "${var.project_name}-${var.environment}-terraform-state"
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
id = "expire-noncurrent"
status = "Enabled"
noncurrent_version_expiration {
noncurrent_days = 30
}
}
}
resource "aws_s3_bucket" "artifacts" {
bucket = "${var.project_name}-${var.environment}-artifacts"
tags = {
Name = "${var.project_name}-${var.environment}-artifacts"
}
}
resource "aws_s3_bucket_versioning" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket" "logs" {
bucket = "${var.project_name}-${var.environment}-logs"
tags = {
Name = "${var.project_name}-${var.environment}-logs"
}
}
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
id = "expire-old-logs"
status = "Enabled"
expiration {
days = 90
}
}
}
output "bucket_name" {
description = "Terraform state S3 bucket name"
value = aws_s3_bucket.terraform_state.id
}
output "artifacts_bucket_name" {
description = "Artifacts S3 bucket name"
value = aws_s3_bucket.artifacts.id
}
output "logs_bucket_name" {
description = "Logs S3 bucket name"
value = aws_s3_bucket.logs.id
}

View File

@@ -0,0 +1,49 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "project_name" {
description = "Project name"
type = string
}
variable "secrets" {
description = "Secrets to store"
type = map(string)
default = {}
}
resource "aws_secretsmanager_secret" "main" {
name = "${var.project_name}-${var.environment}-app-secrets"
description = "Application secrets for ${var.project_name} (${var.environment})"
tags = {
Name = "${var.project_name}-${var.environment}-app-secrets"
Environment = var.environment
}
}
resource "aws_secretsmanager_secret_version" "main" {
secret_id = aws_secretsmanager_secret.main.id
secret_string = jsonencode(merge({
DATABASE_URL = "postgresql://shieldai:${var.project_name}@${var.project_name}-${var.environment}-db.${data.aws_caller_identity.current.account_id}.us-east-1.rds.amazonaws.com:5432/shieldai"
REDIS_URL = "redis://${var.project_name}-${var.environment}-redis.${data.aws_caller_identity.current.account_id}.us-east-1.cache.amazonaws.com:6379"
NODE_ENV = var.environment
LOG_LEVEL = var.environment == "production" ? "info" : "debug"
}, var.secrets))
}
data "aws_caller_identity" "current" {}
output "secrets_manager_arn" {
description = "Secrets Manager ARN"
value = aws_secretsmanager_secret.main.arn
}
output "secrets_manager_name" {
description = "Secrets Manager secret name"
value = aws_secretsmanager_secret.main.name
}

235
infra/modules/vpc/main.tf Normal file
View File

@@ -0,0 +1,235 @@
variable "environment" {
description = "Deployment environment"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
}
variable "az_count" {
description = "Number of availability zones"
type = number
}
variable "project_name" {
description = "Project name"
type = string
}
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
}
}
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_subnet" "public" {
count = var.az_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-${data.aws_availability_zones.available.names[count.index]}"
"kubernetes.io/role/elb" = "1"
}
}
resource "aws_subnet" "private" {
count = var.az_count
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, var.az_count + count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.project_name}-${var.environment}-private-${data.aws_availability_zones.available.names[count.index]}"
"kubernetes.io/role/internal-elb" = "1"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}
resource "aws_eip" "nat" {
count = var.az_count
domain = "vpc"
tags = {
Name = "${var.project_name}-${var.environment}-nat-${count.index}"
}
}
resource "aws_nat_gateway" "main" {
count = var.az_count
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-${var.environment}-nat-${count.index}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
}
}
resource "aws_route_table" "private" {
count = var.az_count
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.project_name}-${var.environment}-private-rt-${count.index}"
}
}
resource "aws_route_table_association" "public" {
count = var.az_count
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = var.az_count
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
resource "aws_security_group" "ecs" {
name_prefix = "${var.project_name}-${var.environment}-ecs"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3000
to_port = 3003
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Service ports"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-ecs-sg"
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.project_name}-${var.environment}-rds"
vpc_id = aws_vpc.main.id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.ecs.id]
description = "PostgreSQL from ECS"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-rds-sg"
}
}
resource "aws_security_group" "elasticache" {
name_prefix = "${var.project_name}-${var.environment}-elasticache"
vpc_id = aws_vpc.main.id
ingress {
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.ecs.id]
description = "Redis from ECS"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-elasticache-sg"
}
}
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "private_subnet_ids" {
description = "Private subnet IDs"
value = aws_subnet.private[*].id
}
output "public_subnet_ids" {
description = "Public subnet IDs"
value = aws_subnet.public[*].id
}
output "ecs_security_group_id" {
description = "ECS security group ID"
value = aws_security_group.ecs.id
}
output "rds_security_group_id" {
description = "RDS security group ID"
value = aws_security_group.rds.id
}
output "elasticache_security_group_id" {
description = "ElastiCache security group ID"
value = aws_security_group.elasticache.id
}