El 60% de los Hacks en AWS Vienen de Aquí
📚 Serie: AWS Zero to Architect – Módulo 2
⏱️ Tiempo de lectura: 18 minutos
💻 Tiempo de implementación: 90 minutos
En los módulos anteriores configuramos AWS de forma segura y creamos infraestructura con Terraform. Ahora viene la parte que separa a los juniors de los seniors: IAM (Identity and Access Management).
🔥 Por Qué Deberías Preocuparte por IAM
Estadística que te hará sudar
60% de los incidentes de seguridad en AWS son por permisos mal configurados.
No por hackeo sofisticado. No por zero-days. Por dar permisos de más.
Historia Real: $47,000 en 3 Días
“Creé un rol IAM con `dynamodb:
en` porque ‘era más rápido’. Alguien comprometió mi Lambda. En 3 días:– Borraron 12 tablas de producción
– Crearon 200 tablas nuevas en 5 regiones
– Factura: $47,000
– Tiempo de recuperación: 2 semanas
– Clientes perdidos: 34″
— Dev anónimo, AWS re:Invent 2023
El costo de “era más rápido”.
🎯 Lo Que Vas a Aprender
- ✅ IAM Users vs Roles (y por qué confundirlos es peligroso)
- ✅ Cómo leer y escribir Policies sin morir en el intento
- ✅ Least Privilege: El principio de seguridad más importante
- ✅ Trust Relationships (el paso que todos olvidan)
- ✅ DynamoDB sin-erver (serverless) configurado profesionalmente
- ✅ Todo con Terraform (Infrastructure as Code)
🏢 IAM Explicado: La Analogía del Edificio
Imagina AWS como un edificio de oficinas:
👤 IAM Users = Empleados con Credenciales
Juan el Admin:
- Username: juan-admin
- Password: ••••••••
- Access Keys: AKIAIOSFODNN7EXAMPLE
Tiene credenciales PERMANENTES.
Entra todos los días con su tarjeta.
Uso: Personas que acceden a AWS (tú, tu equipo).
👔 IAM Roles = Uniformes con Permisos
Rol: "lambda-db-writer"
- NO tiene credenciales permanentes
- Lambda se lo "pone" temporalmente
- Credenciales válidas por 15 minutos
- Luego expiran automáticamente
Es como un chaleco que dice:
"Quien use esto puede escribir en la tabla 'users'"
Uso: Servicios de AWS (Lambda, EC2, ECS).
📜 IAM Policies = Las Reglas Escritas
{
"Effect": "Allow",
"Action": "dynamodb:PutItem",
"Resource": "arn:aws:dynamodb:*:*:table/users"
}
Traducción: “Permite escribir en la tabla ‘users'”
🔍 Anatomía de una Policy (Sin Marearte)
La Policy Más Simple del Mundo
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
En español: “Permite leer objetos del bucket ‘my-bucket'”
Desglose Línea por Línea
1. Version
"Version": "2012-10-17"
- Siempre usa esta versión (sí, dice 2012 pero es la actual)
- No es la versión de TU policy, es del formato
2. Effect
"Effect": "Allow" // o "Deny"
- Allow: Permitir
- Deny: Denegar (SIEMPRE gana sobre Allow)
Regla de oro: Sin Allow explícito = denegado.
3. Action
"Action": "dynamodb:PutItem"
- Formato:
servicio:operación - Ejemplos:
-
dynamodb:GetItem→ Leer item -
s3:PutObject→ Subir archivo -
logs:PutLogEvents→ Escribir logs -
dynamodb:*→ Todas las operaciones (⚠️ peligroso)
-
4. Resource
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/users"
- ARN (Amazon Resource Name) = Dirección única del recurso
- Puede usar wildcards:
*
🛡️ Least Privilege: La Regla de Oro
“Da solo los permisos que necesitas, NADA más.”
El Desastre: Permisos Excesivos
{
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "*"
}
Traducción: “Lambda puede hacer CUALQUIER COSA en CUALQUIER tabla”
¿Qué puede salir mal?
Si hackean tu Lambda:
✓ Borrar TODAS las tablas
✓ Crear 1000 tablas nuevas → $$$
✓ Leer datos de pagos, usuarios, admin
✓ Modificar datos críticos
✓ Exportar TODO a un bucket externo
Tiempo para detectarlo: 2-7 días
Daño: Irreversible en muchos casos
La Salvación: Least Privilege
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/auth-sessions"
}
Traducción: “Lambda puede SOLO leer y escribir en la tabla ‘auth-sessions'”
Si hackean tu Lambda:
Daño limitado:
✓ Solo pueden leer/escribir en 1 tabla
✗ NO pueden borrar la tabla
✗ NO pueden acceder a otras tablas
✗ NO pueden crear recursos nuevos
Impacto: Contenido y reversible
Tiempo de recuperación: Horas, no semanas
Comparación Visual
❌ Permisos Excesivos = Dar llaves del edificio completo
✅ Least Privilege = Dar llave de UNA oficina específica
🔑 Trust Relationships: El Paso Olvidado
¿Qué es un Trust Relationship?
Define QUIÉN puede usar (asumir) un rol.
Analogía: El rol es un chaleco con permisos. El trust relationship dice quién puede ponerse ese chaleco.
Trust Policy
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}]
}
Traducción: “Solo Lambda puede ponerse este chaleco”
¿Qué Pasa Sin Trust Relationship?
Error: The role defined for the function
cannot be assumed by Lambda.
Traducción: "Lambda no puede usar este rol"
Secuencia correcta:
1. Trust Relationship ✓
↓
2. ¿Lambda puede asumir el rol? → SÍ
↓
3. Permisos del rol se activan
↓
4. Lambda ejecuta con esos permisos
Sin trust relationship:
1. Intentas asignar rol a Lambda
↓
2. AWS rechaza: "Lambda no está en la lista de confianza"
↓
3. ERROR
💾 DynamoDB: Base de Datos Serverless
¿Por Qué DynamoDB?
Tradicional (RDS):
- Tienes que provisionar servidores
- Pagas 24/7 aunque no uses
- Escalado manual
- Backups manuales
Costo mínimo: ~$15/mes
DynamoDB:
- Sin servidores (serverless)
- Pagas SOLO por uso
- Escala automáticamente
- Backups automáticos
Costo mínimo: $0.00
Billing Modes
On-Demand (Recomendado para empezar)
billing_mode = "PAY_PER_REQUEST"
- Pagas por operación (~$1.25 por millón)
- Escalado automático infinito
- Ideal para: Dev, tráfico variable
Ejemplo de costo:
10,000 lecturas/día × 30 días = 300,000 ops/mes
Costo: 300,000 ÷ 1,000,000 × $1.25 = $0.375/mes
Provisioned (Para producción estable)
billing_mode = "PROVISIONED"
read_capacity_units = 5
write_capacity_units = 5
- Pagas por capacidad reservada
- Más económico si el tráfico es predecible
- Requiere planificación
TTL: Auto-Limpieza Gratis
ttl {
attribute_name = "expires_at"
enabled = true
}
Uso: Sesiones, cachés, datos temporales
Ejemplo:
{
"session_id": "abc-123",
"expires_at": 1735689600 // Unix timestamp
}
DynamoDB borra automáticamente este item después de esa fecha. Gratis.
💻 Implementación: El Código que Importa
Estructura
terraform/
├── variables.tf (variables globales)
├── iam.tf (roles y policies) ⭐
├── dynamodb.tf (tabla)
└── outputs.tf (outputs)
IAM Role + Policies
# Rol para Lambda
resource "aws_iam_role" "lambda_execution" {
name = "go-hexagonal-auth-dev-lambda-role"
# Trust Relationship: Solo Lambda
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
Action = "sts:AssumeRole"
}]
})
}
# Policy para DynamoDB (Least Privilege)
resource "aws_iam_policy" "lambda_dynamodb" {
name = "lambda-dynamodb-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem"
]
# Solo esta tabla específica
Resource = aws_dynamodb_table.auth_sessions.arn
}]
})
}
# Asociar policy al rol
resource "aws_iam_role_policy_attachment" "lambda_dynamodb" {
role = aws_iam_role.lambda_execution.name
policy_arn = aws_iam_policy.lambda_dynamodb.arn
}
DynamoDB Table
resource "aws_dynamodb_table" "auth_sessions" {
name = "auth-sessions"
billing_mode = "PAY_PER_REQUEST" # Pay-per-use
hash_key = "session_id"
attribute {
name = "session_id"
type = "S" # String
}
# Auto-eliminar sesiones expiradas
ttl {
attribute_name = "expires_at"
enabled = true
}
# Encryption habilitada por defecto
server_side_encryption {
enabled = true
}
}
Ejecución
# 1. Exportar variables
export TF_VAR_aws_account_id=$(aws sts get-caller-identity --query Account --output text)
# 2. Aplicar
cd terraform
terraform init
terraform apply
Recursos creados: 6
- 1 IAM Role
- 2 IAM Policies (DynamoDB + Logs)
- 2 Policy Attachments
- 1 DynamoDB Table
🧪 Testing: Verificar que Funciona
Test 1: Insertar Sesión
# Crear sesión
aws dynamodb put-item
--table-name auth-sessions
--item '{
"session_id": {"S": "test-123"},
"user_id": {"S": "user-456"},
"expires_at": {"N": "1735689600"}
}'
# Leer sesión
aws dynamodb get-item
--table-name auth-sessions
--key '{"session_id": {"S": "test-123"}}'
Test 2: Verificar Trust Relationship
aws iam get-role
--role-name go-hexagonal-auth-dev-lambda-role
--query 'Role.AssumeRolePolicyDocument'
Debe mostrar Lambda en “Principal”.
🆘 Troubleshooting: Problemas Comunes
“Tabla no aparece en Console”
Problema: Console en región incorrecta
Solución:
- AWS Console → Arriba derecha
- Cambiar región a US East (N. Virginia)
- Refrescar
“Put-Item no inserta datos”
Problema: JSON mal formateado en archivo
Solución: Usar sintaxis inline
aws dynamodb put-item
--table-name auth-sessions
--item '{"session_id": {"S": "test"}}'
💰 Costos Reales
| Recurso | Costo |
|---|---|
| IAM Role + Policies | $0.00 (gratis) |
| DynamoDB (sin uso) | $0.00 |
| DynamoDB (1M ops/mes) | ~$0.25 |
Total con uso ligero: < $0.50/mes
🎓 Lo Que Aprendiste
- ✅ IAM Roles vs Users (y cuándo usar cada uno)
- ✅ Policies con Least Privilege (no “todo para todos”)
- ✅ Trust Relationships (prerequisito olvidado)
- ✅ DynamoDB serverless (pay-per-use)
- ✅ TTL para auto-limpieza (gratis)
- ✅ Todo con Terraform (reproducible)
🚀 Próximos Pasos
Módulo 3: Lambda Functions con Go
Vamos a crear:
- Lambda function en Go
- Compilar para AWS (ARM64)
- Deploy con Terraform
- API Gateway
- Testing end-to-end
📦 Código Completo
Todo el código está en GitHub:

edgar-macias-se
/
aws_road
Learn aws in a secured way and with the best practices
🚀 AWS Engineering Repository
Author: Edgar Macías — Senior Software Engineer & AppSec Advocate
Este repositorio documenta y consolida mi proceso profesional de adopción, dominio y aplicación de Amazon Web Services (AWS) desde la perspectiva de un ingeniero de software sénior, con enfoque en:
- Arquitectura moderna
- Seguridad desde el diseño (Security by Design)
- Infraestructura como código (Terraform)
- Buenas prácticas de ingeniería en la nube
- Desarrollo de servicios serverless (Go + AWS Lambda)
- Gobernanza, control de costos y operación segura
El objetivo no es ser un “roadmap de aprendizaje”, sino construir un cuerpo de trabajo técnico verificable, con estándares profesionales y material que refleje la forma en que implemento, documento y despliego soluciones en AWS.
📘 Propósito del Repositorio
Este proyecto funciona como un laboratorio estructurado, donde desarrollo:
- Componentes reales listos para producción
- Arquitecturas modulares basadas en principios hexagonales
- Configuración segura y reproducible de infraestructura
- Adaptadores, servicios y…
Carpeta: terraform/
💬 Tu Turno
¿Has tenido un susto con permisos en AWS? Cuenta tu historia en los comentarios 👇
¿Preguntas sobre IAM? Pregunta, respondo todo.
🔗 Conecta
- GitHub: @edgar-macias-se
- LinkedIn: edgar-macias-devcybsec
- Website: edgarmacias.com/es
- Dev.to: @emp_devcybsec
Serie: AWS Zero to Architect
Anterior: Módulo 1 – Terraform & Remote Backend
Siguiente: Módulo 3 – Lambda Functions con Go (próximamente)
💡 Tip: Si este tutorial te salvó de un error costoso, compártelo con tu equipo. El mejor seguro contra hackeos es el conocimiento.
