Imagen destacada del artículo: Amazon S3 e ISO27001: cómo convertir buckets en controles de seguridad reales

Amazon S3 e ISO27001: cómo convertir buckets en controles de seguridad reales

Hablar de ISO 27001 no va de cumplir por cumplir. Va de demostrar control.

Y pocos servicios ponen tan a prueba ese control como Amazon S3.

Porque S3 no falla, pero expone cualquier debilidad organizativa:

  • 🔓 Accesos mal definidos
  • ⏳ Retenciones inexistentes
  • 📋 Auditorías improvisadas
  • 👥 Responsabilidades poco claras

Cuando estuve preparando las pruebas de certificación, teníamos inicialmente unos buckets que podría llamarlos caóticos, y causaron más de 15 no conformidades. Cuando se preparó correctamente, se aceleró muchos estas pruebas.

Este artículo conecta Amazon S3 con ISO 27001 desde un enfoque práctico: cómo usar buckets para cumplir, evidenciar y sostener un SGSI en el tiempo, sin burocracia inútil.

El error más común: “ISO es solo documentación”

En Amazon Web Services, S3 suele convertirse en:

Usos típicos de S3:
  - Repositorio de backups
  - Almacén de logs de aplicaciones
  - Contenedor de evidencias de auditoría
  - Archivos de compliance
  - Datos históricos regulatorios

Y ahí aparece el error: pensar que ISO 27001 se resuelve con PDFs y políticas.

La realidad es más cruda:

Si tu S3 no está bien diseñado, tu SGSI tampoco lo está.

ISO 27001 exige controles técnicos reales, no solo textos bonitos. Un auditor competente revisará:

  • ✅ Configuración técnica real de buckets
  • ✅ Logs de acceso verificables
  • ✅ Políticas de retención implementadas
  • ✅ Evidencias de revisiones periódicas
  • ✅ Controles de cifrado activos

No basta con escribir “tenemos controles de acceso”. Hay que demostrarlo con evidencias técnicas.

ISO 27001 y S3: el marco conceptual

ISO 27001 define un Sistema de Gestión de Seguridad de la Información (SGSI) basado en:

Ciclo PDCA aplicado a S3:

Plan (Planificar):
  - Identificar buckets como activos
  - Clasificar información almacenada
  - Definir controles técnicos necesarios
  - Asignar responsables (owners)

Do (Hacer):
  - Implementar configuraciones técnicas
  - Activar cifrado, logging, lifecycle
  - Documentar decisiones de diseño
  - Capacitar a equipos

Check (Verificar):
  - Auditar configuraciones periódicamente
  - Revisar logs de acceso
  - Validar cumplimiento de controles
  - Medir efectividad

Act (Actuar):
  - Corregir desviaciones detectadas
  - Mejorar configuraciones
  - Actualizar documentación
  - Implementar lecciones aprendidas

S3 encaja perfectamente en este ciclo… si lo diseñas conscientemente.

S3 como activo del SGSI

El primer paso correcto es tratar cada bucket como un activo de información.

Registro de activos (Anexo A.8.1):

Ficha de activo - Bucket S3:
  
  Nombre: s3://company-financial-reports-prod
  
  Clasificación: CONFIDENCIAL
  
  Propietario: CFO / Finance Department
  
  Custodio Técnico: Cloud Platform Team
  
  Tipo de información:
    - Reportes financieros trimestrales
    - Estados contables
    - Auditorías financieras
  
  Requisitos legales:
    - Retención: 10 años (Ley Tributaria)
    - Disponibilidad: 99.9%
    - Cifrado: Obligatorio
  
  Controles aplicados:
    - A.9.1.2: Control de acceso
    - A.10.1.1: Cifrado
    - A.12.3.1: Backup
    - A.12.4.1: Logging
    - A.18.1.3: Retención
  
  Riesgos identificados:
    - Acceso no autorizado (Probabilidad: Media, Impacto: Alto)
    - Pérdida de datos (Probabilidad: Baja, Impacto: Crítico)
    - Borrado accidental (Probabilidad: Media, Impacto: Alto)
  
  Revisión: Trimestral
  Última revisión: 15/12/2025
  Próxima revisión: 15/03/2026

Un bucket sin dueño es un riesgo latente. Y en una auditoría, eso se detecta rápido.

Implementación práctica con tags:

# Terraform: tagging para ISO 27001
resource "aws_s3_bucket" "financial_reports" {
  bucket = "company-financial-reports-prod"
  
  tags = {
    # Gestión de activos
    AssetID          = "ASSET-S3-001"
    Owner            = "finance-team"
    TechnicalOwner   = "platform-team"
    
    # Clasificación
    DataClassification = "CONFIDENTIAL"
    Criticality        = "HIGH"
    
    # Compliance
    ISO27001Control    = "A.8.1.1,A.9.1.2,A.10.1.1"
    RetentionPeriod    = "10-years"
    LegalRequirement   = "Tax-Law-2023"
    
    # Operacional
    Environment      = "production"
    BackupRequired   = "true"
    DRPlan           = "enabled"
    
    # Auditoría
    LastReview       = "2025-12-15"
    NextReview       = "2026-03-15"
    AuditScope       = "ISO27001"
  }
}

