# =============================================================================================================== # 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}" } } 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}" } } 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 } 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}" } } 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 } # ------------------------------------------------------------------------------- # 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"] # SSH desde cualquier IP (pipeline Bitbucket + administración) } 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] } 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 { 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 { 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" } } resource "aws_iam_role_policy" "ec2_policy" { name = "${var.project_name}-ec2-policy-${var.environment}" role = aws_iam_role.ec2_role.id 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 = "*" } ] }) } 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" } } # ------------------------------------------------------------------------------- # 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] iam_instance_profile = aws_iam_instance_profile.ec2_profile.name key_name = var.ec2_key_name root_block_device { volume_type = "gp3" volume_size = var.ec2_root_volume_size encrypted = true delete_on_termination = true } user_data = base64encode(templatefile("${path.module}/user-data.sh", { pipeline_public_key = var.pipeline_public_key })) tags = { Name = "${var.project_name}-api-${var.environment}" Environment = var.environment } } # ------------------------------------------------------------------------------- # 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" } } 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 } } # ------------------------------------------------------------------------------- # S3 Buckets # ------------------------------------------------------------------------------- resource "aws_s3_bucket" "frontend" { bucket = var.s3_frontend_bucket tags = { Name = "${var.project_name}-frontend" } } resource "aws_s3_bucket_versioning" "frontend" { bucket = aws_s3_bucket.frontend.id versioning_configuration { status = "Enabled" } } 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 } resource "aws_s3_bucket_website_configuration" "frontend" { bucket = aws_s3_bucket.frontend.id index_document { suffix = "index.html" } error_document { key = "index.html" } } resource "aws_s3_bucket" "artifacts" { bucket = var.s3_artifacts_bucket tags = { Name = "${var.project_name}-artifacts" } } resource "aws_s3_bucket_versioning" "artifacts" { bucket = aws_s3_bucket.artifacts.id versioning_configuration { status = "Enabled" } } 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 } # ------------------------------------------------------------------------------- # 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" } 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] } # ------------------------------------------------------------------------------- # 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 { 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] } # ------------------------------------------------------------------------------- # 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" 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" } }