Al final de 04-01 dejamos una promesa a medio cumplir. La política pol-mercadofresco-tienda exige que cada miniatura se suba con s3:x-amz-server-side-encryption: aws:kms, y rol-lambda-miniaturas tiene concedidos kms:Decrypt y kms:GenerateDataKey sobre una clave identificada por un UUID. Pero esa clave, alias/mercadofresco-datos, sigue siendo poco más que un nombre: nadie ha decidido quién la administra, quién puede usarla, cada cuánto rota, ni qué ocurre exactamente cuando S3 «cifra» un objeto.

Y hay una razón de negocio detrás. MercadoFresco guarda nombres, direcciones de reparto, teléfonos e historial de pedidos de clientes españoles. Eso son datos personales en el sentido del RGPD, y el artículo 32 del reglamento cita el cifrado explícitamente como medida técnica apropiada. Si un portátil con una copia de la base de datos acaba en el asiento trasero de un taxi, la diferencia entre «incidente» y «brecha notificable en 72 horas» suele ser si el contenido estaba cifrado.

AWS Key Management Service (KMS) es el servicio que custodia las claves de cifrado, controla quién puede usarlas y registra cada uso. En esta lección Marta lo entiende de verdad —incluido el mecanismo del cifrado de sobre, que explica todo lo demás— y cifra por fin las copias de la base de datos, los volúmenes y la instancia mercadofresco-pedidos.

Advertencia. Los ejemplos de esta lección son didácticos. Toda configuración de cifrado, gestión de claves o cumplimiento normativo (RGPD, PCI DSS, ENS) que vaya a aplicarse sobre datos reales de clientes debe ser revisada por un profesional de seguridad o de cumplimiento antes de llegar a producción. El cifrado mal implementado da una falsa sensación de seguridad, y una clave mal borrada destruye datos de forma irreversible. Todos los identificadores y datos de este curso son ficticios.

Contenido

  1. Por qué cifrar: el RGPD y los datos de MercadoFresco
  2. En reposo y en tránsito
  3. Criptografía simétrica y asimétrica, lo justo
  4. Qué es KMS y qué no es
  5. Tipos de clave
  6. Claves multirregión, material importado y CloudHSM
  7. El cifrado de sobre
  8. El descifrado, paso a paso
  9. Cifrado del lado del cliente y del lado del servidor
  10. SSE-S3, SSE-KMS y SSE-C
  11. Las claves de bucket de S3 y el coste de las peticiones
  12. La política de clave y su relación con IAM
  13. kms:ViaService y el contexto de cifrado
  14. Concesiones (grants)
  15. Rotación automática y qué significa realmente
  16. Cifrar mercadofresco-copias-basedatos
  17. Cifrar volúmenes de EBS y snapshots
  18. Cifrar mercadofresco-pedidos, que ya existe sin cifrar
  19. Compartir un snapshot cifrado con otra cuenta
  20. KMS desde boto3
  21. Límites, cuotas y coste real para MercadoFresco
  22. ACM no es KMS
  23. Eliminar una clave: los 7 a 30 días de espera

Por qué cifrar: el RGPD y los datos de MercadoFresco

El cifrado no protege de todo. Protege de un conjunto muy concreto de amenazas, y conviene ser honesto sobre cuál:

Amenaza ¿Ayuda el cifrado en reposo?
Alguien roba un disco físico del centro de datos , totalmente
Un snapshot o una copia se comparte por error con otra cuenta , si no tiene la clave
Un bucket queda expuesto públicamente con SSE-KMS: además del permiso de S3 hace falta el de KMS
Se cumple el requisito normativo de una auditoría , y además con registro de cada uso
Un atacante roba las credenciales de la aplicación No: la aplicación tiene permiso para descifrar
Una inyección SQL extrae la tabla de clientes No: la base de datos devuelve los datos ya en claro

Esa tabla es importante: el cifrado en reposo es una capa, no un escudo mágico. Las dos últimas filas son exactamente las razones por las que 04-01 existe y por las que 04-05 existirá.

Para MercadoFresco hay tres conjuntos de datos que deben cifrarse sin discusión:

  1. mercadofresco-copias-basedatos: copias completas de la base de datos pedidos, con nombres, direcciones y teléfonos de todos los clientes. Es el activo más peligroso de la cuenta.
  2. La instancia mercadofresco-pedidos y sus snapshots automáticos.
  3. Los volúmenes de EBS de las instancias de la tienda y el volumen mercadofresco-fotos-01.

El catálogo de fotos públicas (productos/) no contiene datos personales, pero se cifra igualmente porque el coste marginal es cero y no tener que pensar en qué está cifrado y qué no es en sí una ventaja operativa.

En reposo y en tránsito

Son dos problemas distintos, resueltos por mecanismos distintos:

En reposo En tránsito
Qué protege Datos guardados en disco Datos viajando por la red
Mecanismo AES-256 sobre el volumen, el objeto o la fila TLS 1.2/1.3
Servicio KMS ACM para los certificados
En MercadoFresco S3, EBS, RDS, snapshots HTTPS en CloudFront y en el ALB (03-03, 03-05)
Cómo se fuerza Cifrado por defecto del bucket, kms: en la política Deny con aws:SecureTransport: false

El cifrado en tránsito ya está resuelto desde el módulo 3: el certificado de ACM en el ALB y en CloudFront, y el Deny que escribimos en pol-mercadofresco-tienda cuando aws:SecureTransport es falso. Esta lección va del otro.

Criptografía simétrica y asimétrica, lo justo

No hace falta saber criptografía para usar KMS, pero sí distinguir dos familias:

Simétrica. La misma clave cifra y descifra. Es rapidísima: AES-256 cifra cientos de megabytes por segundo en cualquier CPU moderna. Su problema es la distribución: hay que hacer llegar la clave al otro extremo sin que nadie la intercepte.

Asimétrica. Dos claves relacionadas matemáticamente: la pública cifra y la privada descifra (o al revés, para firmar). Resuelve la distribución —la pública puede publicarse— pero es órdenes de magnitud más lenta y solo puede cifrar datos muy pequeños (una clave RSA de 2048 bits cifra como mucho 190 bytes).

Simétrica Asimétrica
Claves Una Par pública/privada
Velocidad Muy rápida Lenta
Tamaño de datos Ilimitado Cientos de bytes
Algoritmo típico AES-256-GCM RSA-2048, ECC
Uso en KMS Por defecto, cifrado de datos Firma, cifrado entre partes sin secreto compartido

En KMS, salvo que pidas otra cosa, todas las claves son simétricas AES-256. Y la combinación de ambas ideas —usar simétrica para los datos y proteger esa clave con otra clave— es exactamente el cifrado de sobre que vemos en dos apartados.

Qué es KMS y qué no es

KMS es un servicio que:

  • Genera y custodia claves maestras dentro de módulos de hardware certificados FIPS 140-3 nivel 3, de los que el material de la clave nunca sale en claro.
  • Ejecuta operaciones criptográficas (Encrypt, Decrypt, GenerateDataKey, Sign, Verify) sobre datos pequeños.
  • Controla quién puede usar cada clave, mediante políticas.
  • Registra cada llamada en CloudTrail (05-03), incluido quién descifró qué y cuándo.

