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
- Por qué cifrar: el RGPD y los datos de MercadoFresco
- En reposo y en tránsito
- Criptografía simétrica y asimétrica, lo justo
- Qué es KMS y qué no es
- Tipos de clave
- Claves multirregión, material importado y CloudHSM
- El cifrado de sobre
- El descifrado, paso a paso
- Cifrado del lado del cliente y del lado del servidor
- SSE-S3, SSE-KMS y SSE-C
- Las claves de bucket de S3 y el coste de las peticiones
- La política de clave y su relación con IAM
kms:ViaServicey el contexto de cifrado- Concesiones (grants)
- Rotación automática y qué significa realmente
- Cifrar
mercadofresco-copias-basedatos - Cifrar volúmenes de EBS y snapshots
- Cifrar
mercadofresco-pedidos, que ya existe sin cifrar - Compartir un snapshot cifrado con otra cuenta
- KMS desde boto3
- Límites, cuotas y coste real para MercadoFresco
- ACM no es KMS
- 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 | Sí, totalmente |
| Un snapshot o una copia se comparte por error con otra cuenta | Sí, si no tiene la clave |
| Un bucket queda expuesto públicamente | Sí con SSE-KMS: además del permiso de S3 hace falta el de KMS |
| Se cumple el requisito normativo de una auditoría | Sí, 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:
mercadofresco-copias-basedatos: copias completas de la base de datospedidos, con nombres, direcciones y teléfonos de todos los clientes. Es el activo más peligroso de la cuenta.- La instancia
mercadofresco-pedidosy sus snapshots automáticos. - 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) | Tú | 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:
- La aplicación pide a KMS una clave de datos. No envía los datos.
- 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). - La aplicación cifra los 200 GB localmente con la versión en claro. Va a velocidad de CPU, sin red de por medio.
- La aplicación borra de memoria la versión en claro. Este paso es el que hace que el sistema sea seguro.
- Guarda juntos los datos cifrados y el
CiphertextBlob. El blob es inútil para quien no pueda llamar akms:Decryptsobre 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 | Sí | 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:
- 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. - 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 aparecekms:Decryptnikms: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 akms:ViaService. Si alguien roba las credenciales de la instancia y llama akms:Decryptdirectamente 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ónkms:GrantIsForAWSResourcelimita 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.
Sirve para dos cosas:
- Auditoría legible. En CloudTrail ves «se descifró algo del componente copias» en lugar de un UUID opaco.
- 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-devRotació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-devY 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-devDespué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-devEn 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-devY 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-devCon 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-devPara 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, arrancarEl 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-devCuatro 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 apedidos.interno.mercadofresco.example, basta con cambiar un CNAME. - La réplica
mercadofresco-pedidos-lecturano 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_keydevuelve 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. decryptno recibeKeyId: el blob lo lleva dentro.- El
EncryptionContextdebe coincidir carácter a carácter al descifrar. Si no,InvalidCiphertext. - En producción no se usa
Ferneta 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 sí 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-devUna 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-devDeshabilitar 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 incluyecomponente=informes. - Un rol
rol-mercadofresco-etlpuede 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 actionResponde: (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 |
Sí | Millones de peticiones, fotos públicas, la traza por objeto no aporta nada |
mercadofresco-informes-analitica |
Sí | 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ón —s3: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
- ¿Qué es AWS?
- Configuración de tu cuenta de AWS
- Infraestructura global de AWS
- Consola de administración de AWS
- AWS CLI y SDKs
Módulo 2: Servicios principales de AWS
Módulo 3: Redes y entrega de contenido
- Amazon VPC
- Grupos de seguridad y listas de control de acceso
- Elastic Load Balancing
- Amazon CloudFront
- Route 53
Módulo 4: Seguridad e identidad
- AWS Identity and Access Management (IAM)
- AWS Key Management Service (KMS)
- Secrets Manager y Parameter Store
- AWS Shield
- AWS WAF
Módulo 5: Monitorización y gestión
- Amazon CloudWatch
- AWS X-Ray y trazabilidad distribuida
- AWS CloudTrail
- AWS Config
- AWS Trusted Advisor
Módulo 6: Bases de datos
- Cómo elegir la base de datos adecuada
- Amazon DynamoDB
- Amazon Aurora
- Amazon Redshift
- Amazon ElastiCache
Módulo 7: Integración de aplicaciones
- Amazon SQS
- Amazon SNS
- Amazon EventBridge
- AWS Step Functions
- Patrones de integración: idempotencia, reintentos y colas de mensajes fallidos
