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

⏱ 75-90 minDificultad: mediaTask statements: 1.31.2

Qué vas a construir

Una customer managed key de AWS KMS con alias y rotación automática, y con ella vas a:

  1. Cifrar y descifrar un dato pequeño directamente con KMS (y comprobar el papel del encryption context).
  2. Hacer envelope encryption a mano: pedir una clave de datos, cifrar un fichero localmente con OpenSSL y guardar solo la clave cifrada.
  3. 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.
  4. 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

  1. 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.

  1. En la consola de KMS abre la clave: pestañas Key policy, Key rotation (90 días) y Aliases.
  2. En la consola de Secrets Manager comprueba que el secreto usa alias/lab13 como 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

  1. 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.

  1. 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.

  1. ¿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).

  1. 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.

  1. 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.


Volver al módulo