KMS no es:

  • Un almacén de secretos: para contraseñas y cadenas de conexión está Secrets Manager (04-03).
  • Un servicio para cifrar ficheros grandes: hay un límite duro de 4 KB por llamada a Encrypt.
  • Una autoridad de certificación: eso es ACM, y lo aclaramos al final.
  • Un sitio de donde puedas extraer tu clave para llevártela. Con las claves gestionadas por KMS, el material no se puede exportar. Es una garantía, no una limitación.

Esa última frase merece detenerse: si el material nunca sale, ¿cómo se cifran los datos? La respuesta es el cifrado de sobre.

Tipos de clave

En KMS la unidad fundamental es la KMS key (antes llamada CMK, Customer Master Key). Hay tres categorías por origen:

Tipo Quién la crea Política Rotación Coste Cuándo
Propiedad de AWS AWS, compartida entre clientes No la ves AWS decide Gratis Cifrado por defecto de S3 (SSE-S3)
Gestionada por AWS (aws/s3, aws/rds, aws/ebs) AWS, una por servicio y cuenta No editable Anual, obligatoria Gratis (sí las peticiones) Empezar rápido, sin control fino
Gestionada por el cliente (CMK) Editable Opcional, configurable 1 USD/mes Cuando necesitas controlar quién usa la clave

La diferencia decisiva entre las dos últimas no es el precio, es el control:

  • Con una clave gestionada por AWS no puedes editar la política de clave. Cualquier principal de tu cuenta con permisos de S3 podrá descifrar. No puedes usarla desde otra cuenta ni denegar su uso a un rol concreto.
  • Con una clave gestionada por el cliente decides exactamente quién administra y quién usa, puedes denegarle el acceso a un rol aunque tenga permisos de S3, puedes compartirla entre cuentas y puedes desactivarla, lo que convierte todos los datos cifrados con ella en ilegibles al instante. Eso último es un botón de emergencia muy potente.

Para mercadofresco-copias-basedatos, que contiene datos personales, la clave debe ser gestionada por el cliente. Ese es el papel de alias/mercadofresco-datos.

Y una nota sobre los alias: un alias es un puntero mutable a una clave. Usar alias/mercadofresco-datos en el código en lugar del UUID permite cambiar la clave subyacente sin tocar la aplicación. Cada alias es regional y no puede repetirse dentro de la región.

Claves multirregión, material importado y CloudHSM

Tres variantes que conviene conocer aunque MercadoFresco no las use hoy:

Claves multirregión. Una clave primaria en eu-west-1 y réplicas en otras regiones que comparten el mismo material criptográfico y el mismo identificador (con prefijo mrk-). Lo que se cifra en una región se descifra en otra sin llamadas entre regiones. Es imprescindible para copias de seguridad entre regiones y para recuperación ante desastres. Rompe el aislamiento regional, así que solo cuando de verdad haga falta.

Material importado (BYOK). Traes tu propio material de clave y KMS solo lo custodia. Se usa cuando una normativa exige que la organización genere las claves. Tiene una contrapartida seria: si pierdes tu copia del material y expira en KMS, los datos son irrecuperables. Y no admite rotación automática.

Almacén de claves personalizado sobre CloudHSM. El material vive en un clúster de CloudHSM de tu propiedad, dedicado, con inquilino único. Es lo que exigen algunos entornos financieros o del sector público. Cuesta del orden de 1,50 USD por hora y HSM —más de 1.000 USD al mes por HSM, y se recomiendan dos—, así que es una decisión de cumplimiento, no de ingeniería. Solo lo mencionamos.

El cifrado de sobre

Este es el concepto central de la lección. Si el material de la clave nunca sale de KMS y Encrypt solo admite 4 KB, ¿cómo se cifra un snapshot de 200 GB?

La respuesta es que los datos grandes nunca viajan a KMS. Se usan dos niveles de clave:

  • La clave maestra (la KMS key) vive en KMS y no sale nunca.
  • La clave de datos (data key) es una clave AES-256 de un solo uso que KMS genera, entrega en dos formatos y luego olvida.
sequenceDiagram
    participant A as Aplicacion / S3
    participant K as KMS<br/>alias/mercadofresco-datos
    participant D as Disco

    Note over A,D: CIFRADO
    A->>K: GenerateDataKey(KeyId, KeySpec=AES_256)
    K->>K: Genera una clave AES-256 aleatoria
    K-->>A: Plaintext (clave en claro)<br/>+ CiphertextBlob (la misma, cifrada<br/>con la clave maestra)
    A->>A: Cifra los 200 GB con Plaintext (AES local, rapido)
    A->>A: BORRA Plaintext de memoria
    A->>D: Guarda: datos cifrados + CiphertextBlob juntos

Los pasos, en palabras:

  1. La aplicación pide a KMS una clave de datos. No envía los datos.
  2. KMS genera una clave AES-256 aleatoria y devuelve dos versiones de la misma clave: en claro (Plaintext) y cifrada con la clave maestra (CiphertextBlob).
  3. La aplicación cifra los 200 GB localmente con la versión en claro. Va a velocidad de CPU, sin red de por medio.
  4. La aplicación borra de memoria la versión en claro. Este paso es el que hace que el sistema sea seguro.
  5. Guarda juntos los datos cifrados y el CiphertextBlob. El blob es inútil para quien no pueda llamar a kms:Decrypt sobre la clave maestra.

Ventajas de este diseño, todas consecuencia del mismo truco:

Ventaja Por qué
Rendimiento Los datos se cifran localmente a velocidad de CPU, no de red
Coste Una llamada a KMS por fichero (o menos), no por byte
Sin límite de tamaño El límite de 4 KB solo afecta a la clave de datos, que ocupa 32 bytes
Rotación barata Rotar la maestra no obliga a recifrar los datos (lo vemos más adelante)
Trazabilidad Cada GenerateDataKey y cada Decrypt queda en CloudTrail

El descifrado, paso a paso

sequenceDiagram
    participant D as Disco
    participant A as Aplicacion / S3
    participant K as KMS

    Note over D,K: DESCIFRADO
    D-->>A: datos cifrados + CiphertextBlob
    A->>K: Decrypt(CiphertextBlob)
    K->>K: Comprueba la politica de clave<br/>y los permisos IAM del llamante
    alt Autorizado
        K-->>A: Plaintext (clave de datos en claro)
        A->>A: Descifra los datos localmente
        A->>A: BORRA Plaintext
    else No autorizado
        K-->>A: AccessDeniedException
        Note over A: Los datos son ilegibles<br/>aunque tengas el fichero
    end

Fíjate en el punto crítico: para descifrar no hace falta indicar qué clave se usó. El CiphertextBlob lleva dentro el identificador de la clave maestra, y KMS la localiza sola. Por eso Decrypt no necesita el parámetro KeyId con claves simétricas.

Y fíjate en la rama else: quien tenga el fichero pero no permiso sobre la clave se queda con ruido. Ese es el motivo de que un snapshot cifrado compartido por error siga siendo seguro.

Cifrado del lado del cliente y del lado del servidor

