📚 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.tfstatesin 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: "..."
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
}
Ahora tu compañero:
git clone tu-repo
terraform apply
# ✅ Bucket idéntico creado en 30 segundos
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
}
}]
}
]
}
¿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: 😱
"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
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 💀
🛡️ Remote Backend: La Solución Profesional
Arquitectura
Desarrolladores
↓
DynamoDB (Locking)
↓
S3 Bucket (State Storage)
• Encriptado AES-256
• Versionado habilitado
• Privado
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 ✅
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
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
}
}
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
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
}
}
2. Migrar
cd terraform
terraform init -migrate-state
# Responder: yes
Output esperado:
Successfully configured the backend "s3"!
Terraform will automatically use this backend.
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 ✅
✅ Testing: Verificar que Funciona
Test de Locking
Terminal 1:
terraform console # Adquiere un lock
Terminal 2:
terraform plan
# Error: State locked by Terminal 1 ✅
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.tfstatecontiene 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:
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
- GitHub: Edgar Macias SE
- Twitter: @emp_devcybsec
- LinkedIn: Edgar Macías
Serie: AWS Zero to Architect
Siguiente: Módulo 2 - IAM Roles y Lambda Functions
Top comments (0)