Los tags no son metadata bonita. Son evidencia auditable.

Controles ISO 27001 que impactan directamente en S3

Aunque ISO 27001 no habla específicamente de S3, muchos controles del Anexo A caen directamente sobre él:

Mapeado completo de controles:

ISO 27001 Anexo A → Amazon S3:

A.8 - Gestión de activos:
  A.8.1.1 - Inventario de activos:
    ✅ Registro de todos los buckets
    ✅ Tags de clasificación y ownership
    ✅ Documentación de propósito
  
  A.8.2.3 - Manipulación de activos:
    ✅ Lifecycle policies
    ✅ Versionado para cambios
    ✅ Procedimientos de borrado seguro

A.9 - Control de acceso:
  A.9.1.1 - Política de control de acceso:
    ✅ Bucket policies restrictivas
    ✅ IAM roles con mínimo privilegio
    ✅ Block Public Access habilitado
  
  A.9.1.2 - Acceso a redes y servicios:
    ✅ Acceso solo desde VPC autorizada
    ✅ Endpoint policies
    ✅ Restricción por IP si aplica
  
  A.9.2.1 - Registro de usuarios:
    ✅ IAM users/roles documentados
    ✅ Matriz RACI de accesos
  
  A.9.4.1 - Restricción de acceso a la información:
    ✅ Policies basadas en tags
    ✅ Session policies
    ✅ Conditions en bucket policies

A.10 - Criptografía:
  A.10.1.1 - Política de uso de controles criptográficos:
    ✅ Cifrado en reposo (SSE-S3/KMS)
    ✅ Cifrado en tránsito (TLS enforced)
    ✅ Gestión de claves documentada
  
  A.10.1.2 - Gestión de claves:
    ✅ AWS KMS para claves maestras
    ✅ Rotación automática
    ✅ Auditoría de uso de claves

A.12 - Seguridad de las operaciones:
  A.12.3.1 - Copias de seguridad:
    ✅ Versionado habilitado
    ✅ Replicación cross-region
    ✅ Lifecycle para retención
  
  A.12.4.1 - Registro de eventos:
    ✅ CloudTrail activo
    ✅ S3 Access Logs
    ✅ Almacenamiento de logs protegido
  
  A.12.4.2 - Protección de logs:
    ✅ Logs en bucket separado
    ✅ Solo append, no modificación
    ✅ Lifecycle para retención legal

A.13 - Seguridad de las comunicaciones:
  A.13.2.1 - Políticas y procedimientos de transferencia:
    ✅ TLS obligatorio (aws:SecureTransport)
    ✅ Presigned URLs con expiración
    ✅ VPC Endpoints para tráfico interno

A.18 - Cumplimiento:
  A.18.1.3 - Protección de registros:
    ✅ Versionado inmutable
    ✅ Object Lock para WORM
    ✅ Retención según requisitos legales
  
  A.18.1.5 - Regulación de controles criptográficos:
    ✅ Cifrado conforme a estándares
    ✅ Documentación de algoritmos
    ✅ Revisión periódica

S3 puede cubrirlos todos… o fallarlos todos, según cómo lo configures.

A.9 - Control de accesos: mínimo privilegio o no hay ISO

Desde ISO 27001, no vale “todo el equipo tiene acceso”. El control A.9.1.2 exige restricción efectiva.

Implementación conforme:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EnforceMinimumPrivilege",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::sensitive-data-prod",
        "arn:aws:s3:::sensitive-data-prod/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalAccount": "123456789012"
        }
      }
    },
    {
      "Sid": "AllowOnlyFromCorporateVPC",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::sensitive-data-prod",
        "arn:aws:s3:::sensitive-data-prod/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpc": "vpc-corporate-12345"
        }
      }
    },
    {
      "Sid": "EnforceTLSOnly",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::sensitive-data-prod",
        "arn:aws:s3:::sensitive-data-prod/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    },
    {
      "Sid": "AllowReadOnlyForAuditors",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/ISO27001-Auditor"
      },
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::sensitive-data-prod",
        "arn:aws:s3:::sensitive-data-prod/*"
      ]
    }
  ]
}

Matriz de accesos documentada:

Matriz RACI - Bucket: sensitive-data-prod

Roles IAM:
  
  data-engineers-role:
    Permissions: Read, Write
    Justification: "Procesamiento diario de datos"
    Approved by: CISO
    Review date: Q1-2026
    Responsible: Data Team Lead
  
  auditors-role:
    Permissions: Read-only
    Justification: "Auditorías de compliance"
    Approved by: CISO
    Review date: Anual
    Accountable: Compliance Officer
  
  backup-service-role:
    Permissions: Read, Write, Delete
    Justification: "Gestión automatizada de backups"
    Approved by: CTO
    Review date: Q2-2026
    Consulted: Platform Team
  
  platform-admins-role:
    Permissions: Full (con MFA Delete)
    Justification: "Gestión de infraestructura"
    Approved by: CTO + CISO
    Review date: Mensual
    Informed: Security Team

Revisión periódica: Trimestral
Última revisión: 15/12/2025
Hallazgos: 0 accesos innecesarios detectados

Esto no solo cumple ISO: reduce incidentes reales.

A.10 - Cifrado y protección del dato

ISO 27001 control A.10.1.1 exige proteger la información almacenada. En S3 eso se traduce en:

Decisión documentada de cifrado:

Política de cifrado S3 - ISO27001:

Clasificación PÚBLICA:
  Cifrado: SSE-S3 (AES-256)
  Justificación: "Sin requisitos especiales"
  Coste: $0
  
Clasificación INTERNA:
  Cifrado: SSE-S3 (AES-256)
  Justificación: "Protección básica suficiente"
  Coste: $0
  
Clasificación CONFIDENCIAL:
  Cifrado: SSE-KMS
  Key: aws/s3 (AWS managed)
  Justificación: "Auditoría de uso de claves necesaria"
  Coste: ~$1/mes + requests
  
Clasificación CRÍTICA/PII:
  Cifrado: SSE-KMS
  Key: Customer Managed Key (CMK)
  Rotación: Automática anual
  Justificación: "Control total sobre claves, GDPR/HIPAA"
  Coste: ~$1/mes/key + requests
  Policies: Restricción por rol y uso

Implementación con enforcement:

# KMS Key para datos críticos
resource "aws_kms_key" "s3_critical_data" {
  description             = "KMS key for critical S3 data - ISO27001 A.10.1.1"
  deletion_window_in_days = 30
  enable_key_rotation     = true
  
  tags = {
    ISO27001Control = "A.10.1.1"
    Purpose         = "S3-Encryption-Critical-Data"
    Compliance      = "GDPR,ISO27001"
  }
}

resource "aws_kms_alias" "s3_critical_data" {
  name          = "alias/s3-critical-data"
  target_key_id = aws_kms_key.s3_critical_data.key_id
}

# Bucket con cifrado obligatorio
resource "aws_s3_bucket" "critical_data" {
  bucket = "company-critical-data-prod"
  
  tags = {
    DataClassification = "CRITICAL"
    ISO27001Control    = "A.10.1.1"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "critical_data" {
  bucket = aws_s3_bucket.critical_data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3_critical_data.arn
    }
    bucket_key_enabled = true
  }
}

# Forzar cifrado vía policy
resource "aws_s3_bucket_policy" "critical_data_encryption" {
  bucket = aws_s3_bucket.critical_data.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "DenyUnencryptedObjectUploads"
        Effect = "Deny"
        Principal = "*"
        Action = "s3:PutObject"
        Resource = "${aws_s3_bucket.critical_data.arn}/*"
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption" = "aws:kms"
          }
        }
      },
      {
        Sid    = "DenyIncorrectKMSKey"
        Effect = "Deny"
        Principal = "*"
        Action = "s3:PutObject"
        Resource = "${aws_s3_bucket.critical_data.arn}/*"
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption-aws-kms-key-id" = aws_kms_key.s3_critical_data.arn
          }
        }
      }
    ]
  })
}

No es necesario sobredimensionar, pero sí justificar la decisión. ISO no castiga la sencillez, castiga la improvisación.

A.12.4 - Auditoría y trazabilidad: la prueba de fuego

Un auditor ISO 27001 no quiere promesas, quiere evidencias verificables.

Control A.12.4.1 - Registro de eventos:

Con S3 debes poder demostrar:

Evidencias auditables:
  
  ¿Quién accede?
    Fuente: CloudTrail
    Dato: userIdentity.principalId
    
  ¿Cuándo?
    Fuente: CloudTrail
    Dato: eventTime (ISO 8601)
    
  ¿Desde dónde?
    Fuente: CloudTrail + Access Logs
    Dato: sourceIPAddress, vpcEndpointId
    
  ¿Qué acciones realiza?
    Fuente: CloudTrail
    Dato: eventName (GetObject, PutObject, DeleteObject)
    
  ¿Con qué resultado?
    Fuente: CloudTrail
    Dato: errorCode, errorMessage
    
  ¿Qué objetos?
    Fuente: CloudTrail
    Dato: requestParameters.bucketName, requestParameters.key

