Lab práctico · Semana 7: Seguridad de datos: cifrado, secretos, identidad de aplicaciones y detección
KMS y Secrets Manager: cifrado de sobre, rotación y secretos de aplicación
Qué vas a construir
Una customer managed key de AWS KMS con alias y rotación automática, y con ella vas a:
- Cifrar y descifrar un dato pequeño directamente con KMS (y comprobar el papel del encryption context).
- Hacer envelope encryption a mano: pedir una clave de datos, cifrar un fichero localmente con OpenSSL y guardar solo la clave cifrada.
- Guardar una contraseña en Secrets Manager y en Parameter Store (
SecureString) cifradas con tu clave, y leerlas como lo haría una aplicación. - Ver en CloudTrail cada uso de la clave.
flowchart LR
CS["CloudShell (tú)"] -->|"Encrypt / Decrypt / GenerateDataKey"| KMS["KMS key alias/lab13"]
CS --> SM["Secrets Manager: lab13/bd"]
CS --> PS["Parameter Store: /lab13/bd/password"]
SM -->|"cifra con"| KMS
PS -->|"cifra con"| KMS
KMS -->|"cada uso"| CT["CloudTrail event history"]
Antes de empezar
- Entra con tu usuario de IAM Identity Center (o usuario IAM con MFA) con permisos de administrador del laboratorio; no uses el usuario root.
- Región: eu-south-2 (España). Abre AWS CloudShell desde la consola en esa región.
- Coste: una customer managed key cuesta 1 USD/mes prorrateado por horas (unos céntimos si la programas para borrado hoy; mientras está pendiente de borrado no se cobra). Secrets Manager cobra 0,40 USD por secreto y mes y 0,05 USD por 10.000 llamadas; borra el secreto al terminar. Las 20.000 peticiones mensuales de KMS son gratis. Precios consultados el 30/09/2026.
Prepara unas variables:
export AWS_REGION=eu-south-2
export CUENTA=$(aws sts get-caller-identity --query Account --output text)
mkdir -p ~/lab13 && cd ~/lab13
echo "Cuenta: $CUENTA Región: $AWS_REGION"
Paso 1: crear la customer managed key y su alias
Creas una clave simétrica de cifrado (el tipo por defecto). La key policy por defecto da permisos a la cuenta, lo que delega en IAM: tu rol de administrador podrá usarla gracias a su política IAM.
KEY_ID=$(aws kms create-key \
--description "Lab 13 - clave de pruebas" \
--tags TagKey=proyecto,TagValue=lab13 \
--query KeyMetadata.KeyId --output text)
echo "KEY_ID=$KEY_ID"
aws kms create-alias --alias-name alias/lab13 --target-key-id "$KEY_ID"
aws kms describe-key --key-id alias/lab13 \
--query 'KeyMetadata.[KeyId,KeyManager,KeySpec,KeyUsage,Origin,MultiRegion]' --output table
Fíjate en KeyManager = CUSTOMER, KeySpec = SYMMETRIC_DEFAULT, Origin = AWS_KMS y MultiRegion = False.
Mira la key policy por defecto:
aws kms get-key-policy --key-id "$KEY_ID" --policy-name default --output text
Verás una única declaración con el principal arn:aws:iam::CUENTA:root y kms:*: es la que habilita las políticas IAM de la cuenta.
Paso 2: activar la rotación automática
aws kms enable-key-rotation --key-id "$KEY_ID" --rotation-period-in-days 90
aws kms get-key-rotation-status --key-id "$KEY_ID"
El periodo admite de 90 a 2.560 días (365 por defecto). La rotación cambia el material, no el ID, el ARN ni el alias. Puedes ver las rotaciones hechas con aws kms list-key-rotations --key-id "$KEY_ID" (ahora saldrá vacía). No hagas una rotación bajo demanda: la primera y la segunda rotación suben el precio de la clave.
Paso 3: cifrar y descifrar un dato pequeño
Encrypt acepta como mucho 4.096 bytes. Vas a usar además un encryption context.
echo "IBAN de prueba ES00 0000 0000 0000 0000 0000" > dato.txt
aws kms encrypt --key-id alias/lab13 \
--plaintext fileb://dato.txt \
--encryption-context proyecto=lab13 \
--query CiphertextBlob --output text | base64 --decode > dato.enc
ls -l dato.txt dato.enc
Descifra. No hace falta indicar la clave: con claves simétricas, el identificador va dentro del texto cifrado.
aws kms decrypt --ciphertext-blob fileb://dato.enc \
--encryption-context proyecto=lab13 \
--query Plaintext --output text | base64 --decode
Ahora prueba con un contexto distinto:
aws kms decrypt --ciphertext-blob fileb://dato.enc \
--encryption-context proyecto=otro \
--query Plaintext --output text
Falla con InvalidCiphertextException: el contexto debe coincidir exactamente.
Paso 4: envelope encryption a mano
Imagina un fichero de varios MB. No se lo envías a KMS: pides una clave de datos y cifras en local.
# Un fichero "grande" de prueba (5 MB)
head -c 5000000 /dev/urandom > informe.bin
# 1) Pedir la clave de datos: KMS devuelve la versión en claro y la cifrada
aws kms generate-data-key --key-id alias/lab13 --key-spec AES_256 > datakey.json
# 2) Guardar la clave de datos CIFRADA (esto es lo que se almacena junto al fichero)
jq -r .CiphertextBlob datakey.json | base64 --decode > informe.key.enc
# 3) Cifrar localmente con la clave EN CLARO (AES-256-CBC con OpenSSL)
CLAVE_HEX=$(jq -r .Plaintext datakey.json | base64 --decode | od -An -tx1 | tr -d ' \n')
IV=$(openssl rand -hex 16)
echo "$IV" > informe.iv
openssl enc -aes-256-cbc -K "$CLAVE_HEX" -iv "$IV" -in informe.bin -out informe.bin.enc
# 4) Borrar la clave en claro de disco y de memoria
unset CLAVE_HEX
rm -f datakey.json
ls -l informe.*
Ahora solo tienes el fichero cifrado, el IV y la clave de datos cifrada. Para descifrar necesitas que KMS te devuelva la clave en claro, lo que exige permiso kms:Decrypt sobre alias/lab13 (y queda registrado):
CLAVE_HEX=$(aws kms decrypt --ciphertext-blob fileb://informe.key.enc \
--query Plaintext --output text | base64 --decode | od -An -tx1 | tr -d ' \n')
openssl enc -d -aes-256-cbc -K "$CLAVE_HEX" -iv "$(cat informe.iv)" \
-in informe.bin.enc -out informe.descifrado.bin
unset CLAVE_HEX
cmp informe.bin informe.descifrado.bin && echo "OK: el fichero descifrado es idéntico"
Esto es exactamente lo que hacen por dentro SSE-KMS en S3, EBS o el AWS Encryption SDK (con AES-GCM en lugar de CBC).
Paso 5: guardar un secreto en Secrets Manager
Genera una contraseña robusta con la propia API y crea el secreto cifrado con tu clave:
PASS=$(aws secretsmanager get-random-password --password-length 24 \
--exclude-punctuation --query RandomPassword --output text)
aws secretsmanager create-secret --name lab13/bd \
--description "Credenciales de prueba del lab 13" \
--kms-key-id alias/lab13 \
--secret-string "{\"usuario\":\"app_pedidos\",\"password\":\"$PASS\",\"host\":\"bd.ejemplo.internal\",\"puerto\":5432}"
Léelo como lo haría una aplicación (con su rol, en tiempo de ejecución):
aws secretsmanager get-secret-value --secret-id lab13/bd \
--query SecretString --output text | jq .
aws secretsmanager describe-secret --secret-id lab13/bd \
--query '{Nombre:Name,Clave:KmsKeyId,Rotacion:RotationEnabled,Versiones:VersionIdsToStages}'
Fíjate en la etiqueta de versión AWSCURRENT. Si activaras la rotación (con una base de datos real y la plantilla de Lambda de rotación), aparecería temporalmente AWSPENDING. En este lab no hay base de datos, así que no la actives.
Paso 6: lo mismo en Parameter Store
aws ssm put-parameter --name /lab13/bd/usuario --type String --value app_pedidos
aws ssm put-parameter --name /lab13/bd/password --type SecureString \
--key-id alias/lab13 --value "$PASS"
unset PASS
# Sin descifrar: ves el valor cifrado
aws ssm get-parameter --name /lab13/bd/password --query Parameter.Value --output text
# Descifrando (requiere kms:Decrypt sobre la clave)
aws ssm get-parameter --name /lab13/bd/password --with-decryption \
--query Parameter.Value --output text
# Lectura jerárquica de toda la configuración de la app
aws ssm get-parameters-by-path --path /lab13/bd --with-decryption \
--query 'Parameters[].[Name,Type,Tier]' --output table
Ambos parámetros son de nivel Standard (gratis, hasta 4 KB). Compara con el secreto: Parameter Store no ofrece rotación ni replicación entre regiones.
Comprueba que funciona
- Mira en CloudTrail los usos de la clave (pueden tardar unos minutos en aparecer en el event history):
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue="arn:aws:kms:$AWS_REGION:$CUENTA:key/$KEY_ID" \
--max-results 20 \
--query 'Events[].[EventTime,EventName,Username]' --output table
Deberías ver GenerateDataKey, Decrypt (tuyos y los que hicieron Secrets Manager y SSM en tu nombre) y las operaciones de gestión. Cada uso de una customer managed key queda auditado.
- En la consola de KMS abre la clave: pestañas Key policy, Key rotation (90 días) y Aliases.
- En la consola de Secrets Manager comprueba que el secreto usa
alias/lab13como clave de cifrado.
Limpieza
Hazlo en este orden (primero lo que depende de la clave):
# 1) Secreto: borrado inmediato sin ventana de recuperación (solo en labs)
aws secretsmanager delete-secret --secret-id lab13/bd --force-delete-without-recovery
# 2) Parámetros
aws ssm delete-parameters --names /lab13/bd/usuario /lab13/bd/password
# 3) Alias y programación del borrado de la clave con la espera mínima (7 días)
aws kms delete-alias --alias-name alias/lab13
aws kms schedule-key-deletion --key-id "$KEY_ID" --pending-window-in-days 7
aws kms describe-key --key-id "$KEY_ID" --query 'KeyMetadata.[KeyState,DeletionDate]' --output text
# 4) Ficheros locales
cd ~ && rm -rf ~/lab13
La clave queda en estado PendingDeletion durante 7 días, sin coste, y después desaparece. Si te equivocas, aws kms cancel-key-deletion --key-id "$KEY_ID" la recupera (quedará desactivada; actívala con enable-key).
Preguntas para pensar como arquitecto
- Tu aplicación sube 20.000 objetos por segundo a S3 con SSE-KMS en eu-south-2 y empiezan los
ThrottlingException. ¿Qué harías antes de pedir un aumento de cuota?
Respuesta
Activar S3 Bucket Keys en el bucket: S3 genera una clave de nivel de bucket y reduce enormemente las llamadas GenerateDataKey/Decrypt a KMS (y su coste). En eu-south-2 la cuota compartida por defecto es de 20.000 operaciones simétricas por segundo, y cuenta también lo que hacen otros servicios en tu nombre. Además, reintentos con backoff exponencial y jitter en el cliente; si aun así no basta, aumento de cuota en Service Quotas.
- Otra cuenta de la organización debe leer objetos de un bucket cifrado con tu clave. ¿Qué dos permisos hacen falta?
Respuesta
En tu cuenta, la key policy debe permitir kms:Decrypt (y lo necesario) al rol de la otra cuenta o a la cuenta entera; y la bucket policy, el acceso a los objetos. En la otra cuenta, la política IAM del rol debe conceder kms:Decrypt sobre el ARN de tu clave y s3:GetObject sobre el bucket. Con una clave AWS managed (aws/s3) no sería posible, porque su key policy no se puede modificar.
- ¿Por qué en el paso 4 guardas la clave de datos cifrada junto al fichero y no la KMS key?
Respuesta
Porque la KMS key nunca sale de KMS: no se puede exportar. Lo que viaja y se guarda es la clave de datos cifrada por ella. Para leer el fichero hay que pedir a KMS que descifre esa clave de datos, lo que exige permiso sobre la KMS key y queda auditado. Así una sola KMS key protege millones de ficheros y los datos grandes nunca pasan por KMS (que solo acepta 4 KB en Encrypt).
- El equipo de seguridad exige que las credenciales de la base de datos cambien cada 30 días sin intervención humana. ¿Secrets Manager o Parameter Store?
Respuesta
Secrets Manager: tiene rotación automática nativa para RDS, Aurora, Redshift y DocumentDB (o con Lambda para otros secretos), programable hasta cada 4 horas. Parameter Store no rota secretos; habría que construir la rotación con EventBridge y Lambda, lo que aumenta la operación.
- Programaste el borrado de una clave y un compañero avisa de que unos backups antiguos se cifraron con ella. ¿Qué haces?
Respuesta
Cancelar el borrado durante el periodo de espera (cancel-key-deletion) y volver a habilitar la clave. Si la clave se hubiera borrado, esos backups serían irrecuperables. Para detectarlo antes: alarma de CloudWatch sobre intentos de uso de claves pendientes de borrado y revisión en CloudTrail del uso histórico de la clave. Ante la duda, desactivar en lugar de borrar.