DEV Community

Edgar (Homz) Macias
Edgar (Homz) Macias

Posted on • Originally published at github.com

AWS Modulo 1: Terraform & Remote Backend

📚 Serie: AWS Zero to Architect - Módulo 1

⏱️ Tiempo de lectura: 15 minutos

💻 Tiempo de implementación: 60 minutos

En el Módulo 0 aseguramos nuestra cuenta AWS. Ahora viene la pregunta crítica: ¿cómo creamos infraestructura sin hacer clic en consolas como si fueran videojuegos?

La respuesta: Infrastructure as Code con Terraform.


🎯 Lo que aprenderás

  • ✅ Por qué ClickOps es peligroso (y caro)
  • ✅ Qué es Infrastructure as Code
  • ✅ El riesgo crítico del archivo terraform.tfstate
  • ✅ Cómo crear un Remote Backend seguro (S3 + DynamoDB)
  • ✅ Migrar el state sin perder datos
  • ✅ Implementar locking para equipos

⚠️ Historia de Terror: Por Qué Este Tutorial Existe

"Hice commit de terraform.tfstate sin darme cuenta. Contenía credenciales de mi base de datos RDS. En 10 minutos, alguien accedió a mi DB, la copió completa, la borró, y dejó una nota de rescate pidiendo Bitcoin. Perdí datos de 500 clientes."

— Desarrollador anónimo, Reddit 2023

Estadísticas reales:

  • Tiempo promedio de detección por bots: 5-8 minutos
  • Costo promedio de error en AWS: $1,000 - $15,000
  • Días para resolver una cuenta comprometida: 3-7 días

Este tutorial te enseña a evitar estos errores desde el día 1.


🤔 ClickOps vs Infrastructure as Code

El Problema: ClickOps

Imagina que tu compañero de equipo te dice: "Necesito el mismo bucket S3 que creaste para producción"

Enfoque ClickOps (tradicional):

Tú: "Uhh... creo que hice esto:
     1. AWS Console → S3 → Create bucket
     2. Clic en... ¿Versioning? Sí, creo que sí
     3. CORS lo configuré, pero no recuerdo los valores exactos
     4. ¿Encryption? Mmm... tal vez
     5. ¿Block Public Access? Espero que sí..."

Compañero: "..."
Enter fullscreen mode Exit fullscreen mode

Resultado:

  • 🔴 Buckets inconsistentes entre dev/prod
  • 🔴 Configuraciones olvidadas = bugs en producción
  • 🔴 Sin documentación real
  • 🔴 "En mi máquina funciona" pero nadie sabe por qué

La Solución: Infrastructure as Code

Con Terraform:

resource "aws_s3_bucket" "app_storage" {
  bucket = "my-app-${var.environment}-bucket"

  tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

resource "aws_s3_bucket_versioning" "app_storage" {
  bucket = aws_s3_bucket.app_storage.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_public_access_block" "app_storage" {
  bucket = aws_s3_bucket.app_storage.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
Enter fullscreen mode Exit fullscreen mode

Ahora tu compañero:

git clone tu-repo
terraform apply
# ✅ Bucket idéntico creado en 30 segundos
Enter fullscreen mode Exit fullscreen mode

Beneficios de IaC

ClickOps 🙁 Infrastructure as Code 😎
Instrucciones verbales Código versionado en Git
Cada quien hace sus clics terraform apply = reproducible
"Creo que configuré X..." git diff muestra cambios exactos
Sin historial git log = auditoría completa
Documentación desactualizada El código ES la documentación

🔥 El Peligro del terraform.tfstate

Aquí viene la parte que muchos tutoriales omiten y que termina costando miles de dólares.

¿Qué es el State File?

Cuando ejecutas Terraform, crea un archivo terraform.tfstate que mapea tu código a los recursos reales en AWS.

Ejemplo de contenido:

{
  "resources": [
    {
      "type": "aws_db_instance",
      "name": "main",
      "instances": [{
        "attributes": {
          "endpoint": "prod-db.abc123.us-east-1.rds.amazonaws.com",
          "username": "admin",
          "password": "SUPER_SECRET_PASSWORD_123",  // ⚠️⚠️⚠️
          "port": 5432
        }
      }]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

¿Ves el problema?

El state file contiene:

  • Contraseñas en texto plano
  • Access Keys de IAM
  • Endpoints privados de bases de datos
  • IDs de recursos sensibles
  • Configuraciones de seguridad

El Escenario de Pesadilla

Desarrollador: git add .
Desarrollador: git commit -m "Add infrastructure"
Desarrollador: git push

5 minutos después...

Bot automatizado:
  1. Escanea GitHub buscando "terraform.tfstate"
  2. Encuentra tu repo público
  3. Extrae contraseñas
  4. Crea 50 instancias EC2 GPU en tu cuenta

Tu factura: $15,000
Tu expresión: 😱
Enter fullscreen mode Exit fullscreen mode

"Pero lo pondré en .gitignore"

Problema 1: Pérdida de Datos

# Tu laptop se rompe
# El state se perdió
# Terraform ya no sabe qué recursos existen
# No puedes hacer cambios sin "reimportar" todo manualmente
Enter fullscreen mode Exit fullscreen mode

Problema 2: Trabajo en Equipo

10:00 AM - Dev A: terraform apply
          → Crea base de datos
          → terraform.tfstate actualizado (en su laptop)

10:30 AM - Dev B: git pull (el .tfstate está en .gitignore, no se descarga)
          → terraform apply
          → Terraform no sabe que existe la DB
          → "Esta DB no está en mi state, la borraré"

Resultado: DATA LOSS 💀
Enter fullscreen mode Exit fullscreen mode

🛡️ Remote Backend: La Solución Profesional

Arquitectura

Desarrolladores
    ↓
DynamoDB (Locking)
    ↓
S3 Bucket (State Storage)
• Encriptado AES-256
• Versionado habilitado
• Privado
Enter fullscreen mode Exit fullscreen mode

Componentes

1. S3 Bucket

  • Almacena el state file
  • Versionado = rollback si algo falla
  • Encriptación = seguridad en reposo
  • Bloqueado públicamente = sin acceso desde internet

2. DynamoDB Table

  • Implementa "locking" (bloqueo)
  • Previene que 2 personas ejecuten terraform simultáneamente
  • Evita race conditions

Flujo de Locking (Magia que evita desastres)

Dev A ejecuta: terraform apply
  ↓
Terraform escribe en DynamoDB: "Lock adquirido por Dev A"
  ↓
Dev B ejecuta: terraform apply (al mismo tiempo)
  ↓
DynamoDB: "Error, ya hay un lock activo"
  ↓
Terraform: "State locked, esperando a que Dev A termine..."
  ↓
Dev A termina → Libera el lock
  ↓
Dev B puede continuar ✅
Enter fullscreen mode Exit fullscreen mode

Resultado: Sin conflictos, sin data loss.


💻 Implementación (Paso a Paso)

Estructura del Proyecto

go-hexagonal-auth/
├── terraform/
│   ├── .gitignore           # ← CRÍTICO
│   └── backend/
│       ├── main.tf          # Recursos S3 + DynamoDB
│       ├── variables.tf     # Variables
│       └── outputs.tf       # Outputs
Enter fullscreen mode Exit fullscreen mode

Código del Backend

terraform/backend/main.tf

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# S3 Bucket para el state
resource "aws_s3_bucket" "terraform_state" {
  bucket = "${var.project_name}-terraform-state-${var.aws_account_id}"

  lifecycle {
    prevent_destroy = true  # Protección contra borrado accidental
  }
}

# Habilitar versionado
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# Encriptación
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 = "AES256"
    }
  }
}

# Bloquear TODO acceso público
resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# DynamoDB para locking
resource "aws_dynamodb_table" "terraform_locks" {
  name         = "${var.project_name}-terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  lifecycle {
    prevent_destroy = true
  }
}
Enter fullscreen mode Exit fullscreen mode

Ejecución

# 1. Exportar variables
export TF_VAR_aws_account_id=$(aws sts get-caller-identity --query Account --output text)
export TF_VAR_aws_region="us-east-1"
export TF_VAR_project_name="go-hexagonal-auth"

# 2. Crear recursos
cd terraform/backend
terraform init
terraform plan
terraform apply  # Escribir: yes
Enter fullscreen mode Exit fullscreen mode

Recursos creados:

  • ✅ S3 Bucket (encriptado, versionado, privado)
  • ✅ DynamoDB Table (locking habilitado)

🔄 Migrar el State a S3

1. Crear terraform/backend.tf

terraform {
  backend "s3" {
    bucket         = "tu-proyecto-terraform-state-123456789012"
    key            = "terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "tu-proyecto-terraform-locks"
    encrypt        = true
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Migrar

cd terraform
terraform init -migrate-state
# Responder: yes
Enter fullscreen mode Exit fullscreen mode

Output esperado:

Successfully configured the backend "s3"!
Terraform will automatically use this backend.
Enter fullscreen mode Exit fullscreen mode

3. Verificar

# State en S3
aws s3 ls s3://tu-proyecto-terraform-state-123456789012/
# Output: terraform.tfstate

# NO en local
ls terraform.tfstate
# Output: No such file or directory ✅
Enter fullscreen mode Exit fullscreen mode

✅ Testing: Verificar que Funciona

Test de Locking

Terminal 1:

terraform console  # Adquiere un lock
Enter fullscreen mode Exit fullscreen mode

Terminal 2:

terraform plan
# Error: State locked by Terminal 1 ✅
Enter fullscreen mode Exit fullscreen mode

Cierra Terminal 1 → Terminal 2 funciona ✅


💰 Costos

Recurso Costo
S3 Bucket (state < 1MB) $0.00
DynamoDB (pay-per-request) $0.00
Total mensual < $0.01

Traducción: Prácticamente gratis.


📚 Lo Que Aprendiste

  • ✅ Infrastructure as Code > ClickOps
  • terraform.tfstate contiene secretos → NUNCA en Git
  • ✅ Remote Backend (S3 + DynamoDB) = seguridad + locking
  • ✅ Versionado en S3 = rollback si algo falla
  • ✅ Locking = trabajo en equipo sin desastres

🚀 Próximos Pasos

En el Módulo 2 crearemos:

  • IAM Roles con permisos específicos (Least Privilege)
  • Tu primera Lambda function en Go
  • DynamoDB table para la app
  • API Gateway para exponer la Lambda

📦 Código Completo

Todo el código de este tutorial está en GitHub:

GitHub logo edgar-macias-se / go-hexagonal-auth

Production-ready Authentication Microservice in Go. Implements Hexagonal Architecture, JWT, Redis Blacklisting, and Rate Limiting.

🛡️ Go Secure Authentication Service

Un microservicio de autenticación robusto, escalable y listo para producción, escrito en Go siguiendo principios de Arquitectura Hexagonal.

Diseñado con la seguridad como prioridad, implementando las mejores prácticas de OWASP para la gestión de identidad, sesiones y protección contra ataques.


🚀 Key Security Highlights

Este no es solo un login básico. Este proyecto implementa capas de defensa en profundidad:

  • 🔒 Arquitectura Hexagonal (Ports & Adapters): Desacoplamiento total entre la lógica de negocio, la base de datos y la API HTTP. Código testable y mantenible.
  • 🔑 Estrategia de Tokens Duales (JWT):
    • Access Token (15 min): JWT firmado (HS256) de vida corta para minimizar riesgos en caso de robo.
    • Refresh Token (7 días): Token opaco rotativo almacenado en BD para renovar sesiones sin exponer credenciales.
  • 🛡️ Protección contra Fuerza Bruta (Rate Limiting): Middleware distribuido usando Redis. Bloquea IPs/Usuarios tras 5 intentos fallidos por 15 minutos.




Carpeta: terraform/backend/


🙋 Preguntas Frecuentes

Q: ¿Qué pasa si borro el state de S3 por error?

A: El versionado te salva. Puedes restaurar versiones anteriores desde la consola de S3.

Q: ¿Puedo usar el mismo backend para dev y prod?

A: No recomendado. Usa buckets separados o diferentes key paths.

Q: ¿Funciona con Terraform Cloud?

A: Terraform Cloud tiene su propio backend. Este tutorial es para self-hosted.


💬 Comparte Tu Experiencia

¿Te salvó de cometer un error costoso? ¿Encontraste algún problema?

Déjalo en los comentarios 👇


🔗 Conecta


Serie: AWS Zero to Architect

Siguiente: Módulo 2 - IAM Roles y Lambda Functions


Top comments (0)