Implementación completa de logging:

# Bucket dedicado para logs (A.12.4.2)
resource "aws_s3_bucket" "audit_logs" {
  bucket = "company-audit-logs-iso27001"
  
  tags = {
    Purpose         = "ISO27001-Audit-Logs"
    ISO27001Control = "A.12.4.1,A.12.4.2"
    Retention       = "7-years"
  }
}

# Bloquear acceso público a logs
resource "aws_s3_bucket_public_access_block" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id

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

# Cifrado de logs
resource "aws_s3_bucket_server_side_encryption_configuration" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# Versionado para inmutabilidad
resource "aws_s3_bucket_versioning" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

# Object Lock para WORM (Write Once Read Many)
resource "aws_s3_bucket_object_lock_configuration" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id

  rule {
    default_retention {
      mode = "GOVERNANCE"  # o "COMPLIANCE" para requisitos más estrictos
      years = 7
    }
  }
}

# Lifecycle para retención legal
resource "aws_s3_bucket_lifecycle_configuration" "audit_logs" {
  bucket = aws_s3_bucket.audit_logs.id

  rule {
    id     = "ISO27001-Log-Retention"
    status = "Enabled"

    transition {
      days          = 90
      storage_class = "STANDARD_IA"
    }

    transition {
      days          = 365
      storage_class = "GLACIER_IR"
    }

    transition {
      days          = 1825  # 5 años
      storage_class = "DEEP_ARCHIVE"
    }

    expiration {
      days = 2555  # 7 años (requisito legal)
    }
  }
}

# Activar S3 Access Logging en bucket de producción
resource "aws_s3_bucket_logging" "production_bucket" {
  bucket = aws_s3_bucket.critical_data.id

  target_bucket = aws_s3_bucket.audit_logs.id
  target_prefix = "s3-access-logs/critical-data/"
}

# CloudTrail para eventos de gestión
resource "aws_cloudtrail" "iso27001" {
  name                          = "iso27001-audit-trail"
  s3_bucket_name                = aws_s3_bucket.audit_logs.id
  s3_key_prefix                 = "cloudtrail/"
  include_global_service_events = true
  is_multi_region_trail         = true
  enable_log_file_validation    = true  # Integridad de logs
  
  event_selector {
    read_write_type           = "All"
    include_management_events = true

    data_resource {
      type   = "AWS::S3::Object"
      values = ["${aws_s3_bucket.critical_data.arn}/*"]
    }
  }
  
  tags = {
    ISO27001Control = "A.12.4.1"
    Purpose         = "Audit-Trail"
  }
}

Consultas de auditoría con Athena:

-- Query: Accesos fuera de horario laboral (indicador de riesgo)
SELECT 
    eventTime,
    userIdentity.principalId as user,
    eventName,
    requestParameters.bucketName as bucket,
    requestParameters.key as object_key,
    sourceIPAddress,
    errorCode
FROM cloudtrail_logs
WHERE 
    eventSource = 's3.amazonaws.com'
    AND eventName IN ('GetObject', 'PutObject', 'DeleteObject')
    AND (
        CAST(date_format(from_iso8601_timestamp(eventTime), '%H') AS INTEGER) < 8
        OR CAST(date_format(from_iso8601_timestamp(eventTime), '%H') AS INTEGER) > 18
    )
    AND errorCode IS NULL
ORDER BY eventTime DESC
LIMIT 100;

-- Query: Cambios en bucket policies (control crítico)
SELECT 
    eventTime,
    userIdentity.principalId,
    eventName,
    requestParameters.bucketName,
    responseElements
FROM cloudtrail_logs
WHERE 
    eventName IN (
        'PutBucketPolicy',
        'DeleteBucketPolicy',
        'PutBucketPublicAccessBlock',
        'DeleteBucketPublicAccessBlock'
    )
ORDER BY eventTime DESC;

-- Query: Accesos desde IPs no corporativas
SELECT 
    eventTime,
    userIdentity.principalId,
    eventName,
    sourceIPAddress,
    requestParameters.bucketName
FROM cloudtrail_logs
WHERE 
    eventSource = 's3.amazonaws.com'
    AND sourceIPAddress NOT LIKE '10.%'  -- Red corporativa
    AND sourceIPAddress NOT LIKE '172.16.%'
    AND userIdentity.type != 'AWSService'
ORDER BY eventTime DESC;

CloudTrail y los access logs de S3 son controles técnicos, no extras opcionales. Si no están activos en buckets críticos, el SGSI es débil.

A.18.1.3 - Retención y borrado: lifecycle como control ISO

ISO 27001 exige que los datos:

  • ✅ No vivan más de lo necesario (minimización)
  • ✅ Se eliminen de forma controlada (borrado seguro)
  • ✅ Se conserven según requisitos legales (retención)