Del lado del servidor (SSE) Del lado del cliente (CSE)
Quién cifra AWS, al recibir el dato Tu aplicación, antes de enviarlo
Qué ve AWS El dato en claro durante el procesamiento Solo datos cifrados, nunca en claro
Complejidad Casi ninguna: una casilla Alta: gestionas el proceso
Búsquedas, índices Funcionan No funcionan sobre lo cifrado
Cuándo El 95 % de los casos Confidencialidad extrema frente al proveedor

MercadoFresco usa del lado del servidor en todo. El cifrado del lado del cliente tiene sentido cuando el modelo de amenaza incluye al propio proveedor de nube o cuando una normativa lo exige; para una tienda de producto fresco, la complejidad adicional no compensa. Si algún día hiciera falta, existe el AWS Encryption SDK y el cliente cifrado de S3, que implementan el sobre por ti.

SSE-S3, SSE-KMS y SSE-C

S3 ofrece tres formas de cifrado del lado del servidor. La diferencia está en quién controla la clave:

SSE-S3 SSE-KMS SSE-C
Clave maestra Propiedad de AWS Tuya, en KMS Tuya, la envías en cada petición
Cabecera AES256 aws:kms aws:kms no; cabeceras -customer-
Política de acceso Solo IAM de S3 IAM de S3 y política de clave La tienes tú
Registro en CloudTrail No Sí, cada uso No
Coste Gratis 1 USD/mes + peticiones Gratis en KMS
Rotación Automática, invisible Configurable y auditable Manual, tuya
Doble autorización No No
Cuándo Datos no sensibles Datos personales, cumplimiento Casi nunca

La fila decisiva es doble autorización. Con SSE-KMS, para leer un objeto hacen falta dos permisos: s3:GetObject y kms:Decrypt sobre la clave. Si un bucket con datos personales quedara accidentalmente expuesto al público, el atacante obtendría el objeto cifrado y no podría abrirlo, porque no tiene permiso sobre la clave. Es una red de seguridad real que SSE-S3 no da.

SSE-C, donde tú envías la clave en cada petición y AWS la usa y la olvida, obliga a gestionar la distribución de claves por tu cuenta y no deja registro. Se menciona por completitud.

Decisión para MercadoFresco:

Bucket Cifrado Motivo
mercadofresco-copias-basedatos SSE-KMS con alias/mercadofresco-datos Datos personales, RGPD, auditoría
mercadofresco-catalogo-fotos SSE-KMS Coherencia; ya lo exige la política de la tienda
mercadofresco-informes-analitica SSE-KMS Datos agregados de negocio
mercadofresco-registros-web SSE-S3 Volumen alto, sin datos personales directos
mercadofresco-tienda-web-desarrollo SSE-S3 Contenido estático público

Las claves de bucket de S3 y el coste de las peticiones

Aquí hay una trampa económica que hunde presupuestos. Sin nada más, cada objeto que se sube o se descarga de un bucket con SSE-KMS genera una llamada a KMS. mercadofresco-catalogo-fotos sirve —antes de CloudFront— del orden de 2 millones de peticiones al mes. A 0,03 USD por cada 10.000 peticiones, eso son 6 USD al mes solo en llamadas a KMS, más que la propia clave.

La solución es la clave de bucket de S3 (S3 Bucket Key): S3 pide a KMS una clave de nivel de bucket, la mantiene en caché unas horas y deriva de ella las claves de los objetos individuales.

Sin clave de bucket Con clave de bucket
Llamadas a KMS Una por objeto Una cada pocas horas
Reducción de coste Hasta el 99 %
Registro en CloudTrail Una entrada por objeto Una entrada por clave de bucket
Coste añadido Ninguno

La contrapartida es la tercera fila: pierdes la traza por objeto en CloudTrail. Para mercadofresco-catalogo-fotos, con millones de fotos públicas, es un intercambio excelente. Para mercadofresco-copias-basedatos, donde quieres saber exactamente quién descifró qué copia y cuándo, puede interesar dejarla desactivada: son pocas peticiones al mes y la traza vale más que el ahorro.

Actívala siempre que no necesites la traza por objeto. Es gratis y ahorra un 99 %.

La política de clave y su relación con IAM

Aquí está la excepción que anunciamos en 04-01. En casi todos los servicios, en la misma cuenta basta con que una de las dos políticas conceda. En KMS no. Toda clave tiene una política de clave (una política basada en recurso) y:

Si la política de clave no lo permite, nadie puede usar la clave. Ni el administrador de la cuenta, ni siquiera el root.

Esto tiene una consecuencia que hay que grabarse a fuego: si creas una clave por CLI con una política que no incluye a nadie, la clave queda inutilizable para siempre y solo se puede programar su eliminación. AWS lo evita en la consola, pero no por CLI.

Hay dos modelos:

  1. La política de clave delega en IAM. Se incluye la declaración "Principal": {"AWS": "arn:aws:iam::111122223333:root"} con "Action": "kms:*", lo que significa «las políticas de IAM de esta cuenta pueden conceder acceso a esta clave». Es lo que hace la consola por defecto.
  2. La política de clave concede directamente, nombrando roles concretos. Más restrictivo y más explícito.

MercadoFresco usa un modelo mixto: delega en IAM para la administración pero nombra explícitamente a quien usa la clave. Esta es la política completa de alias/mercadofresco-datos:

{
  "Version": "2012-10-17",
  "Id": "politica-mercadofresco-datos",
  "Statement": [
    {
      "Sid": "PermitirQueIAMGobierneLaClave",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "MartaAdministraLaClavePeroNoLaUsa",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:user/marta" },
      "Action": [
        "kms:Create*", "kms:Describe*", "kms:Enable*", "kms:List*", "kms:Put*",
        "kms:Update*", "kms:Revoke*", "kms:Disable*", "kms:Get*", "kms:Delete*",
        "kms:TagResource", "kms:UntagResource", "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "LosRolesDeAplicacionUsanLaClave",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
          "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda",
          "arn:aws:iam::111122223333:role/rol-lambda-miniaturas"
        ]
      },
      "Action": [
        "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*",
        "kms:GenerateDataKey*", "kms:DescribeKey"
      ],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": ["s3.eu-west-1.amazonaws.com"]
        }
      }
    },
    {
      "Sid": "PermitirQueRDSYEBSUsenLaClaveMedianteConcesiones",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "kms:CreateGrant",
      "Resource": "*",
      "Condition": {
        "Bool": { "kms:GrantIsForAWSResource": "true" },
        "StringEquals": {
          "kms:ViaService": ["rds.eu-west-1.amazonaws.com", "ec2.eu-west-1.amazonaws.com"]
        }
      }
    },
    {
      "Sid": "ProhibirElUsoFueraDeEuWest1",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "kms:*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": { "aws:RequestedRegion": "eu-west-1" }
      }
    }
  ]
}

