Seguridad y costes en S3: donde se separa un usuario de un Cloud Engineer
Amazon S3 es tan fiable que invita a confiarse. Y ahí empieza el problema.
La mayoría de incidentes relacionados con S3 no vienen de fallos del servicio. Vienen de malas decisiones humanas:
- 🔓 Permisos demasiado abiertos
- 🌍 Buckets públicos “sin querer”
- ⏳ Lifecycle inexistente
- 💸 Facturas que crecen sin que nadie sepa por qué
Este artículo va de responsabilidad real. Porque cuando gestionas S3 en producción, no solo almacenas datos: proteges información y dinero.
He visto empresas facturadas $50,000 al mes por buckets mal configurados. He investigado leaks de datos que empezaron con un simple “Block Public Access: Off” en un entorno de testing que pasó a producción.
La diferencia entre un usuario de S3 y un Cloud Engineer no está en saber qué es un bucket. Está en diseñar sistemas que no colapsan a las 3 AM ni aparecen en las noticias.
El modelo de seguridad de S3: quién manda aquí
En Amazon Web Services S3, la seguridad no vive en un solo sitio. Vive en varias capas, y entender cuál tiene prioridad es crítico.
Las capas principales:
Modelo de seguridad S3:
1. IAM Policies:
- Quién puede hacer qué
- Asociadas a usuarios, roles, grupos
- Scope: identity-based
2. Bucket Policies:
- Qué puede hacerse con este bucket
- Condiciones: IP, VPC, encriptación
- Scope: resource-based
3. ACLs (Access Control Lists):
- Legado, evita si puedes
- Granularidad por objeto
- Casos de uso: compartir con otras cuentas AWS
4. Block Public Access:
- Override de emergencia
- Última línea de defensa
- Debería estar SIEMPRE activado
Regla de oro:
El permiso más restrictivo siempre gana.
Muchos leaks históricos han ocurrido no porque alguien “abriera” un bucket, sino porque no entendía qué policy estaba aplicando realmente.
Caso real de exposición accidental:
Escenario:
- Bucket con Block Public Access: OFF (testing)
- Bucket Policy: restrictiva (solo VPC)
- ACL: por defecto
- Problema: desarrollador añade ACL pública a un objeto
Resultado:
✅ Block Public Access hubiera bloqueado esto
❌ Sin él: objeto expuesto públicamente
Exposición: 3 días hasta detección
Datos comprometidos: 12,000 registros PII
Este error cuesta carreras, no solo dinero.
IAM Policy vs Bucket Policy: la confusión clásica
La pregunta más frecuente en auditorías:
“¿Por qué este rol puede acceder aunque la bucket policy lo niega?”
La diferencia:
IAM Policy: define quién puede hacer qué sobre recursos AWS.
Bucket Policy: define qué puede hacerse con este bucket y bajo qué condiciones.
Ejemplo mental:
IAM Policy (en el rol):
"Este rol puede leer objetos S3 en general"
Bucket Policy (en el bucket):
"Solo permitir acceso desde:
- Esta cuenta AWS
- Esta VPC
- Con TLS habilitado"
Ambas deben permitir la acción para que funcione.
Caso práctico: restricción por VPC
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AccessOnlyFromVPC",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-private-bucket",
"arn:aws:s3:::my-private-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-12345678"
}
}
}
]
}
Lo que hace:
- ✅ Acceso permitido solo desde VPC específica
- ❌ Bloquea acceso desde internet, consola AWS, CLI local
- 🎯 Úsalo para: buckets con datos sensibles en arquitecturas multi-tenant
Un Cloud Engineer piensa en políticas como contratos, no como listas de permisos.
Block Public Access: tu último cinturón de seguridad
S3 incluye un mecanismo explícito para evitar exposiciones accidentales: Block Public Access.
Configuración recomendada:
# Terraform: activar Block Public Access
resource "aws_s3_bucket_public_access_block" "main" {
bucket = aws_s3_bucket.main.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
Debe estar:
- ✅ Activado a nivel de cuenta AWS
- ✅ Activado a nivel de bucket individual
¿Excepciones?
Sí, pero muy controladas:
Casos válidos para desactivar:
- Static websites públicos (con CloudFront mejor)
- Assets públicos versionados (logos, CSS, JS)
- Compartir datasets públicos (investigación, open data)
Casos NO válidos:
- "Porque no me deja probar algo" ❌
- "Solo temporalmente" ❌
- "Es un entorno de desarrollo" ❌
Si desactivas esto “porque no te deja probar algo”, el problema no es S3, es el diseño.
Checklist antes de desactivar Block Public Access:
- ¿Realmente necesitas acceso público o puedes usar presigned URLs?
- ¿Has considerado CloudFront con Origin Access Identity?
- ¿Tienes alertas configuradas para detectar cambios?
- ¿Está documentado y aprobado el motivo?
- ¿Hay revisión periódica programada?
Encriptación: no todo es KMS (ni todo debe serlo)
S3 cifra los datos en reposo por defecto desde 2023, pero hay matices importantes según tu caso de uso.
Opciones de encriptación:
Tipos de Server-Side Encryption:
SSE-S3 (AES-256):
- Encriptación gestionada por AWS
- Sin coste adicional
- Sin control sobre rotación de keys
- Ideal para: mayoría de casos
SSE-KMS:
- Keys gestionadas en AWS KMS
- Control granular de permisos
- Auditoría completa en CloudTrail
- Rotación automática de keys
- Coste: $0.03 por 10,000 requests
- Ideal para: datos sensibles, compliance
SSE-C (Customer-Provided):
- Tú gestionas las keys
- AWS nunca almacena la key
- Complejidad operacional alta
- Ideal para: requisitos legales específicos
Cuándo usar KMS:
Casos de uso SSE-KMS:
✅ Datos financieros
✅ PII (Personal Identifiable Information)
✅ Datos médicos (HIPAA)
✅ Requisitos regulatorios (GDPR, PCI-DSS)
✅ Necesidad de auditoría detallada
✅ Control de acceso por clave
Cuándo NO usar KMS:
No necesitas KMS para:
❌ Logs de aplicación masivos
❌ Datos fácilmente regenerables
❌ Assets públicos (imágenes, CSS)
❌ Artifacts de CI/CD
❌ Backups con retención < 30 días
Pagar KMS sin necesitarlo es tan mala práctica como no cifrar cuando toca.
Coste real de KMS en S3:
# Ejemplo: bucket con 10M de objetos, 100k requests/día
# Con SSE-S3
Coste encriptación: $0
# Con SSE-KMS
- Key storage: $1/mes
- Requests: 100,000 * 30 = 3M requests/mes
- Coste: (3,000,000 / 10,000) * $0.03 = $9/mes
# Total adicional: ~$10/mes
# ¿Vale la pena? Depende del caso de uso.
Forzar encriptación vía Bucket Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
]
}
Esto previene subidas sin encriptación, incluso por error.
Auditoría: CloudTrail y access logs
Si no sabes quién accede a tus buckets, no estás operando S3, solo lo estás usando.
Dos tipos de logging:
1. CloudTrail (API calls):
- Quién hizo qué y cuándo
- Llamadas a la API de S3
- Integración con GuardDuty
- Coste: ~$2 por 100,000 eventos
- Ejemplo: s3:PutObject, s3:DeleteBucket
2. S3 Server Access Logs:
- Requests HTTP individuales
- IP origen, user-agent, latencia
- Útil para debugging
- Gratis (solo pagas storage)
- Ejemplo: GET /file.jpg 200 OK
Configuración de Access Logs:
# Terraform
resource "aws_s3_bucket_logging" "main" {
bucket = aws_s3_bucket.main.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "s3-access-logs/"
}
Buenas prácticas de auditoría:
Checklist auditoría S3:
✅ CloudTrail activo en todas las regiones
✅ Access logs en buckets críticos
✅ Retention de logs según compliance (7-90 días)
✅ Lifecycle para mover logs a Glacier (coste)
✅ Alertas automáticas para:
- Cambios en bucket policies
- Desactivación de Block Public Access
- Borrados masivos
- Accesos fuera de horario laboral
✅ Revisión mensual, no solo en incidentes
Caso real: detección de exfiltración:
Escenario detectado vía CloudTrail:
- Usuario: backend-service-role
- Acción: s3:GetObject
- Volumen: 500 GB en 2 horas (usual: 5 GB/día)
- IP origen: fuera de VPC
Investigación:
✅ Credenciales comprometidas detectadas
✅ Acceso revocado en 15 minutos
✅ Datos no sensibles (logs públicos)
Sin CloudTrail: detección semanas después vía factura
Un SRE no espera al problema para mirar logs. Los mira antes.
Costes en S3: el enemigo silencioso
S3 parece barato… hasta que deja de serlo.
Factores clave de coste:
Desglose real de factura S3:
1. Storage ($):
- Cantidad de datos * clase de storage
- Varía 100x entre Standard y Deep Archive
2. Requests ($):
- PUT/POST/LIST: más caros
- GET: baratos
- DELETE: gratis
3. Data Transfer ($$):
- Ingress: GRATIS
- Egress: caro ($0.09/GB)
- Entre regiones: medio caro
4. Versiones antiguas (💰):
- Olvidadas = dinero invisible
- Sin lifecycle = coste infinito
5. Multipart uploads incompletos:
- Uploads fallidos sin cleanup
- Cobran como objetos completos
Errores típicos que explotan la factura:
Anti-patterns costosos:
❌ Versionado sin lifecycle:
- Cada modificación = nuevo objeto
- 100 versiones de 1 GB = 100 GB cobrados
❌ Logs eternos:
- Logs de 5 años en Standard
- Nunca consultados después de 30 días
❌ Glacier sin política de borrado:
- Backups acumulándose indefinidamente
- "Por si acaso"
❌ Listados masivos innecesarios:
- LIST operations caras a escala
- Apps que listan buckets completos cada 5 min
❌ Transfers sin CloudFront:
- $0.09/GB vs $0.085/GB + caching
Caso real: optimización de $18,000 → $2,400/mes:
Bucket: logs de aplicación (3 años)
Tamaño: 800 TB
Clase: S3 Standard
Problema identificado:
- Versionado activo sin lifecycle
- ~50 versiones promedio por objeto
- Logs nunca accedidos después de 90 días
- Todo en Standard
Solución aplicada:
1. Lifecycle policy por antigüedad
2. Límite de versiones (últimas 5)
3. Transition a storage classes económicos
4. Borrado automático > 2 años
Nueva estructura:
- < 30 días: Standard ($0.023/GB)
- 30-90 días: Standard-IA ($0.0125/GB)
- 90-365 días: Glacier Instant ($0.004/GB)
- > 1 año: Deep Archive ($0.00099/GB)
- > 2 años: DELETE
Resultado:
Antes: $18,400/mes
Después: $2,400/mes
Ahorro anual: $192,000
S3 no avisa cuando estás diseñando algo caro. La factura sí.
Storage Classes: elegir bien o pagar de más
No todos los datos necesitan estar en Standard. De hecho, la mayoría no deberían.
Comparativa completa:
Storage Classes (us-east-1):
S3 Standard:
Coste: $0.023/GB/mes
Disponibilidad: 99.99%
Latencia: ms
Ideal: datos activos
S3 Intelligent-Tiering:
Coste: $0.0025-$0.023/GB/mes (automático)
Monitoreo: $0.0025 por 1,000 objetos
Ideal: patrón de acceso impredecible
S3 Standard-IA:
Coste storage: $0.0125/GB/mes
Coste retrieval: $0.01/GB
Min storage: 30 días
Ideal: backups mensuales
S3 One Zone-IA:
Coste: $0.01/GB/mes
Disponibilidad: 99.5% (single AZ)
Ideal: datos regenerables
S3 Glacier Instant:
Coste: $0.004/GB/mes
Retrieval: $0.03/GB
Latencia: ms
Min storage: 90 días
Ideal: archivos trimestrales
S3 Glacier Flexible:
Coste: $0.0036/GB/mes
Retrieval: 1min-12h ($0.01-$0.03/GB)
Min storage: 90 días
Ideal: archivos anuales
S3 Glacier Deep Archive:
Coste: $0.00099/GB/mes
Retrieval: 12-48h
Min storage: 180 días
Ideal: compliance 7-10 años
Lifecycle Policy completa:
{
"Rules": [
{
"Id": "OptimizeLogsCost",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_IR"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 2555
},
"NoncurrentVersionTransitions": [
{
"NoncurrentDays": 7,
"StorageClass": "GLACIER_IR"
}
],
"NoncurrentVersionExpiration": {
"NoncurrentDays": 90
}
},
{
"Id": "CleanupIncompleteMultipartUploads",
"Status": "Enabled",
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
}
]
}
Regla simple: la clase correcta depende de frecuencia de acceso, no de intuición.
Mover datos automáticamente con lifecycle es una de las decisiones más rentables que puedes tomar en AWS.
Versionado: protección vs coste descontrolado
S3 Versioning es un arma de doble filo:
Ventajas:
✅ Protección contra borrados accidentales
✅ Recuperación de versiones anteriores
✅ Cumplimiento de auditorías
Peligros:
❌ Cada modificación = nuevo objeto cobrado
❌ Versiones antiguas invisibles en consola
❌ Delete markers ocupan espacio
❌ Coste exponencial sin lifecycle
Configuración inteligente:
# Terraform: versionado con límite
resource "aws_s3_bucket_versioning" "main" {
bucket = aws_s3_bucket.main.id
versioning_configuration {
status = "Enabled"
}
}
# Lifecycle: límite de versiones
resource "aws_s3_bucket_lifecycle_configuration" "main" {
bucket = aws_s3_bucket.main.id
rule {
id = "expire-old-versions"
status = "Enabled"
noncurrent_version_expiration {
noncurrent_days = 90
newer_noncurrent_versions = 5 # Mantener solo 5 versiones
}
}
}
Versionado SÍ, pero con lifecycle. Siempre.
El patrón que delata inexperiencia
Se repite una y otra vez en auditorías:
Bucket creado rápidamente:
❌ Sin policies claras
❌ Sin lifecycle
❌ Sin logs
❌ Con permisos amplios "temporalmente"
❌ Sin tags de ownership
❌ Sin alertas
❌ Sin documentación
Ese "temporalmente" acaba durando años.
Bucket bien diseñado desde el inicio:
Checklist producción:
✅ Nombre descriptivo y versionado
✅ Tags: Environment, Owner, Project, CostCenter
✅ Block Public Access: ON
✅ Versioning: Enabled (con lifecycle)
✅ Encryption: SSE-S3 o SSE-KMS según caso
✅ Bucket Policy: restrictiva
✅ Lifecycle Policy: definida
✅ Access Logging: enabled
✅ CloudTrail: monitoreado
✅ CloudWatch Alarms: configuradas
✅ Backup: replication a otra región (críticos)
✅ Documentación: README con propósito y owner
Alertas: CloudWatch + EventBridge
No basta con configurar bien. Hay que monitorear activamente.
Alertas críticas recomendadas:
# CloudWatch Alarms para S3
resource "aws_cloudwatch_metric_alarm" "bucket_size" {
alarm_name = "s3-bucket-size-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "BucketSizeBytes"
namespace = "AWS/S3"
period = 86400
statistic = "Average"
threshold = 1000000000000 # 1 TB
alarm_description = "Bucket size exceeds 1TB"
dimensions = {
BucketName = aws_s3_bucket.main.id
StorageType = "StandardStorage"
}
}
EventBridge para cambios de seguridad:
{
"source": ["aws.s3"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": [
"PutBucketPublicAccessBlock",
"DeleteBucketPublicAccessBlock",
"PutBucketPolicy",
"DeleteBucketPolicy"
]
}
}
Configurar esto lleva 30 minutos. No hacerlo puede costarte la carrera.
Checklist mental antes de crear un bucket en producción
Un Cloud Engineer se pregunta:
Preguntas clave:
Antes de crear el bucket:
1. Acceso:
- ¿Quién accede? (roles, servicios)
- ¿Desde dónde? (VPC, IPs, cuentas)
- ¿Qué operaciones necesita? (read, write, delete)
2. Ciclo de vida:
- ¿Cuánto tiempo vive el dato?
- ¿Se accede después de X días?
- ¿Cuándo se puede borrar?
3. Durabilidad:
- ¿Qué pasa si se borra?
- ¿Necesito versionado?
- ¿Necesito replicación a otra región?
4. Costes:
- ¿Cuánto costará en 6 meses?
- ¿Qué storage class es óptima?
- ¿Tengo lifecycle configurado?
5. Seguridad:
- ¿Necesita ser público?
- ¿Qué nivel de encriptación?
- ¿Tengo auditoría configurada?
6. Operaciones:
- ¿Quién es el owner?
- ¿Hay alertas configuradas?
- ¿Está documentado?
Si no puedes responder a esto, no deberías crear el bucket todavía.
Terraform: infraestructura como código para S3
La única forma sana de gestionar S3 a escala es con IaC:
# Bucket completo production-ready
module "secure_bucket" {
source = "./modules/s3-secure-bucket"
bucket_name = "my-app-data-prod"
# Seguridad
enable_versioning = true
enable_encryption = true
encryption_type = "SSE-KMS"
kms_key_id = aws_kms_key.s3.arn
block_public_access = true
enable_access_logging = true
log_bucket = aws_s3_bucket.logs.id
# Lifecycle
lifecycle_rules = [
{
name = "optimize-costs"
prefix = "data/"
transitions = [
{ days = 30, storage_class = "STANDARD_IA" },
{ days = 90, storage_class = "GLACIER_IR" },
{ days = 365, storage_class = "DEEP_ARCHIVE" }
]
expiration_days = 2555 # 7 años
}
]
# Policies
bucket_policy = data.aws_iam_policy_document.bucket.json
# Replicación
enable_replication = true
replication_region = "us-west-2"
# Tags
tags = {
Environment = "production"
Project = "core-app"
Owner = "platform-team"
CostCenter = "engineering"
Compliance = "GDPR"
}
}
IaC no es opcional a escala. Es supervivencia.
Cierre: S3 como responsabilidad, no como servicio
S3 es uno de los servicios más sólidos de AWS. Precisamente por eso expone sin piedad los errores de diseño.
La diferencia real:
Usuario de S3:
- Sube archivos
- Usa la consola
- "Funciona"
Cloud Engineer con S3:
- Diseña seguridad en capas
- Optimiza costes automáticamente
- Audita accesos proactivamente
- Previene incidentes antes de que ocurran
- Protege datos y presupuesto
Cuando empiezas a cuidar:
- ✅ Seguridad (Block Public Access, policies, encriptación)
- ✅ Auditoría (CloudTrail, access logs, alertas)
- ✅ Costes (storage classes, lifecycle, cleanup)
Dejas de ser “el que sube archivos” y pasas a ser el que protege datos y presupuesto.
Y ahí es donde, sin darte cuenta, ya estás actuando como Cloud Engineer o SRE, aunque tu título siga diciendo backend developer.