Política de retención documentada:

Política de retención - ISO27001 A.18.1.3:

Logs de aplicación:
  Retención: 90 días
  Justificación: "Debugging operacional"
  Base legal: "Política interna"
  Destrucción: Automática (lifecycle)
  
Logs de acceso (S3/CloudTrail):
  Retención: 7 años
  Justificación: "Auditoría y cumplimiento"
  Base legal: "ISO 27001, normativa local"
  Destrucción: Automática post-retención
  
Backups de bases de datos:
  Retención: 30 días (diarios), 12 meses (mensuales)
  Justificación: "Recuperación ante desastres"
  Base legal: "Plan de continuidad"
  Destrucción: Automática con verificación
  
Documentos financieros:
  Retención: 10 años
  Justificación: "Requisitos fiscales"
  Base legal: "Código Tributario Art. 123"
  Destrucción: Proceso aprobado por CFO
  
Datos personales (GDPR):
  Retención: Mientras exista relación contractual + 5 años
  Justificación: "Base legal: ejecución de contrato"
  Base legal: "GDPR Art. 6.1.b"
  Destrucción: Automática salvo oposición legal

Implementación con Lifecycle Policies:

{
  "Rules": [
    {
      "Id": "ISO27001-ApplicationLogs-90days",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "logs/application/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        }
      ],
      "Expiration": {
        "Days": 90
      }
    },
    {
      "Id": "ISO27001-AuditLogs-7years",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "logs/audit/"
      },
      "Transitions": [
        {
          "Days": 90,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 365,
          "StorageClass": "GLACIER_IR"
        },
        {
          "Days": 1825,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 2555
      }
    },
    {
      "Id": "ISO27001-FinancialDocs-10years",
      "Status": "Enabled",
      "Filter": {
        "And": {
          "Prefix": "documents/financial/",
          "Tags": [
            {
              "Key": "DataClassification",
              "Value": "CONFIDENTIAL"
            },
            {
              "Key": "RetentionPeriod",
              "Value": "10-years"
            }
          ]
        }
      },
      "Transitions": [
        {
          "Days": 365,
          "StorageClass": "GLACIER_IR"
        },
        {
          "Days": 1095,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 3650
      }
    },
    {
      "Id": "CleanupIncompleteMultipartUploads",
      "Status": "Enabled",
      "AbortIncompleteMultipartUpload": {
        "DaysAfterInitiation": 7
      }
    },
    {
      "Id": "ExpireOldVersions",
      "Status": "Enabled",
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 90,
        "NewerNoncurrentVersions": 3
      }
    }
  ]
}

Un bucket sin lifecycle es una bomba de tiempo… y un punto débil en auditoría.

A.12.3.1 - Copias de seguridad: versionado y replicación

ISO 27001 exige backups verificables y recuperables.

Estrategia de backup conforme:

Plan de backup S3 - ISO27001 A.12.3.1:

Nivel 1 - Versionado:
  Mecanismo: S3 Versioning
  RPO: 0 (inmediato)
  RTO: < 5 minutos
  Alcance: Todos los buckets críticos
  Pruebas: Mensuales
  
Nivel 2 - Replicación Cross-Region:
  Mecanismo: S3 Replication
  Destino: eu-west-1 (región secundaria)
  RPO: < 15 minutos
  RTO: < 1 hora
  Alcance: Buckets clasificados como CRÍTICOS
  Pruebas: Trimestrales con failover real
  
Nivel 3 - Exportación a Glacier:
  Mecanismo: Lifecycle + S3 Batch Operations
  Frecuencia: Semanal (snapshots)
  Retención: 7 años
  Alcance: Documentos regulatorios
  Pruebas: Anuales con restauración completa

Implementación de replicación:

# Bucket de destino (región secundaria)
resource "aws_s3_bucket" "replication_dest" {
  provider = aws.secondary_region
  bucket   = "company-critical-data-replica-eu"
  
  tags = {
    Purpose         = "Disaster-Recovery"
    ISO27001Control = "A.12.3.1"
  }
}

# Versionado en ambos buckets (requisito para replicación)
resource "aws_s3_bucket_versioning" "replication_dest" {
  provider = aws.secondary_region
  bucket   = aws_s3_bucket.replication_dest.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

# IAM Role para replicación
resource "aws_iam_role" "replication" {
  name = "s3-replication-role-iso27001"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "s3.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy" "replication" {
  role = aws_iam_role.replication.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:GetReplicationConfiguration",
          "s3:ListBucket"
        ]
        Effect = "Allow"
        Resource = aws_s3_bucket.critical_data.arn
      },
      {
        Action = [
          "s3:GetObjectVersionForReplication",
          "s3:GetObjectVersionAcl"
        ]
        Effect = "Allow"
        Resource = "${aws_s3_bucket.critical_data.arn}/*"
      },
      {
        Action = [
          "s3:ReplicateObject",
          "s3:ReplicateDelete"
        ]
        Effect = "Allow"
        Resource = "${aws_s3_bucket.replication_dest.arn}/*"
      }
    ]
  })
}