Declaración por declaración:

  • PermitirQueIAMGobierneLaClave: el «interruptor» que hace que las políticas de IAM cuenten para esta clave. Sin él, ninguna política de identidad tendría efecto sobre ella. Es lo que evita quedarte fuera de tu propia clave.
  • MartaAdministraLaClavePeroNoLaUsa: Marta puede habilitar, deshabilitar, etiquetar, cambiar la política y programar la eliminación... pero no aparece kms:Decrypt ni kms:Encrypt. Esa es la separación de funciones: quien administra la clave no puede leer los datos que protege. Es un requisito habitual en auditorías y aquí se ve implementado en dos líneas.
  • LosRolesDeAplicacionUsanLaClave: los dos roles que formalizamos en 04-01 pueden cifrar y descifrar, pero solo a través de S3 gracias a kms:ViaService. Si alguien roba las credenciales de la instancia y llama a kms:Decrypt directamente contra un blob que consiguió por otra vía, KMS lo rechaza.
  • PermitirQueRDSYEBSUsenLaClaveMedianteConcesiones: RDS y EBS no llaman a la clave directamente; crean concesiones en tu nombre. La condición kms:GrantIsForAWSResource limita esa capacidad a los servicios de AWS, no a personas.
  • ProhibirElUsoFueraDeEuWest1: barrera de residencia de datos. Encaja con la argumentación de RGPD sobre mantener el tratamiento dentro de la UE.

Y el rol de la tienda, además, necesita en su política de identidad los mismos permisos de KMS —recuerda: en la misma cuenta basta con que una conceda, salvo que la de recurso deniegue, pero en KMS la de recurso siempre debe permitir. En la práctica se escriben ambas.

kms:ViaService y el contexto de cifrado

Dos mecanismos que refinan el control y que aparecen constantemente en políticas reales.

kms:ViaService. Restringe el uso de la clave a peticiones que llegan a través de un servicio concreto, y solo cuando el servicio actúa en nombre del principal. Valores típicos: s3.eu-west-1.amazonaws.com, rds.eu-west-1.amazonaws.com, ec2.eu-west-1.amazonaws.com, secretsmanager.eu-west-1.amazonaws.com. Es la condición más útil de KMS: convierte una clave genérica en una clave «solo para S3».

Contexto de cifrado (encryption context). Un diccionario de pares clave-valor que se asocia a una operación de cifrado. No es secreto —aparece en los registros de CloudTrail en claro— pero está autenticado: si al descifrar no proporcionas exactamente el mismo contexto, la operación falla.

contexto = {"proyecto": "mercadofresco", "componente": "copias", "entorno": "produccion"}

Sirve para dos cosas:

  1. Auditoría legible. En CloudTrail ves «se descifró algo del componente copias» en lugar de un UUID opaco.
  2. Autorización granular. Se puede condicionar la política de clave a un contexto concreto:
{
  "Sid": "SoloDescifrarCopias",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-restauracion" },
  "Action": "kms:Decrypt",
  "Resource": "*",
  "Condition": {
    "StringEquals": { "kms:EncryptionContext:componente": "copias" }
  }
}

Ese rol puede descifrar copias de la base de datos y nada más, aunque todo esté cifrado con la misma clave. S3 usa automáticamente como contexto el ARN del bucket u objeto, así que ya te beneficias de esto sin hacer nada.

Concesiones (grants)

Una concesión es un permiso temporal y granular sobre una clave, pensado para servicios y procesos, no para personas:

Política de clave Concesión
Formato JSON Llamada a la API
Duración Permanente hasta que se edite Se retira con RetireGrant
Granularidad Principal + acción + condición Principal + operaciones concretas + contexto
Quién las usa Humanos y roles Servicios de AWS, sobre todo

Cuando creas una instancia de RDS cifrada, RDS crea una concesión sobre la clave para poder descifrar el volumen en cada arranque, en cada failover y en cada restauración de snapshot. Por eso la política de clave debía permitir kms:CreateGrant con kms:GrantIsForAWSResource.

Consecuencia práctica muy importante: si retiras esa concesión, la base de datos deja de arrancar. Y si la concesión se retira mientras la base está parada, no volverá. No toques las concesiones que crean los servicios.

aws kms list-grants --key-id alias/mercadofresco-datos \
  --query 'Grants[].[GranteePrincipal,Operations[0],Name]' --output table \
  --profile mercadofresco-dev

Rotación automática y qué significa realmente

Se puede activar la rotación anual con una llamada:

aws kms enable-key-rotation --key-id alias/mercadofresco-datos --profile mercadofresco-dev
aws kms get-key-rotation-status --key-id alias/mercadofresco-datos --profile mercadofresco-dev

Y ahora la parte que casi todo el mundo entiende mal. Cuando KMS rota una clave:

  • Genera nuevo material criptográfico y lo asocia a la misma clave lógica y al mismo ARN.
  • Conserva todo el material anterior, indefinidamente.
  • El material nuevo se usa para las operaciones nuevas de cifrado.
  • Los datos ya cifrados no se recifran. Siguen usando su material antiguo, que KMS localiza solo.

O sea: la rotación es transparente y gratuita, pero no «rejuvenece» los datos antiguos. Si un atacante hubiera obtenido de algún modo el material antiguo, los datos cifrados con él siguen comprometidos. Para renovar de verdad los datos hay que recifrarlos, copiando los objetos sobre sí mismos o usando ReEncrypt.

Rotación automática de KMS Recifrado real de los datos
Qué cambia El material de las operaciones nuevas Los datos ya guardados
Coste Gratis Peticiones a KMS + tiempo
Cumplimiento Cubre «rotación anual de claves» Cubre «renovación tras un incidente»
Esfuerzo Un comando Un proceso por lotes

Desde 2024 el periodo de rotación es configurable entre 90 y 2.560 días con --rotation-period-in-days. Para MercadoFresco, la rotación anual por defecto es suficiente y es lo que la mayoría de auditorías esperan ver.

Cifrar mercadofresco-copias-basedatos

Primero, crear la clave con la política del apartado anterior:

aws kms create-key \
  --description "Clave de datos personales de MercadoFresco (RGPD)" \
  --key-usage ENCRYPT_DECRYPT \
  --key-spec SYMMETRIC_DEFAULT \
  --policy file:///tmp/politica-clave.json \
  --tags TagKey=Proyecto,TagValue=mercadofresco TagKey=Entorno,TagValue=produccion \
         TagKey=Componente,TagValue=datos TagKey=Propietario,TagValue=marta \
         TagKey=CentroCoste,TagValue=tecnologia \
  --profile mercadofresco-dev

# Devuelve KeyId: 1234abcd-12ab-34cd-56ef-1234567890ab
aws kms create-alias \
  --alias-name alias/mercadofresco-datos \
  --target-key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
  --profile mercadofresco-dev

aws kms enable-key-rotation --key-id alias/mercadofresco-datos --profile mercadofresco-dev

Después, cifrado por defecto en el bucket. Fíjate en que aquí no activamos la clave de bucket, porque queremos la traza por objeto:

aws s3api put-bucket-encryption \
  --bucket mercadofresco-copias-basedatos \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
      },
      "BucketKeyEnabled": false
    }]
  }' \
  --profile mercadofresco-dev

En el catálogo de fotos, en cambio, sí la activamos:

aws s3api put-bucket-encryption \
  --bucket mercadofresco-catalogo-fotos \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos"
      },
      "BucketKeyEnabled": true
    }]
  }' \
  --profile mercadofresco-dev

Y por último, la política de bucket que rechaza cualquier subida sin cifrar, para que el cifrado por defecto no dependa de que nadie lo desactive:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RechazarSubidasSinKMS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::mercadofresco-copias-basedatos/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "RechazarOtraClave",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::mercadofresco-copias-basedatos/*",
      "Condition": {
        "StringNotEqualsIfExists": {
          "s3:x-amz-server-side-encryption-aws-kms-key-id":
            "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
        }
      }
    }
  ]
}

Importante: el cifrado por defecto no cifra retroactivamente. Los objetos que ya estaban en el bucket siguen como estaban. Para cifrarlos hay que copiarlos sobre sí mismos:

aws s3 cp s3://mercadofresco-copias-basedatos/ s3://mercadofresco-copias-basedatos/ \
  --recursive --sse aws:kms \
  --sse-kms-key-id arn:aws:kms:eu-west-1:111122223333:alias/mercadofresco-datos \
  --metadata-directive REPLACE \
  --profile mercadofresco-dev

Con versionado activo esto crea una versión nueva cifrada y deja la antigua sin cifrar. Hay que eliminar las versiones antiguas después, o el trabajo no sirve de nada. Es un detalle que se olvida con frecuencia y que una auditoría detecta enseguida.

Cifrar volúmenes de EBS y snapshots

Lo más sencillo: activar el cifrado por defecto de EBS en la región. A partir de ese momento, todo volumen nuevo se cifra sin que nadie tenga que acordarse.

aws ec2 enable-ebs-encryption-by-default --region eu-west-1 --profile mercadofresco-dev
aws ec2 modify-ebs-default-kms-key-id \
  --kms-key-id alias/mercadofresco-datos --region eu-west-1 --profile mercadofresco-dev
aws ec2 get-ebs-default-kms-key-id --region eu-west-1 --profile mercadofresco-dev

Para un volumen ya existente sin cifrar —como mercadofresco-fotos-01, creado en 02-02— no se puede cifrar en caliente. El procedimiento es snapshot, copia cifrada, volumen nuevo:

# 1. Snapshot del volumen sin cifrar
aws ec2 create-snapshot --volume-id vol-0abc123 \
  --description "mercadofresco-fotos-01 previo al cifrado" \
  --profile mercadofresco-dev

# 2. Copiar el snapshot CIFRANDO en el proceso
aws ec2 copy-snapshot \
  --source-region eu-west-1 --source-snapshot-id snap-0abc123 \
  --encrypted --kms-key-id alias/mercadofresco-datos \
  --description "mercadofresco-fotos-01 cifrado" \
  --profile mercadofresco-dev

# 3. Crear el volumen nuevo desde el snapshot cifrado
aws ec2 create-volume --snapshot-id snap-0def456 \
  --availability-zone eu-west-1a --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=mercadofresco-fotos-01},
      {Key=Proyecto,Value=mercadofresco},{Key=Entorno,Value=produccion}]' \
  --profile mercadofresco-dev

# 4. Parar la instancia, desmontar el viejo, montar el nuevo, arrancar

El truco está en el paso 2: copy-snapshot --encrypted es la única operación de EBS que convierte algo sin cifrar en algo cifrado. Recuérdalo, porque es la pieza que falta en casi todos los intentos.

Y una regla que ahorra sustos: de un volumen cifrado solo salen snapshots cifrados, y de un snapshot cifrado solo salen volúmenes cifrados. La propiedad se hereda y no se puede quitar.

Cifrar mercadofresco-pedidos, que ya existe sin cifrar

Esta es la operación más delicada del módulo, porque implica tiempo de inactividad de la base de datos de producción. RDS no permite cifrar una instancia existente. El camino es:

flowchart TD
    A["mercadofresco-pedidos<br/>SIN cifrar (produccion)"] --> B["1. Snapshot manual<br/>mercadofresco-pedidos-precifrado"]
    B --> C["2. copy-db-snapshot --kms-key-id<br/>alias/mercadofresco-datos"]
    C --> D["3. restore-db-instance-from-db-snapshot<br/>mercadofresco-pedidos-cifrada"]
    D --> E["4. Verificar: datos, parametros,<br/>grupo de subredes, SG"]
    E --> F["5. Ventana de mantenimiento:<br/>parar escrituras"]
    F --> G["6. Renombrar: la vieja pasa a -antigua,<br/>la nueva a mercadofresco-pedidos"]
    G --> H["7. Reactivar Multi-AZ y la replica<br/>mercadofresco-pedidos-lectura"]
    H --> I["8. Conservar la vieja 7 dias,<br/>despues eliminar"]
# 1 y 2
aws rds create-db-snapshot \
  --db-instance-identifier mercadofresco-pedidos \
  --db-snapshot-identifier mercadofresco-pedidos-precifrado \
  --profile mercadofresco-dev

aws rds copy-db-snapshot \
  --source-db-snapshot-identifier mercadofresco-pedidos-precifrado \
  --target-db-snapshot-identifier mercadofresco-pedidos-cifrado \
  --kms-key-id alias/mercadofresco-datos \
  --profile mercadofresco-dev

# 3
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier mercadofresco-pedidos-cifrada \
  --db-snapshot-identifier mercadofresco-pedidos-cifrado \
  --db-subnet-group-name sng-mercadofresco-datos \
  --db-parameter-group-name pg16-mercadofresco \
  --multi-az \
  --no-publicly-accessible \
  --profile mercadofresco-dev

Cuatro avisos de campo:

  • La restauración no conserva el grupo de parámetros, los grupos de seguridad ni la configuración Multi-AZ. Hay que indicarlos explícitamente, como en el comando de arriba, o la nueva instancia arrancará con los valores por defecto y quedará mal configurada.
  • El nombre del punto de enlace cambia. Si la aplicación apunta al DNS de RDS, hay que actualizarlo. Por eso en 03-05 creamos la zona privada interno.mercadofresco.example: si la tienda se conecta a pedidos.interno.mercadofresco.example, basta con cambiar un CNAME.
  • La réplica mercadofresco-pedidos-lectura no se migra. Hay que recrearla desde la instancia nueva.
  • Haz el ensayo completo en desarrollo antes. Marta hace esta migración un martes por la noche, nunca un jueves: el pico de los viernes no es momento para descubrir sorpresas.

Compartir un snapshot cifrado con otra cuenta

Un snapshot cifrado con la clave gestionada por AWS (aws/rds) no se puede compartir. Solo se comparten los cifrados con una clave gestionada por el cliente, y hacen falta dos permisos:

# 1. Compartir el snapshot con la cuenta de destino
aws rds modify-db-snapshot-attribute \
  --db-snapshot-identifier mercadofresco-pedidos-cifrado \
  --attribute-name restore --values-to-add 444455556666 \
  --profile mercadofresco-dev
{
  "Sid": "PermitirQueLaCuentaDeAuditoriaDescifreElSnapshot",
  "Effect": "Allow",
  "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
  "Action": ["kms:Decrypt", "kms:DescribeKey", "kms:CreateGrant"],
  "Resource": "*",
  "Condition": {
    "StringEquals": { "kms:ViaService": "rds.eu-west-1.amazonaws.com" }
  }
}

Es decir: compartir el snapshot y dar acceso a la clave. Si solo haces lo primero, la otra cuenta verá el snapshot y no podrá restaurarlo. Y no olvides que la cuenta de destino también necesita permitirlo en sus propias políticas de identidad.

Este es, dicho sea de paso, el mecanismo que hace segura la exposición accidental: compartir un snapshot sin compartir la clave no revela absolutamente nada.

KMS desde boto3

El caso de uso típico —cifrar un fichero de tamaño arbitrario con el sobre, a mano:

import boto3
import os
from cryptography.fernet import Fernet   # solo para el ejemplo local
import base64

sesion = boto3.Session(profile_name="mercadofresco-dev", region_name="eu-west-1")
kms = sesion.client("kms")

CLAVE = "alias/mercadofresco-datos"
CONTEXTO = {"proyecto": "mercadofresco", "componente": "copias"}


def cifrar_fichero(ruta_origen: str, ruta_destino: str) -> None:
    """Cifra un fichero de cualquier tamano usando cifrado de sobre."""
    # 1. Pedir una clave de datos: llegan las DOS versiones
    respuesta = kms.generate_data_key(
        KeyId=CLAVE,
        KeySpec="AES_256",
        EncryptionContext=CONTEXTO,
    )
    clave_en_claro = respuesta["Plaintext"]        # 32 bytes, para cifrar aqui
    clave_cifrada = respuesta["CiphertextBlob"]    # se guarda junto al fichero

    # 2. Cifrar localmente. Los datos NUNCA salen hacia KMS.
    f = Fernet(base64.urlsafe_b64encode(clave_en_claro))
    with open(ruta_origen, "rb") as origen:
        datos_cifrados = f.encrypt(origen.read())

    # 3. Guardar la clave cifrada + los datos cifrados en el mismo fichero
    with open(ruta_destino, "wb") as destino:
        destino.write(len(clave_cifrada).to_bytes(4, "big"))
        destino.write(clave_cifrada)
        destino.write(datos_cifrados)

    # 4. Borrar de memoria la version en claro
    del clave_en_claro


def descifrar_fichero(ruta_origen: str, ruta_destino: str) -> None:
    with open(ruta_origen, "rb") as origen:
        longitud = int.from_bytes(origen.read(4), "big")
        clave_cifrada = origen.read(longitud)
        datos_cifrados = origen.read()

    # KMS deduce la clave maestra del propio blob: no hace falta KeyId
    respuesta = kms.decrypt(
        CiphertextBlob=clave_cifrada,
        EncryptionContext=CONTEXTO,   # debe coincidir EXACTAMENTE
    )
    f = Fernet(base64.urlsafe_b64encode(respuesta["Plaintext"]))

    with open(ruta_destino, "wb") as destino:
        destino.write(f.decrypt(datos_cifrados))


# Cifrado directo, solo para datos de menos de 4 KB
pequeno = kms.encrypt(KeyId=CLAVE, Plaintext=b"dato corto", EncryptionContext=CONTEXTO)
print(kms.decrypt(CiphertextBlob=pequeno["CiphertextBlob"],
                  EncryptionContext=CONTEXTO)["Plaintext"])

Puntos a fijar del ejemplo:

  • generate_data_key devuelve las dos versiones de la misma clave. Esa es la esencia del sobre.
  • El fichero cifrado es autocontenido: lleva dentro la clave cifrada. Se puede mover a cualquier sitio y solo lo abrirá quien tenga permiso sobre alias/mercadofresco-datos.
  • decrypt no recibe KeyId: el blob lo lleva dentro.
  • El EncryptionContext debe coincidir carácter a carácter al descifrar. Si no, InvalidCiphertext.
  • En producción no se usa Fernet a mano: el AWS Encryption SDK hace todo esto correctamente, incluidos el borrado seguro de memoria y el formato de mensaje. El ejemplo es para ver la mecánica.

Límites, cuotas y coste real para MercadoFresco

Concepto Precio
Clave gestionada por el cliente 1,00 USD por clave y mes (prorrateado)
Peticiones simétricas 0,03 USD por cada 10.000
Peticiones RSA-2048 0,03 USD por cada 10.000
Peticiones ECC / RSA >2048 0,15 USD por cada 10.000
Claves gestionadas y propiedad de AWS Gratis (las peticiones se cobran)
Capa gratuita 20.000 peticiones al mes, siempre
Almacén personalizado (CloudHSM) ~1,50 USD/hora por HSM, aparte

Cuotas de peticiones por segundo en eu-west-1: del orden de 50.000 para Decrypt, GenerateDataKey y Encrypt simétricos, y muy inferiores (unos cientos) para operaciones asimétricas. Se comparten entre todas las claves de la cuenta, así que un proceso por lotes agresivo puede provocar ThrottlingException en toda la cuenta. Los SDK reintentan con retroceso exponencial, pero conviene saberlo.

Cálculo para MercadoFresco:

Concepto Cantidad Coste mensual
Clave alias/mercadofresco-datos 1 1,00 USD
mercadofresco-catalogo-fotos con clave de bucket ~2.000.000 peticiones → ~700 llamadas 0,00 USD (capa gratuita)
mercadofresco-copias-basedatos sin clave de bucket 60 copias × 2 (subir + verificar) = 120 0,00 USD
EBS y RDS (concesiones y arranques) ~500 0,00 USD
Total 1,00 USD/mes

Un dólar al mes por cifrar todos los datos personales de la empresa, con auditoría de cada uso. Es, con diferencia, la mejor relación coste-beneficio de todo el curso.

El contraste es útil: sin la clave de bucket, esos 2 millones de peticiones habrían generado 2 millones de llamadas a KMS, unos 6 USD al mes. No es dinero, pero a escala de 100 millones de peticiones serían 300 USD mensuales por no haber marcado una casilla.

ACM no es KMS

Confusión frecuente, porque ambos hablan de claves:

AWS KMS AWS Certificate Manager
Protege Datos en reposo Datos en tránsito
Objeto Claves de cifrado simétricas Certificados X.509
Coste 1 USD/clave/mes Gratis para certificados públicos
En MercadoFresco alias/mercadofresco-datos Certificados del ALB y de CloudFront (03-03, 03-05)
Renovación Rotación anual del material Renovación automática vía DNS

Son servicios independientes que resuelven mitades distintas del mismo problema. El certificado de mercadofresco.example que validamos con un CNAME en 03-05 no tiene nada que ver con alias/mercadofresco-datos.

Eliminar una clave: los 7 a 30 días de espera

No se puede borrar una clave de KMS de forma inmediata, y es deliberado: borrarla destruiría de forma irreversible todos los datos cifrados con ella. Solo se puede programar la eliminación con un periodo de espera de entre 7 y 30 días.

# Antes de nada: ¿la está usando alguien? Mírate CloudTrail (05-03)
aws kms schedule-key-deletion \
  --key-id alias/mercadofresco-datos --pending-window-in-days 30 \
  --profile mercadofresco-dev

# Cancelar mientras esté pendiente
aws kms cancel-key-deletion --key-id 1234abcd-12ab-34cd-56ef-1234567890ab \
  --profile mercadofresco-dev

Una alternativa mucho más sensata para probar el impacto es deshabilitar la clave: es reversible al instante y produce exactamente el mismo efecto que borrarla —todo lo cifrado deja de poder leerse—, pero sin destruir nada.

aws kms disable-key --key-id alias/mercadofresco-datos --profile mercadofresco-dev
aws kms enable-key  --key-id alias/mercadofresco-datos --profile mercadofresco-dev

Deshabilitar durante 30 días, comprobar que nada se rompe y solo entonces programar la eliminación: ese es el procedimiento correcto. Y mientras la clave esté deshabilitada se sigue pagando su dólar mensual.

Errores Comunes y Consejos

Escribir una política de clave que deja a todo el mundo fuera. Es el error irreversible de KMS. Si creas la clave por CLI con una política que no incluye "Principal": {"AWS": "arn:aws:iam::111122223333:root"} con kms:*, la clave queda inutilizable y solo se puede programar su eliminación. Incluye siempre esa declaración.

Creer que el cifrado por defecto de un bucket cifra lo que ya había. No lo hace. Los objetos existentes siguen sin cifrar hasta que los copies sobre sí mismos, y con versionado activo hay que purgar además las versiones antiguas.

Olvidar kms:Decrypt en el rol. El síntoma es confuso: s3:GetObject está concedido, el objeto existe y aun así llega AccessDenied. Con SSE-KMS hacen falta dos permisos. La primera comprobación ante un AccessDenied en un bucket cifrado es siempre KMS.

No activar la clave de bucket en buckets de mucho tráfico. Multiplica el coste de KMS por el número de peticiones. Actívala salvo que necesites la traza por objeto.

Usar Encrypt para ficheros grandes. Falla con ValidationException a partir de 4 KB. Para eso está GenerateDataKey y el sobre.

Confundir deshabilitar con eliminar. Deshabilitar es reversible y sigue costando 1 USD al mes; eliminar es irreversible y destruye los datos. Empieza siempre deshabilitando.

Retirar una concesión creada por RDS o EBS. La base de datos deja de arrancar. Las concesiones que crean los servicios no se tocan.

Consejo: usa alias, nunca UUID, en el código. alias/mercadofresco-datos es legible y permite cambiar la clave subyacente sin desplegar. En las políticas, en cambio, usa el ARN de clave: es inequívoco y no depende de a dónde apunte el alias.

Consejo: aplica separación de funciones desde el primer día. Que quien administra la clave no pueda descifrar los datos, como en la política de alias/mercadofresco-datos. Cuesta dos líneas y es lo primero que pregunta un auditor.

Consejo: activa el cifrado por defecto de EBS en la región. Un comando, gratis, y a partir de ahí nadie puede crear un volumen sin cifrar por descuido.

Consejo: pon una alarma sobre las llamadas a kms:Decrypt. Un pico anómalo de descifrados es una de las señales más tempranas de una exfiltración de datos. Lo montaremos con CloudWatch en 05-01 sobre los eventos de CloudTrail de 05-03.

Ejercicios

Ejercicio 1: diseñar la política de una clave nueva

MercadoFresco crea una segunda clave, alias/mercadofresco-analitica, para cifrar mercadofresco-informes-analitica. Requisitos:

  • Marta administra la clave pero no puede descifrar.
  • Sara (usuaria sara) puede descifrar informes, pero solo a través de S3 y solo si el contexto de cifrado incluye componente=informes.
  • Un rol rol-mercadofresco-etl puede cifrar y descifrar sin restricción de contexto, también solo a través de S3.
  • Nadie puede usar la clave fuera de eu-west-1.
  • Las políticas de IAM de la cuenta deben poder conceder acceso.

Escribe la política de clave completa.

Ejercicio 2: calcular el coste y decidir sobre la clave de bucket

MercadoFresco abre en Portugal y el catálogo pasa a servir 40 millones de peticiones al mes, de las cuales CloudFront absorbe el 90 % (solo el 10 % llega a S3). Además, el bucket mercadofresco-copias-basedatos recibe 3 copias diarias y mercadofresco-informes-analitica genera 50.000 objetos al mes.

Calcula el coste mensual de KMS en dos escenarios —con y sin clave de bucket en el catálogo— usando 2 claves gestionadas por el cliente, y decide qué configuración recomendarías para cada bucket justificando el compromiso entre coste y trazabilidad.

Ejercicio 3: diagnosticar un cifrado que falla

Luis despliega una versión nueva de la tienda. Las fotos se leen bien, pero al generar una miniatura la función mercadofresco-generar-miniaturas falla con:

An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:sts::111122223333:assumed-role/rol-lambda-miniaturas/mercadofresco-generar-miniaturas
is not authorized to perform: kms:GenerateDataKey on resource:
arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab
because no identity-based policy allows the kms:GenerateDataKey action

Responde: (a) ¿por qué falla al escribir si al leer funcionaba?; (b) ¿en qué dos sitios distintos hay que mirar y en cuál está el problema según el mensaje?; (c) escribe la corrección mínima; (d) si el error hubiera dicho with an explicit deny in a resource-based policy, ¿qué sospecharías, sabiendo que la política de clave incluye kms:ViaService?

Soluciones

Solución 1

{
  "Version": "2012-10-17",
  "Id": "politica-mercadofresco-analitica",
  "Statement": [
    {
      "Sid": "PermitirQueIAMGobierneLaClave",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "kms:*",
      "Resource": "*"
    },
    {
      "Sid": "MartaAdministra",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:user/marta" },
      "Action": [
        "kms:Describe*", "kms:Get*", "kms:List*", "kms:Enable*", "kms:Disable*",
        "kms:Update*", "kms:Put*", "kms:Revoke*", "kms:TagResource", "kms:UntagResource",
        "kms:ScheduleKeyDeletion", "kms:CancelKeyDeletion"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SaraDescifraSoloInformes",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:user/sara" },
      "Action": ["kms:Decrypt", "kms:DescribeKey"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "s3.eu-west-1.amazonaws.com",
          "kms:EncryptionContext:componente": "informes"
        }
      }
    },
    {
      "Sid": "ElProcesoETLCifraYDescifra",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-etl" },
      "Action": ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey*", "kms:DescribeKey"],
      "Resource": "*",
      "Condition": {
        "StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
      }
    },
    {
      "Sid": "ProhibirFueraDeEuWest1",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "kms:*",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": { "aws:RequestedRegion": "eu-west-1" }
      }
    }
  ]
}

Puntos clave: en la declaración de Marta no aparecen kms:Encrypt, kms:Decrypt ni kms:GenerateDataKey —separación de funciones—; Sara solo tiene Decrypt, nunca Encrypt, y con doble condición; y el Deny final se aplica a "Principal": "*", es decir, también a Marta y al root.

Solución 2

Peticiones a KMS por bucket:

  • Catálogo: 40.000.000 × 10 % = 4.000.000 peticiones a S3 al mes.
    • Sin clave de bucket: 4.000.000 llamadas a KMS.
    • Con clave de bucket: aproximadamente una cada pocas horas, del orden de 250 al mes.
  • Copias: 3 diarias × 30 = 90 objetos → ~180 llamadas (escritura + verificación).
  • Informes: 50.000 objetos → 50.000 llamadas sin clave de bucket, ~250 con ella.

Escenario A, sin clave de bucket en ningún sitio:

  • Peticiones: 4.000.000 + 180 + 50.000 = 4.050.180. Menos 20.000 de capa gratuita = 4.030.180.
  • Coste: 4.030.180 ÷ 10.000 × 0,03 = 12,09 USD
  • Claves: 2 × 1,00 = 2,00 USD
  • Total: 14,09 USD/mes

Escenario B, con clave de bucket en catálogo e informes, sin ella en copias:

  • Peticiones: 250 + 180 + 250 = 680, muy por debajo de la capa gratuita de 20.000.
  • Coste de peticiones: 0,00 USD
  • Claves: 2,00 USD
  • Total: 2,00 USD/mes

Recomendación. Ahorro de 12,09 USD al mes, unos 145 USD al año, y a mayor escala crece linealmente. Configuración propuesta:

Bucket Clave de bucket Justificación
mercadofresco-catalogo-fotos Millones de peticiones, fotos públicas, la traza por objeto no aporta nada
mercadofresco-informes-analitica 50.000 objetos al mes, datos agregados sin identificadores personales
mercadofresco-copias-basedatos No 90 objetos al mes: el ahorro es cero y la traza de quién descifró cada copia de datos personales es exactamente lo que pedirá una auditoría de RGPD

El razonamiento general: activa la clave de bucket salvo que el volumen sea bajo y la trazabilidad por objeto tenga valor de cumplimiento.

Solución 3

(a) Porque leer y escribir requieren permisos distintos de KMS. Descargar un objeto cifrado con SSE-KMS necesita kms:Decrypt; subir uno nuevo necesita kms:GenerateDataKey, porque S3 tiene que pedir una clave de datos nueva para cifrarlo. En 04-01 concedimos ambos a rol-lambda-miniaturas, así que aquí lo que ha ocurrido es que alguien ha desplegado una versión de la política con solo kms:Decrypt, o el bucket ha cambiado de clave.

(b) Hay que mirar en dos sitios: la política de identidad del rol y la política de clave de alias/mercadofresco-datos. El mensaje lo resuelve: because no identity-based policy allows señala inequívocamente a la política de identidad del rol, y además indica denegación implícita —falta un Allow—, no un Deny explícito.

(c) Añadir a rol-lambda-miniaturas la acción que falta:

{
  "Sid": "UsarLaClaveDelBucket",
  "Effect": "Allow",
  "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
  "Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
  "Condition": {
    "StringEquals": { "kms:ViaService": "s3.eu-west-1.amazonaws.com" }
  }
}

Lo mínimo es kms:GenerateDataKey; se incluye kms:Decrypt porque la función también lee el original. No hace falta kms:Encrypt: con el cifrado de sobre, S3 nunca la usa.

(d) Sospecharía de la condición kms:ViaService. Si la función hubiera pasado a llamar a KMS directamente —por ejemplo porque alguien cambió el código para cifrar antes de subir, en lugar de dejar que lo haga S3—, la petición ya no llegaría «a través de S3», la condición no se cumpliría y la política de clave la rechazaría. También podría ser el Deny de aws:RequestedRegion si el cliente de boto3 se hubiera creado apuntando a otra región. En ambos casos el arreglo no es ampliar permisos, sino corregir cómo se está llamando.

Conclusión

alias/mercadofresco-datos ha dejado de ser un nombre. Ahora es una clave gestionada por el cliente, con política propia, rotación anual activada, etiquetas de proyecto y una separación de funciones explícita: Marta la administra pero no puede descifrar; rol-mercadofresco-tienda y rol-lambda-miniaturas la usan pero solo a través de S3, gracias a kms:ViaService; RDS y EBS pueden crear concesiones porque son servicios de AWS; y nadie, ni el root, puede usarla fuera de eu-west-1.

Entiendes el mecanismo que lo sostiene todo, el cifrado de sobre: KMS genera una clave de datos y la devuelve en dos formatos —en claro y cifrada con la maestra—, la aplicación cifra localmente a velocidad de CPU, borra la versión en claro y guarda el CiphertextBlob junto a los datos. Por eso los datos grandes nunca viajan a KMS, por eso el límite de 4 KB de Encrypt no es un problema real, y por eso Decrypt no necesita que le digas qué clave usar. Sabes también qué hace y qué no hace la rotación automática: cambia el material para lo nuevo, conserva el antiguo para siempre y no recifra lo ya guardado.

Distingues SSE-S3, SSE-KMS y SSE-C, y sabes que la ventaja real de SSE-KMS es la doble autorizacións3:GetObject y kms:Decrypt—, que convierte una exposición accidental en un susto en lugar de una brecha. Sabes que las claves de bucket de S3 recortan hasta un 99 % las llamadas a KMS a cambio de perder la traza por objeto, y has decidido activarlas en mercadofresco-catalogo-fotos y no en mercadofresco-copias-basedatos, donde la traza vale más que el ahorro.

En lo práctico, MercadoFresco ha cifrado el bucket de copias con política de bucket que rechaza cualquier subida sin KMS, ha activado el cifrado por defecto de EBS en eu-west-1, sabe convertir un volumen sin cifrar mediante copy-snapshot --encrypted —la única operación que hace ese salto— y ha migrado mercadofresco-pedidos a una instancia cifrada por el camino de snapshot, copia cifrada y restauración, sin olvidar que la restauración no conserva el grupo de parámetros ni Multi-AZ y que el punto de enlace cambia. Todo ello por 1,00 USD al mes, la mejor relación coste-beneficio del curso. Y sabes que eliminar una clave es irreversible, que el camino sensato es deshabilitarla primero y que el periodo de espera de 7 a 30 días existe precisamente para salvarte.

Queda, sin embargo, el agujero más incómodo de todos, y es de los que no se arreglan con criptografía. La contraseña del usuario mfadmin de mercadofresco-pedidos sigue estando donde no debe: pasó por el user data de la plantilla lt-mercadofresco-tienda, vive en un fichero de configuración del servidor y hay una copia en el .env que Luis compartió por chat cuando montó el entorno de desarrollo. Da igual que la base de datos esté cifrada con AES-256: cualquiera que lea ese fichero entra con credenciales válidas, y el cifrado, como vimos en la primera tabla de esta lección, no protege de eso. En la lección 04-03, «Secrets Manager y Parameter Store», esa contraseña saldrá por fin de donde está: veremos la diferencia entre configuración y secreto, el Parameter Store para lo primero y el Secrets Manager para lo segundo, cómo se consume desde la aplicación sin volver a escribirla nunca en un fichero, y sobre todo la rotación automática cada 30 días con una Lambda gestionada, con su ciclo createSecret / setSecret / testSecret / finishSecret — que se apoya, precisamente, en la clave que acabas de construir.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados