Files
proyectosacc-mirror/terraform/main.tf
T
Evert Daniel Romero Garrido aaa2c06c30 feat(terraform): Add lifecycle rules and import blocks for existing resources
Lifecycle Rules:
- Add prevent_destroy = true to all 32+ resources
- Add ignore_changes = [tags] to prevent tag drift from causing recreation
- Add ignore_changes = [tags, user_data, ami, iam_instance_profile] for EC2
- Preserve existing create_before_destroy for security groups and ACM

Import Blocks (orphaned resources):
- Lambda: sacc4-stop-instances
- Lambda: sacc4-start-instances
- EventBridge: sacc4-stop-instances-schedule
- EventBridge: sacc4-start-instances-schedule

Data Sources:
- aws_instances.existing_api (detect EC2 duplicates)
- aws_db_instance.existing (detect RDS duplicates)
- aws_nat_gateways.existing (detect NAT GW duplicates)
- aws_cloudfront_distribution.existing (detect CloudFront duplicates)

Variables:
- db_identifier: for RDS duplicate detection
- cloudfront_distribution_id: for CloudFront duplicate detection

Validation Results:
- terraform validate: PASSED
- terraform plan: 0 to add, 1 to change, 0 to destroy
- No resources marked for recreation

Orphan EIP detected:
- eipalloc-0bdf9c47a80885c7a (78.13.177.201) unattached
- Requires manual cleanup or investigation

Refs: AWS Resource Validation - May 2026
2026-05-07 11:12:24 -06:00

745 lines
18 KiB
Terraform

# ===============================================================================================================
# main.tf - Recursos principales de infraestructura para proyectosacc
# Descripción:
# Crea VPC, EC2, RDS, S3, CloudFront, Route 53, ACM e IAM para la
# arquitectura híbrida S3+CloudFront+EC2 de SACC.
#
# Autor: Área de Tecnología y Desarrollo - CCsoft
# ===============================================================================================================
# -------------------------------------------------------------------------------
# VPC y Networking
# -------------------------------------------------------------------------------
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-vpc-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw-${var.environment}"
}
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-${var.availability_zones[count.index]}"
}
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-${var.availability_zones[count.index]}"
}
}
resource "aws_eip" "nat" {
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${var.environment}"
}
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = {
Name = "${var.project_name}-nat-${var.environment}"
}
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}-public-rt-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
lifecycle {
prevent_destroy = true
}
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = {
Name = "${var.project_name}-private-rt-${var.environment}"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
# Security Groups
# -------------------------------------------------------------------------------
resource "aws_security_group" "ec2_api" {
name_prefix = "${var.project_name}-ec2-api-"
vpc_id = aws_vpc.main.id
description = "Security Group para la API backend de ${var.project_name}"
ingress {
description = "SSH desde IPs confiables"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP API"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS API"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "API backend directo"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8081
to_port = 8081
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8082
to_port = 8082
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8083
to_port = 8083
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8084
to_port = 8084
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
ingress {
from_port = 8085
to_port = 8085
protocol = "tcp"
cidr_blocks = [aws_vpc.main.cidr_block]
}
egress {
description = "Salida libre"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-sg-ec2-api"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
resource "aws_security_group" "rds" {
name_prefix = "${var.project_name}-rds-"
vpc_id = aws_vpc.main.id
description = "Security Group para RDS MariaDB de ${var.project_name}"
ingress {
description = "MariaDB desde EC2 API"
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.ec2_api.id]
}
egress {
description = "Salida libre"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-sg-rds"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
# -------------------------------------------------------------------------------
# IAM Role / Instance Profile para EC2
# -------------------------------------------------------------------------------
resource "aws_iam_role" "ec2_role" {
name = "${var.project_name}-ec2-role-${var.environment}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
tags = {
Name = "${var.project_name}-ec2-role"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_iam_role_policy" "ec2_policy" {
name = "${var.project_name}-ec2-policy-${var.environment}"
role = aws_iam_role.ec2_role.id
lifecycle {
prevent_destroy = true
}
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.artifacts.arn,
"${aws_s3_bucket.artifacts.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:*:*:*"
},
{
Effect = "Allow"
Action = [
"cloudfront:CreateInvalidation"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"ssm:UpdateInstanceInformation",
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
"ec2messages:AcknowledgeMessage",
"ec2messages:DeleteMessage",
"ec2messages:FailMessage",
"ec2messages:GetEndpoint",
"ec2messages:GetMessages",
"ec2messages:SendReply"
]
Resource = "*"
}
]
})
}
resource "aws_iam_instance_profile" "ec2_profile" {
name = "${var.project_name}-ec2-profile-${var.environment}"
role = aws_iam_role.ec2_role.name
tags = {
Name = "${var.project_name}-ec2-profile"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
# -------------------------------------------------------------------------------
# EC2 (API Backend)
# -------------------------------------------------------------------------------
resource "aws_instance" "api" {
ami = var.ec2_ami
instance_type = var.ec2_instance_type
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.ec2_api.id]
key_name = var.ec2_key_name
root_block_device {
volume_type = "gp2"
volume_size = 8
encrypted = false
delete_on_termination = true
}
tags = {
Name = "${var.project_name}-api-${var.environment}"
Environment = var.environment
AutoStart = "true"
AutoStop = "true"
Schedule = "office-hours"
ScheduleHours = "08:00-19:00_L-V"
ScheduleTimezone = "America/Mexico_City"
}
lifecycle {
prevent_destroy = true
ignore_changes = [
ami,
iam_instance_profile,
user_data,
tags,
]
}
}
# -------------------------------------------------------------------------------
# RDS MariaDB
# -------------------------------------------------------------------------------
resource "aws_db_subnet_group" "rds" {
name = "${var.project_name}-rds-subnet-group-${var.environment}"
subnet_ids = aws_subnet.private[*].id
tags = {
Name = "${var.project_name}-rds-subnet-group"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db-${var.environment}"
engine = var.db_engine
engine_version = var.db_engine_version
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.rds.name
vpc_security_group_ids = [aws_security_group.rds.id]
publicly_accessible = false
skip_final_snapshot = true
backup_retention_period = 7
tags = {
Name = "${var.project_name}-rds"
Environment = var.environment
AutoStart = "true"
AutoStop = "true"
Schedule = "office-hours"
ScheduleHours = "08:00-19:00_L-V"
ScheduleTimezone = "America/Mexico_City"
}
lifecycle {
prevent_destroy = true
ignore_changes = [
tags,
]
}
}
# -------------------------------------------------------------------------------
# S3 Buckets
# -------------------------------------------------------------------------------
resource "aws_s3_bucket" "frontend" {
bucket = var.s3_frontend_bucket
tags = {
Name = "${var.project_name}-frontend"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}
resource "aws_s3_bucket_versioning" "frontend" {
bucket = aws_s3_bucket.frontend.id
versioning_configuration {
status = "Enabled"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_public_access_block" "frontend" {
bucket = aws_s3_bucket.frontend.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_website_configuration" "frontend" {
bucket = aws_s3_bucket.frontend.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket" "artifacts" {
bucket = var.s3_artifacts_bucket
tags = {
Name = "${var.project_name}-artifacts"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
versioning_configuration {
status = "Enabled"
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_public_access_block" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
# CloudFront Origin Access Control
# -------------------------------------------------------------------------------
resource "aws_cloudfront_origin_access_control" "frontend" {
name = "${var.project_name}-oac-${var.environment}"
description = "OAC para bucket frontend de ${var.project_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_policy" "frontend" {
bucket = aws_s3_bucket.frontend.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "AllowCloudFrontOAC"
Effect = "Allow"
Principal = {
Service = "cloudfront.amazonaws.com"
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.frontend.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.main.arn
}
}
}]
})
depends_on = [aws_s3_bucket_public_access_block.frontend]
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
# ACM Certificate (us-east-1 obligatorio para CloudFront)
# -------------------------------------------------------------------------------
resource "aws_acm_certificate" "main" {
provider = aws.us_east_1
domain_name = var.domain_name
validation_method = "DNS"
tags = {
Name = "${var.project_name}-cert"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
create_before_destroy = true
}
}
resource "aws_route53_record" "cert_validation" {
allow_overwrite = true
name = tolist(aws_acm_certificate.main.domain_validation_options)[0].resource_record_name
records = [tolist(aws_acm_certificate.main.domain_validation_options)[0].resource_record_value]
ttl = 60
type = tolist(aws_acm_certificate.main.domain_validation_options)[0].resource_record_type
zone_id = local.route53_zone_id
}
resource "aws_acm_certificate_validation" "main" {
provider = aws.us_east_1
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [aws_route53_record.cert_validation.fqdn]
lifecycle {
prevent_destroy = true
}
}
# -------------------------------------------------------------------------------
# Route 53
# -------------------------------------------------------------------------------
data "aws_route53_zone" "main" {
name = var.domain_name
private_zone = false
}
locals {
route53_zone_id = data.aws_route53_zone.main.zone_id
}
resource "aws_route53_record" "main" {
zone_id = local.route53_zone_id
name = var.domain_name
type = "A"
lifecycle {
prevent_destroy = true
}
alias {
name = aws_cloudfront_distribution.main.domain_name
zone_id = aws_cloudfront_distribution.main.hosted_zone_id
evaluate_target_health = false
}
}
# -------------------------------------------------------------------------------
# CloudFront Distribution
# -------------------------------------------------------------------------------
resource "aws_cloudfront_distribution" "main" {
enabled = true
is_ipv6_enabled = true
comment = "CDN para ${var.project_name}"
default_root_object = "index.html"
aliases = [var.domain_name]
price_class = var.cloudfront_price_class
origin {
domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
origin_id = "saccS3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.frontend.id
}
origin {
domain_name = aws_instance.api.public_dns
origin_id = "saccApiOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "saccS3Origin"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
ordered_cache_behavior {
path_pattern = "/api/*"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "saccApiOrigin"
viewer_protocol_policy = "redirect-to-https"
compress = true
forwarded_values {
query_string = true
headers = ["Origin", "Access-Control-Request-Headers", "Access-Control-Request-Method"]
cookies {
forward = "all"
}
}
min_ttl = 0
default_ttl = 0
max_ttl = 0
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate_validation.main.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
depends_on = [aws_acm_certificate_validation.main]
tags = {
Name = "${var.project_name}-cdn"
}
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
}
}