# Configuración de replicación
resource "aws_s3_bucket_replication_configuration" "critical_data" {
  bucket = aws_s3_bucket.critical_data.id
  role   = aws_iam_role.replication.arn

  rule {
    id     = "ISO27001-DR-Replication"
    status = "Enabled"

    filter {
      and {
        prefix = ""
        tags = {
          Replicate = "true"
        }
      }
    }

    destination {
      bucket        = aws_s3_bucket.replication_dest.arn
      storage_class = "STANDARD_IA"
      
      replication_time {
        status = "Enabled"
        time {
          minutes = 15
        }
      }
      
      metrics {
        status = "Enabled"
        event_threshold {
          minutes = 15
        }
      }
    }

    delete_marker_replication {
      status = "Enabled"
    }
  }
}

Pruebas de recuperación documentadas:

Registro de pruebas de backup - ISO27001:

Prueba Q4-2025:
  Fecha: 15/12/2025
  Tipo: Recuperación desde versionado
  Escenario: Borrado accidental de objeto crítico
  Procedimiento:
    1. Simular borrado: aws s3 rm s3://bucket/critical-file.pdf
    2. Listar versiones: aws s3api list-object-versions
    3. Restaurar: aws s3api delete-object (delete marker)
  Resultado: ✅ EXITOSO
  Tiempo recuperación: 3 minutos
  Responsable: John Doe (Platform Team)
  
Prueba Q3-2025:
  Fecha: 20/09/2025
  Tipo: Failover cross-region
  Escenario: Caída de región us-east-1
  Procedimiento:
    1. Actualizar DNS a región EU
    2. Verificar integridad de réplica
    3. Validar accesos de aplicación
    4. Rollback tras confirmación
  Resultado: ✅ EXITOSO
  RTO real: 45 minutos (objetivo: < 1 hora)
  Responsable: Jane Smith (SRE Lead)
  Hallazgos: Documentación de runbook actualizada
  
Próxima prueba: Q1-2026 (marzo)

Evidencias para auditoría: S3 como aliado

Cuando S3 está bien diseñado, se convierte en un aliado brutal para auditorías:

Evidencias que puedes presentar:

Paquete de evidencias ISO27001 - Bucket S3:

1. Configuración técnica:
   ✅ Export de Terraform state
   ✅ Screenshot de bucket policies
   ✅ Configuración de cifrado (KMS keys)
   ✅ Lifecycle policies activas
   ✅ Versionado habilitado
   ✅ Replicación configurada

2. Controles de acceso:
   ✅ Lista de IAM roles con permisos
   ✅ Matriz RACI de responsables
   ✅ Logs de revisión trimestral de permisos
   ✅ Block Public Access enabled (screenshot)

3. Registros de auditoría:
   ✅ CloudTrail logs últimos 12 meses
   ✅ S3 Access Logs almacenados
   ✅ Query de Athena: accesos fuera de horario
   ✅ Query de Athena: cambios en policies
   ✅ Alertas configuradas en CloudWatch

4. Gestión de incidentes:
   ✅ Registro de simulacros de recuperación
   ✅ Tiempos de respuesta documentados
   ✅ Runbooks de procedimientos
   ✅ Post-mortems de incidentes

5. Cumplimiento de retención:
   ✅ Política de retención aprobada
   ✅ Lifecycle rules implementadas
   ✅ Reportes de objetos próximos a expirar
   ✅ Logs de eliminaciones automáticas

6. Cifrado y protección:
   ✅ Política de cifrado documentada
   ✅ KMS keys con rotación activa
   ✅ Enforcement via bucket policies
   ✅ Auditoría de uso de claves (CloudTrail)

Dashboard de cumplimiento:

# Script para generar reporte de cumplimiento ISO27001
import boto3
import json
from datetime import datetime

def generate_iso27001_compliance_report(bucket_name):
    s3 = boto3.client('s3')
    
    report = {
        "bucket": bucket_name,
        "audit_date": datetime.now().isoformat(),
        "controls": {}
    }
    
    # A.8.1.1 - Inventario
    try:
        tags = s3.get_bucket_tagging(Bucket=bucket_name)
        report["controls"]["A.8.1.1"] = {
            "status": "COMPLIANT" if tags else "NON_COMPLIANT",
            "evidence": tags.get('TagSet', [])
        }
    except:
        report["controls"]["A.8.1.1"] = {"status": "NON_COMPLIANT"}
    
    # A.9.1.2 - Control de acceso
    try:
        public_access = s3.get_public_access_block(Bucket=bucket_name)
        all_blocked = all([
            public_access['PublicAccessBlockConfiguration']['BlockPublicAcls'],
            public_access['PublicAccessBlockConfiguration']['BlockPublicPolicy'],
            public_access['PublicAccessBlockConfiguration']['IgnorePublicAcls'],
            public_access['PublicAccessBlockConfiguration']['RestrictPublicBuckets']
        ])
        report["controls"]["A.9.1.2"] = {
            "status": "COMPLIANT" if all_blocked else "NON_COMPLIANT",
            "evidence": public_access
        }
    except:
        report["controls"]["A.9.1.2"] = {"status": "NON_COMPLIANT"}
    
    # A.10.1.1 - Cifrado
    try:
        encryption = s3.get_bucket_encryption(Bucket=bucket_name)
        report["controls"]["A.10.1.1"] = {
            "status": "COMPLIANT",
            "evidence": encryption['ServerSideEncryptionConfiguration']
        }
    except:
        report["controls"]["A.10.1.1"] = {"status": "NON_COMPLIANT"}
    
    # A.12.3.1 - Backups
    try:
        versioning = s3.get_bucket_versioning(Bucket=bucket_name)
        is_enabled = versioning.get('Status') == 'Enabled'
        report["controls"]["A.12.3.1"] = {
            "status": "COMPLIANT" if is_enabled else "NON_COMPLIANT",
            "evidence": versioning
        }
    except:
        report["controls"]["A.12.3.1"] = {"status": "NON_COMPLIANT"}
    
    # A.12.4.1 - Logging
    try:
        logging = s3.get_bucket_logging(Bucket=bucket_name)
        is_enabled = 'LoggingEnabled' in logging
        report["controls"]["A.12.4.1"] = {
            "status": "COMPLIANT" if is_enabled else "PARTIAL",
            "evidence": logging
        }
    except:
        report["controls"]["A.12.4.1"] = {"status": "NON_COMPLIANT"}
    
    # A.18.1.3 - Retención
    try:
        lifecycle = s3.get_bucket_lifecycle_configuration(Bucket=bucket_name)
        report["controls"]["A.18.1.3"] = {
            "status": "COMPLIANT",
            "evidence": lifecycle['Rules']
        }
    except:
        report["controls"]["A.18.1.3"] = {"status": "PARTIAL"}
    
    # Resumen
    total = len(report["controls"])
    compliant = sum(1 for c in report["controls"].values() if c["status"] == "COMPLIANT")
    report["summary"] = {
        "compliance_rate": f"{(compliant/total)*100:.1f}%",
        "compliant": compliant,
        "total": total
    }
    
    return report

# Generar reporte
buckets_criticos = ["company-financial-reports-prod", "company-customer-data-prod"]
for bucket in buckets_criticos:
    report = generate_iso27001_compliance_report(bucket)
    print(json.dumps(report, indent=2))

Esto reduce fricción con el auditor y transmite algo clave: el sistema está pensado para ser controlado, no solo para funcionar.

El fallo que delata un SGSI inmaduro

El patrón se repite en auditorías:

Bucket creado "para ISO":
  ❌ Sin owner claro (tag vacío o "TBD")
  ❌ Sin lifecycle (datos crecen indefinidamente)
  ❌ Sin revisión periódica (última review: "nunca")
  ❌ Sin integración real en operaciones
  ❌ Sin procedimientos documentados
  ❌ Sin pruebas de recuperación
  ❌ "Lo hicimos para la auditoría del año pasado"

Resultado:
  - No conformidad mayor
  - Plan de acción correctiva
  - Revisión en 3 meses
  - Pérdida de confianza del auditor

Eso no es cumplimiento. Es teatro de seguridad.

Signos de madurez ISO27001 con S3:

Organización madura:
  ✅ Buckets documentados en registro de activos
  ✅ Owners identificados y responsables
  ✅ Tags estandarizados y verificados
  ✅ IaC (Terraform/CloudFormation) para todo
  ✅ Lifecycle aplicado por diseño
  ✅ Alertas configuradas proactivamente
  ✅ Revisiones trimestrales ejecutadas
  ✅ Pruebas de DR documentadas y exitosas
  ✅ Procedimientos operativos actualizados
  ✅ Métricas de cumplimiento monitoreadas
  ✅ Mejora continua documentada

Resultado:
  ✅ Auditoría fluida
  ✅ 0 no conformidades en S3
  ✅ Auditor satisfecho con evidencias
  ✅ Certificación sin observaciones

Checklist completo: S3 conforme a ISO27001

Usa esto antes de cada auditoría:

Checklist ISO27001 para buckets S3:

□ Gestión de activos (A.8):
  □ Bucket registrado en inventario de activos
  □ Owner y custodio técnico asignados
  □ Clasificación de información documentada
  □ Riesgos identificados y evaluados
  □ Tags completos y actualizados
  □ Revisión periódica programada (trimestral/anual)

□ Control de acceso (A.9):
  □ Block Public Access habilitado
  □ Bucket policy restrictiva implementada
  □ IAM roles con mínimo privilegio
  □ Acceso solo desde VPC/IPs autorizadas
  □ Matriz RACI documentada
  □ Revisión de permisos ejecutada
  □ MFA Delete en buckets críticos

□ Criptografía (A.10):
  □ Cifrado en reposo activo (SSE-S3/KMS)
  □ Cifrado en tránsito forzado (TLS)
  □ Decisión de cifrado documentada y justificada
  □ KMS keys con rotación (si aplica)
  □ Auditoría de uso de claves configurada

□ Operaciones seguras (A.12):
  □ Versionado habilitado (backups)
  □ Replicación cross-region (DR, si aplica)
  □ CloudTrail activo y monitoreado
  □ S3 Access Logs configurados
  □ Logs protegidos (bucket separado, inmutable)
  □ Lifecycle policies implementadas
  □ Retención según requisitos legales
  □ Pruebas de recuperación documentadas

□ Cumplimiento (A.18):
  □ Política de retención definida
  □ Lifecycle conforme a retención
  □ Object Lock para datos inmutables (si aplica)
  □ Procedimientos de borrado seguro
  □ Evidencias de auditoría disponibles
  □ Conformidad con GDPR/HIPAA (si aplica)

□ Documentación:
  □ Procedimientos operativos actualizados
  □ Runbooks de incidentes disponibles
  □ Registros de cambios (change log)
  □ Evidencias de revisiones periódicas
  □ Plan de continuidad documentado
  □ Matriz de cumplimiento actualizada

□ Monitoreo y alertas:
  □ CloudWatch Alarms configuradas
  □ EventBridge rules para cambios críticos
  □ Dashboard de compliance operativo
  □ Notificaciones a equipos responsables
  □ Reportes automáticos generados

Automatización del cumplimiento con AWS Config

Para organizaciones maduras, la verificación continua es clave:

# AWS Config Rules para ISO27001
resource "aws_config_config_rule" "s3_bucket_public_read_prohibited" {
  name = "s3-bucket-public-read-prohibited-iso27001"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"
  }

  depends_on = [aws_config_configuration_recorder.main]
}

resource "aws_config_config_rule" "s3_bucket_public_write_prohibited" {
  name = "s3-bucket-public-write-prohibited-iso27001"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_WRITE_PROHIBITED"
  }
}

resource "aws_config_config_rule" "s3_bucket_server_side_encryption_enabled" {
  name = "s3-bucket-server-side-encryption-enabled-iso27001"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED"
  }
}

resource "aws_config_config_rule" "s3_bucket_versioning_enabled" {
  name = "s3-bucket-versioning-enabled-iso27001"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_VERSIONING_ENABLED"
  }
}

resource "aws_config_config_rule" "s3_bucket_logging_enabled" {
  name = "s3-bucket-logging-enabled-iso27001"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_LOGGING_ENABLED"
  }
}

# Remediation automática
resource "aws_config_remediation_configuration" "s3_enable_versioning" {
  config_rule_name = aws_config_config_rule.s3_bucket_versioning_enabled.name

  target_type      = "SSM_DOCUMENT"
  target_identifier = "AWS-ConfigureS3BucketVersioning"
  target_version   = "1"

  parameter {
    name         = "BucketName"
    resource_value = "RESOURCE_ID"
  }

  parameter {
    name          = "VersioningState"
    static_value = "Enabled"
  }

  automatic                  = true
  maximum_automatic_attempts = 5
  retry_attempt_seconds      = 60
}

Cierre: ISO 27001 no se cumple, se opera

S3 puede ser uno de los pilares más sólidos de tu SGSI… o una fuente constante de no conformidades.

La diferencia no está en AWS, está en cómo piensas el servicio:

Mentalidad usuario:
  - "Es un disco en la nube"
  - "Para guardar cosas"
  - "Funciona solo"
  
Mentalidad Cloud Engineer ISO27001:
  - "Es un activo crítico de información"
  - "Bajo control técnico verificable"
  - "Con evidencias auditables"
  - "Integrado en el SGSI"
  - "Mejora continua"

Cuando usas S3 alineado con ISO 27001, no solo pasas auditorías. Construyes sistemas:

  • ✅ Más seguros (controles técnicos reales)
  • ✅ Más auditables (evidencias verificables)
  • ✅ Más profesionales (diseño consciente)
  • ✅ Más resilientes (DR probado)
  • ✅ Más eficientes (automatización)

Y eso, al final, es exactamente lo que busca cualquier organización que se toma la seguridad en serio.