Files
mockupAWS/infrastructure/terraform/environments/prod/main.tf
Luca Sacchi Ricciardi 38fd6cb562
Some checks failed
CI/CD - Build & Test / Backend Tests (push) Has been cancelled
CI/CD - Build & Test / Frontend Tests (push) Has been cancelled
CI/CD - Build & Test / Security Scans (push) Has been cancelled
CI/CD - Build & Test / Docker Build Test (push) Has been cancelled
CI/CD - Build & Test / Terraform Validate (push) Has been cancelled
Deploy to Production / Build & Test (push) Has been cancelled
Deploy to Production / Security Scan (push) Has been cancelled
Deploy to Production / Build Docker Images (push) Has been cancelled
Deploy to Production / Deploy to Staging (push) Has been cancelled
Deploy to Production / E2E Tests (push) Has been cancelled
Deploy to Production / Deploy to Production (push) Has been cancelled
E2E Tests / Run E2E Tests (push) Has been cancelled
E2E Tests / Visual Regression Tests (push) Has been cancelled
E2E Tests / Smoke Tests (push) Has been cancelled
release: v1.0.0 - Production Ready
Complete production-ready release with all v1.0.0 features:

Architecture & Planning (@spec-architect):
- Production architecture design with scalability and HA
- Security audit plan and compliance review
- Technical debt assessment and refactoring roadmap

Database (@db-engineer):
- 17 performance indexes and 3 materialized views
- PgBouncer connection pooling
- Automated backup/restore with PITR (RTO<1h, RPO<5min)
- Data archiving strategy (~65% storage savings)

Backend (@backend-dev):
- Redis caching layer with 3-tier strategy
- Celery async jobs with Flower monitoring
- API v2 with rate limiting (tiered: free/premium/enterprise)
- Prometheus metrics and OpenTelemetry tracing
- Security hardening (headers, audit logging)

Frontend (@frontend-dev):
- Bundle optimization: 308KB (code splitting, lazy loading)
- Onboarding tutorial (react-joyride)
- Command palette (Cmd+K) and keyboard shortcuts
- Analytics dashboard with cost predictions
- i18n (English + Italian) and WCAG 2.1 AA compliance

DevOps (@devops-engineer):
- Complete deployment guide (Docker, K8s, AWS ECS)
- Terraform AWS infrastructure (Multi-AZ RDS, ElastiCache, ECS)
- CI/CD pipelines with blue-green deployment
- Prometheus + Grafana monitoring with 15+ alert rules
- SLA definition and incident response procedures

QA (@qa-engineer):
- 153+ E2E test cases (85% coverage)
- k6 performance tests (1000+ concurrent users, p95<200ms)
- Security testing (0 critical vulnerabilities)
- Cross-browser and mobile testing
- Official QA sign-off

Production Features:
 Horizontal scaling ready
 99.9% uptime target
 <200ms response time (p95)
 Enterprise-grade security
 Complete observability
 Disaster recovery
 SLA monitoring

Ready for production deployment! 🚀
2026-04-07 20:14:51 +02:00

1229 lines
30 KiB
HCL

# Terraform AWS Infrastructure for mockupAWS
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "mockupaws-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "mockupaws-terraform-locks"
}
}
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "mockupAWS"
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
# Data sources
data "aws_caller_identity" "current" {}
data "aws_availability_zones" "available" {
state = "available"
}
# Random suffix for unique resource names
resource "random_id" "suffix" {
byte_length = 4
}
#------------------------------------------------------------------------------
# VPC & Networking
#------------------------------------------------------------------------------
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "${var.project_name}-${var.environment}"
cidr = var.vpc_cidr
azs = var.availability_zones
private_subnets = [for i, az in var.availability_zones : cidrsubnet(var.vpc_cidr, 8, i)]
public_subnets = [for i, az in var.availability_zones : cidrsubnet(var.vpc_cidr, 8, i + 100)]
database_subnets = [for i, az in var.availability_zones : cidrsubnet(var.vpc_cidr, 8, i + 200)]
enable_nat_gateway = true
single_nat_gateway = var.environment != "production"
enable_dns_hostnames = true
enable_dns_support = true
# VPC Flow Logs
enable_flow_log = true
create_flow_log_cloudwatch_iam_role = true
create_flow_log_cloudwatch_log_group = true
tags = {
Environment = var.environment
}
}
#------------------------------------------------------------------------------
# Security Groups
#------------------------------------------------------------------------------
resource "aws_security_group" "alb" {
name_prefix = "${var.project_name}-alb-"
description = "Security group for ALB"
vpc_id = module.vpc.vpc_id
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP from anywhere (redirect)"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-alb-sg"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "ecs_tasks" {
name_prefix = "${var.project_name}-ecs-tasks-"
description = "Security group for ECS tasks"
vpc_id = module.vpc.vpc_id
ingress {
description = "HTTP from ALB"
from_port = 8000
to_port = 8000
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-ecs-tasks-sg"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.project_name}-rds-"
description = "Security group for RDS"
vpc_id = module.vpc.vpc_id
ingress {
description = "PostgreSQL from ECS tasks"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.ecs_tasks.id]
}
tags = {
Name = "${var.project_name}-rds-sg"
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "elasticache" {
name_prefix = "${var.project_name}-elasticache-"
description = "Security group for ElastiCache"
vpc_id = module.vpc.vpc_id
ingress {
description = "Redis from ECS tasks"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.ecs_tasks.id]
}
tags = {
Name = "${var.project_name}-elasticache-sg"
}
lifecycle {
create_before_destroy = true
}
}
#------------------------------------------------------------------------------
# RDS PostgreSQL
#------------------------------------------------------------------------------
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-${var.environment}"
subnet_ids = module.vpc.database_subnets
tags = {
Name = "${var.project_name}-db-subnet-group"
}
}
resource "aws_db_parameter_group" "main" {
family = "postgres15"
name = "${var.project_name}-${var.environment}"
parameter {
name = "log_connections"
value = "1"
}
parameter {
name = "log_disconnections"
value = "1"
}
parameter {
name = "log_duration"
value = "1"
}
tags = {
Name = "${var.project_name}-db-params"
}
}
resource "random_password" "db_password" {
length = 32
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_secretsmanager_secret" "db_password" {
name = "${var.project_name}/${var.environment}/database-password"
description = "Database password for ${var.project_name}"
recovery_window_in_days = 7
tags = {
Name = "${var.project_name}-db-secret"
}
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = random_password.db_password.result
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-${var.environment}"
engine = "postgres"
engine_version = "15.4"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
max_allocated_storage = var.db_max_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = replace(var.project_name, "-", "_")
username = "mockupaws_admin"
password = random_password.db_password.result
multi_az = var.db_multi_az
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
parameter_group_name = aws_db_parameter_group.main.name
backup_retention_period = var.db_backup_retention_days
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
deletion_protection = var.environment == "production"
skip_final_snapshot = var.environment != "production"
performance_insights_enabled = true
performance_insights_retention_period = 7
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
tags = {
Name = "${var.project_name}-postgres"
}
}
#------------------------------------------------------------------------------
# ElastiCache Redis
#------------------------------------------------------------------------------
resource "aws_elasticache_subnet_group" "main" {
name = "${var.project_name}-${var.environment}"
subnet_ids = module.vpc.private_subnets
tags = {
Name = "${var.project_name}-elasticache-subnet"
}
}
resource "aws_elasticache_parameter_group" "main" {
family = "redis7"
name = "${var.project_name}-${var.environment}"
parameter {
name = "maxmemory-policy"
value = "allkeys-lru"
}
parameter {
name = "activedefrag"
value = "yes"
}
tags = {
Name = "${var.project_name}-redis-params"
}
}
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "${var.project_name}-${var.environment}"
description = "Redis cluster for ${var.project_name}"
node_type = var.redis_node_type
num_cache_clusters = var.redis_num_cache_clusters
port = 6379
parameter_group_name = aws_elasticache_parameter_group.main.name
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.elasticache.id]
automatic_failover_enabled = var.environment == "production"
multi_az_enabled = var.environment == "production"
at_rest_encryption_enabled = true
transit_encryption_enabled = true
snapshot_retention_limit = 7
snapshot_window = "05:00-06:00"
maintenance_window = "sun:06:00-sun:07:00"
apply_immediately = false
tags = {
Name = "${var.project_name}-redis"
}
}
#------------------------------------------------------------------------------
# S3 Buckets
#------------------------------------------------------------------------------
resource "aws_s3_bucket" "reports" {
bucket = "${var.project_name}-reports-${var.environment}-${random_id.suffix.hex}"
tags = {
Name = "${var.project_name}-reports"
}
}
resource "aws_s3_bucket_versioning" "reports" {
bucket = aws_s3_bucket.reports.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "reports" {
bucket = aws_s3_bucket.reports.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_lifecycle_configuration" "reports" {
bucket = aws_s3_bucket.reports.id
rule {
id = "archive-old-reports"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}
resource "aws_s3_bucket" "backups" {
bucket = "${var.project_name}-backups-${var.environment}-${random_id.suffix.hex}"
tags = {
Name = "${var.project_name}-backups"
}
}
resource "aws_s3_bucket_versioning" "backups" {
bucket = aws_s3_bucket.backups.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "backups" {
bucket = aws_s3_bucket.backups.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.main.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_lifecycle_configuration" "backups" {
bucket = aws_s3_bucket.backups.id
rule {
id = "backup-lifecycle"
status = "Enabled"
transition {
days = 30
storage_class = "GLACIER"
}
noncurrent_version_transition {
noncurrent_days = 7
storage_class = "GLACIER"
}
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
resource "aws_s3_bucket_public_access_block" "all" {
for_each = toset([aws_s3_bucket.reports.id, aws_s3_bucket.backups.id])
bucket = each.value
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
#------------------------------------------------------------------------------
# KMS Key
#------------------------------------------------------------------------------
resource "aws_kms_key" "main" {
description = "KMS key for ${var.project_name}"
deletion_window_in_days = 7
enable_key_rotation = true
tags = {
Name = "${var.project_name}-kms"
}
}
resource "aws_kms_alias" "main" {
name = "alias/${var.project_name}-${var.environment}"
target_key_id = aws_kms_key.main.key_id
}
#------------------------------------------------------------------------------
# Application Load Balancer
#------------------------------------------------------------------------------
resource "aws_lb" "main" {
name = "${var.project_name}-${var.environment}"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = module.vpc.public_subnets
enable_deletion_protection = var.environment == "production"
enable_http2 = true
access_logs {
bucket = aws_s3_bucket.logs.id
prefix = "alb-logs"
enabled = true
}
tags = {
Name = "${var.project_name}-alb"
}
}
resource "aws_lb_target_group" "backend" {
name = "${var.project_name}-backend-${var.environment}"
port = 8000
protocol = "HTTP"
vpc_id = module.vpc.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/api/v1/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
tags = {
Name = "${var.project_name}-backend-tg"
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.backend.arn
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
#------------------------------------------------------------------------------
# ECS Cluster & Service
#------------------------------------------------------------------------------
resource "aws_ecs_cluster" "main" {
name = "${var.project_name}-${var.environment}"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "${var.project_name}-ecs-cluster"
}
}
resource "aws_ecs_cluster_capacity_providers" "main" {
cluster_name = aws_ecs_cluster.main.name
capacity_providers = ["FARGATE", "FARGATE_SPOT"]
default_capacity_provider_strategy {
base = 1
weight = 1
capacity_provider = "FARGATE"
}
}
resource "aws_cloudwatch_log_group" "ecs" {
name = "/ecs/${var.project_name}-${var.environment}"
retention_in_days = 30
tags = {
Name = "${var.project_name}-ecs-logs"
}
}
resource "aws_ecs_task_definition" "backend" {
family = "${var.project_name}-backend"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.ecs_task_cpu
memory = var.ecs_task_memory
execution_role_arn = aws_iam_role.ecs_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
container_definitions = jsonencode([
{
name = "backend"
image = "${var.ecr_repository_url}:v1.0.0"
essential = true
portMappings = [
{
containerPort = 8000
protocol = "tcp"
}
]
environment = [
{
name = "APP_ENV"
value = var.environment
},
{
name = "APP_NAME"
value = var.project_name
},
{
name = "DEBUG"
value = "false"
},
{
name = "API_V1_STR"
value = "/api/v1"
},
{
name = "DATABASE_URL"
value = "postgresql+asyncpg://${aws_db_instance.main.username}:@${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
},
{
name = "REDIS_URL"
value = "redis://${aws_elasticache_replication_group.main.primary_endpoint_address}:6379/0"
},
{
name = "FRONTEND_URL"
value = "https://${var.domain_name}"
},
{
name = "S3_REPORTS_BUCKET"
value = aws_s3_bucket.reports.id
}
]
secrets = [
{
name = "JWT_SECRET_KEY"
valueFrom = aws_secretsmanager_secret.jwt_secret.arn
},
{
name = "DATABASE_PASSWORD"
valueFrom = aws_secretsmanager_secret.db_password.arn
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = aws_cloudwatch_log_group.ecs.name
awslogs-region = var.region
awslogs-stream-prefix = "backend"
}
}
healthCheck = {
command = ["CMD-SHELL", "curl -f http://localhost:8000/api/v1/health || exit 1"]
interval = 30
timeout = 5
retries = 3
startPeriod = 60
}
}
])
tags = {
Name = "${var.project_name}-backend-task"
}
}
resource "aws_ecs_service" "backend" {
name = "backend"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.backend.arn
desired_count = var.ecs_desired_count
launch_type = "FARGATE"
network_configuration {
subnets = module.vpc.private_subnets
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.backend.arn
container_name = "backend"
container_port = 8000
}
deployment_controller {
type = "ECS"
}
deployment_circuit_breaker {
enable = true
rollback = true
}
propagate_tags = "SERVICE"
tags = {
Name = "${var.project_name}-backend-service"
}
}
resource "aws_appautoscaling_target" "ecs" {
max_capacity = var.ecs_max_count
min_capacity = var.ecs_desired_count
resource_id = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.backend.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
resource "aws_appautoscaling_policy" "ecs_cpu" {
name = "${var.project_name}-cpu-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 70.0
scale_in_cooldown = 300
scale_out_cooldown = 60
}
}
resource "aws_appautoscaling_policy" "ecs_memory" {
name = "${var.project_name}-memory-autoscaling"
policy_type = "TargetTrackingScaling"
resource_id = aws_appautoscaling_target.ecs.resource_id
scalable_dimension = aws_appautoscaling_target.ecs.scalable_dimension
service_namespace = aws_appautoscaling_target.ecs.service_namespace
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
target_value = 75.0
scale_in_cooldown = 300
scale_out_cooldown = 60
}
}
#------------------------------------------------------------------------------
# CloudFront CDN
#------------------------------------------------------------------------------
resource "aws_cloudfront_distribution" "main" {
enabled = true
is_ipv6_enabled = true
comment = "${var.project_name} CDN"
default_root_object = "index.html"
price_class = "PriceClass_100"
aliases = [var.domain_name, "www.${var.domain_name}"]
origin {
domain_name = aws_lb.main.dns_name
origin_id = "ALB-${var.project_name}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
origin {
domain_name = aws_s3_bucket.reports.bucket_regional_domain_name
origin_id = "S3-${var.project_name}-reports"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "ALB-${var.project_name}"
forwarded_values {
query_string = true
headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"]
cookies {
forward = "all"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 0
max_ttl = 86400
}
ordered_cache_behavior {
path_pattern = "/reports/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.project_name}-reports"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = var.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
logging_config {
include_cookies = false
bucket = aws_s3_bucket.logs.bucket_domain_name
prefix = "cdn-logs/"
}
web_acl_id = aws_wafv2_web_acl.main.arn
tags = {
Name = "${var.project_name}-cdn"
}
}
resource "aws_cloudfront_origin_access_identity" "main" {
comment = "OAI for ${var.project_name}"
}
#------------------------------------------------------------------------------
# WAF Web ACL
#------------------------------------------------------------------------------
resource "aws_wafv2_web_acl" "main" {
name = "${var.project_name}-${var.environment}"
description = "WAF rules for ${var.project_name}"
scope = "CLOUDFRONT"
default_action {
allow {}
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSetMetric"
sampled_requests_enabled = true
}
}
rule {
name = "AWSManagedRulesSQLiRuleSet"
priority = 2
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesSQLiRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesSQLiRuleSetMetric"
sampled_requests_enabled = true
}
}
rule {
name = "AWSManagedRulesKnownBadInputsRuleSet"
priority = 3
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesKnownBadInputsRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesKnownBadInputsRuleSetMetric"
sampled_requests_enabled = true
}
}
rule {
name = "RateLimitRule"
priority = 4
action {
block {}
}
statement {
rate_based_statement {
limit = 2000
aggregate_key_type = "IP"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "RateLimitRuleMetric"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "${var.project_name}-waf-metric"
sampled_requests_enabled = true
}
tags = {
Name = "${var.project_name}-waf"
}
}
#------------------------------------------------------------------------------
# Route53 DNS
#------------------------------------------------------------------------------
resource "aws_route53_zone" "main" {
count = var.create_route53_zone ? 1 : 0
name = var.domain_name
tags = {
Name = "${var.project_name}-zone"
}
}
resource "aws_route53_record" "main" {
zone_id = var.create_route53_zone ? aws_route53_zone.main[0].zone_id : var.hosted_zone_id
name = var.domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = false
}
}
resource "aws_route53_record" "www" {
zone_id = var.create_route53_zone ? aws_route53_zone.main[0].zone_id : var.hosted_zone_id
name = "www.${var.domain_name}"
type = "CNAME"
ttl = 300
records = [var.domain_name]
}
resource "aws_route53_health_check" "main" {
fqdn = var.domain_name
port = 443
type = "HTTPS"
resource_path = "/api/v1/health"
failure_threshold = 3
request_interval = 30
tags = {
Name = "${var.project_name}-health-check"
}
}
#------------------------------------------------------------------------------
# IAM Roles & Policies
#------------------------------------------------------------------------------
resource "aws_iam_role" "ecs_execution" {
name = "${var.project_name}-ecs-execution-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-ecs-execution-role"
}
}
resource "aws_iam_role_policy_attachment" "ecs_execution_managed" {
role = aws_iam_role.ecs_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
resource "aws_iam_role_policy" "ecs_execution_secrets" {
name = "${var.project_name}-ecs-secrets-policy"
role = aws_iam_role.ecs_execution.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = [
aws_secretsmanager_secret.db_password.arn,
aws_secretsmanager_secret.jwt_secret.arn
]
}
]
})
}
resource "aws_iam_role" "ecs_task" {
name = "${var.project_name}-ecs-task-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-ecs-task-role"
}
}
resource "aws_iam_role_policy" "ecs_task_s3" {
name = "${var.project_name}-ecs-s3-policy"
role = aws_iam_role.ecs_task.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
]
Resource = [
"${aws_s3_bucket.reports.arn}/*",
"${aws_s3_bucket.backups.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.reports.arn,
aws_s3_bucket.backups.arn
]
}
]
})
}
resource "aws_iam_role" "rds_monitoring" {
name = "${var.project_name}-rds-monitoring-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "monitoring.rds.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-rds-monitoring-role"
}
}
resource "aws_iam_role_policy_attachment" "rds_monitoring" {
role = aws_iam_role.rds_monitoring.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}
#------------------------------------------------------------------------------
# Secrets Manager
#------------------------------------------------------------------------------
resource "random_password" "jwt_secret" {
length = 64
special = false
}
resource "aws_secretsmanager_secret" "jwt_secret" {
name = "${var.project_name}/${var.environment}/jwt-secret"
description = "JWT signing secret for ${var.project_name}"
recovery_window_in_days = 7
tags = {
Name = "${var.project_name}-jwt-secret"
}
}
resource "aws_secretsmanager_secret_version" "jwt_secret" {
secret_id = aws_secretsmanager_secret.jwt_secret.id
secret_string = random_password.jwt_secret.result
}
#------------------------------------------------------------------------------
# S3 Logs Bucket
#------------------------------------------------------------------------------
resource "aws_s3_bucket" "logs" {
bucket = "${var.project_name}-logs-${var.environment}-${random_id.suffix.hex}"
tags = {
Name = "${var.project_name}-logs"
}
}
resource "aws_s3_bucket_policy" "logs" {
bucket = aws_s3_bucket.logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::127311923021:root" # us-east-1 ELB account
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.logs.arn}/alb-logs/*"
}
]
})
}
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
id = "expire-logs"
status = "Enabled"
expiration {
days = 90
}
}
}