DEV Community

Cover image for SCS-Lab2 — Data Protection en S3 con KMS: cifrado, políticas y evidencia por CLI
Luis Eduardo Lunar Guevara
Luis Eduardo Lunar Guevara

Posted on

SCS-Lab2 — Data Protection en S3 con KMS: cifrado, políticas y evidencia por CLI

Región: us-east-1

Duración estimada: 35–55 minutos

Costo-riesgo: Bajo–Medio

Certificación: AWS Certified Security - Specialty (SCS-C03)

Dominio: Data Protection

Tarea 5.2: Design and implement controls for data at rest

Caso de uso

Camilo viene de una clase de AWS donde hablaron de Data Protection, pero se quedó con una duda que no se atrevió a preguntar al instructor:

“Ok, cifré… ¿cómo se ve eso en la realidad?”

En Digital Café Luna ya están guardando facturas y reportes en S3. La Sra. Blanca quiere que nadie lea esos documentos y que Camilo pueda demostrarle, con evidencia, que los archivos están cifrados y bajo control.

¿Qué vamos a construir?

Este laboratorio busca cerrar la brecha entre teoría y operación, vas a crear:

  • Un bucket S3 para documentos de Café Luna.
  • Una KMS key administrada por el cliente.
  • Cifrado por defecto en S3 usando SSE-KMS.
  • Una bucket policy que obligue HTTPS y uploads con la KMS key del lab.
  • Evidencia por CLI usando head-object, ServerSideEncryption y SSEKMSKeyId.
  • Cleanup completo.

Diagrama SCS-Lab2 — Data Protection en S3 con KMS)

Figura 1 — La Sra. Blanca quiere proteger facturas y reportes; Camilo configura S3 privado, KMS, políticas del bucket y valida la evidencia por CLI.

Nota de alcance

Este lab se enfoca en data at rest para objetos en S3 usando SSE-KMS. No vamos a cubrir IAM Rol, Secrets Manager, rotación de llaves ni acceso entre aplicaciones, eso lo dejamos para próximos labs.

Convención de nombres

Recurso Nombre
KMS Key Alias alias/scs-lab2-data-key
S3 Bucket scs-lab2-data-<tu-sufijo>
Prefix data data/
Prefix evidencia evidence/
Bucket policy file scs-lab2-bucket-guardrails.json

Nota práctica: el nombre del bucket debe ser único globalmente. Sugerencia puedes usar 4 números aleatorios como sufijo.

Requisitos previos

  • Cuenta AWS con permisos para crear recursos en S3 y KMS.
  • Región us-east-1.
  • AWS CLI configurado. Valida con aws sts get-caller-identity.
  • Recomendado: usar CloudShell para evitar problemas locales.

Ojo: KMS puede generar costos por key y por requests. En este lab lo mantenemos pequeño y cerramos con cleanup para evitar sustos.


Paso 1 — Preparar variables y crear la KMS key

Este lab lo haremos por CLI, es viable, limpio y nos ayuda a ganar habilidad en el CLI.

REGION="us-east-1"
LAB_ID="scs-lab2"
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"

KMS_ALIAS="alias/${LAB_ID}-data-key"
KMS_DESC="SCS Lab2 - Data Protection baseline with S3 and KMS"

echo "REGION=$REGION"
echo "ACCOUNT_ID=$ACCOUNT_ID"
echo "KMS_ALIAS=$KMS_ALIAS"
Enter fullscreen mode Exit fullscreen mode

Crear la KMS key:

KMS_KEY_ID="$(aws kms create-key \
  --region "$REGION" \
  --description "$KMS_DESC" \
  --key-usage ENCRYPT_DECRYPT \
  --origin AWS_KMS \
  --tags TagKey=Name,TagValue="${LAB_ID}-data-key" TagKey=Lab,TagValue="$LAB_ID" \
  --query 'KeyMetadata.KeyId' \
  --output text)"

echo "KMS_KEY_ID=$KMS_KEY_ID"
Enter fullscreen mode Exit fullscreen mode

Crear el alias:

aws kms create-alias \
  --region "$REGION" \
  --alias-name "$KMS_ALIAS" \
  --target-key-id "$KMS_KEY_ID"
Enter fullscreen mode Exit fullscreen mode

Obtener el ARN de la key:

KMS_KEY_ARN="$(aws kms describe-key \
  --region "$REGION" \
  --key-id "$KMS_KEY_ID" \
  --query 'KeyMetadata.Arn' \
  --output text)"

echo "KMS_KEY_ARN=$KMS_KEY_ARN"
Enter fullscreen mode Exit fullscreen mode

Validar:

aws kms describe-key \
  --region "$REGION" \
  --key-id "$KMS_KEY_ID" \
  --query 'KeyMetadata.{KeyId:KeyId,Arn:Arn,State:KeyState,Manager:KeyManager}' \
  --output table

aws kms list-aliases \
  --region "$REGION" \
  --query "Aliases[?AliasName=='$KMS_ALIAS'].{AliasName:AliasName,TargetKeyId:TargetKeyId}" \
  --output table
Enter fullscreen mode Exit fullscreen mode

Checkpoint

Valida que:

  • La key aparece en estado Enabled.
  • El alias alias/scs-lab2-data-key apunta a tu KMS_KEY_ID.
  • La key quedó taggeada con Lab=scs-lab2.

Evidencia

Evidencia

Evidencia

Evidencia


Paso 2 — Crear el bucket S3 y habilitar cifrado por defecto

Ahora crearemos un bucket privado y dejaremos cifrado por defecto con la KMS key del lab.

SUFFIX="$(printf "%04d" $((RANDOM%10000)))"
BUCKET="${LAB_ID}-data-camilo-${SUFFIX}"

echo "BUCKET=$BUCKET"
Enter fullscreen mode Exit fullscreen mode

Crear bucket:

aws s3api create-bucket \
  --region "$REGION" \
  --bucket "$BUCKET"
Enter fullscreen mode Exit fullscreen mode

Bloquear acceso público:

aws s3api put-public-access-block \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
Enter fullscreen mode Exit fullscreen mode

Agregar tags:

aws s3api put-bucket-tagging \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --tagging "TagSet=[{Key=Name,Value=${LAB_ID}-data-bucket},{Key=Lab,Value=$LAB_ID}]"
Enter fullscreen mode Exit fullscreen mode

Configurar cifrado por defecto con SSE-KMS:

aws s3api put-bucket-encryption \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --server-side-encryption-configuration "{
    \"Rules\": [
      {
        \"ApplyServerSideEncryptionByDefault\": {
          \"SSEAlgorithm\": \"aws:kms\",
          \"KMSMasterKeyID\": \"$KMS_KEY_ARN\"
        },
        \"BucketKeyEnabled\": true
      }
    ]
  }"
Enter fullscreen mode Exit fullscreen mode

Validar:

aws s3api head-bucket \
  --region "$REGION" \
  --bucket "$BUCKET"

aws s3api get-public-access-block \
  --region "$REGION" \
  --bucket "$BUCKET"

aws s3api get-bucket-encryption \
  --region "$REGION" \
  --bucket "$BUCKET"
Enter fullscreen mode Exit fullscreen mode

Checkpoint

Valida que:

  • El bucket existe.
  • Public Access Block está activo.
  • Default encryption está en aws:kms.
  • La KMS key corresponde a la key del lab.

Evidencia

Evidencia

Evidencia

Evidencia

Paso 3 — Subir un objeto cifrado con KMS y validarlo

Ahora vamos a subir una factura de prueba y comprobar con CLI cómo quedó guardada.

echo "Factura demo - $(date -u)" > factura-demo.txt
Enter fullscreen mode Exit fullscreen mode

Subir el objeto usando SSE-KMS con la key del lab:

aws s3 cp factura-demo.txt "s3://$BUCKET/data/factura-demo.txt" \
  --region "$REGION" \
  --sse aws:kms \
  --sse-kms-key-id "$KMS_KEY_ARN"
Enter fullscreen mode Exit fullscreen mode

Validar el objeto:

aws s3api head-object \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --key "data/factura-demo.txt" \
  --query "{SSE:ServerSideEncryption,KMSKey:SSEKMSKeyId}" \
  --output table
Enter fullscreen mode Exit fullscreen mode

Comparar alias contra la key real:

KEY_ID_FROM_ALIAS="$(aws kms list-aliases \
  --region "$REGION" \
  --query "Aliases[?AliasName=='$KMS_ALIAS'].TargetKeyId" \
  --output text)"

echo "Alias apunta a KeyId: $KEY_ID_FROM_ALIAS"
echo "Key creada en el lab: $KMS_KEY_ID"
Enter fullscreen mode Exit fullscreen mode

Checkpoint

Valida que:

  • head-object muestra SSE = aws:kms.
  • SSEKMSKeyId apunta a la KMS key del lab.
  • El alias apunta al mismo KMS_KEY_ID.

Evidencia


Paso 4 — Aplicar guardrails: HTTPS y KMS key correcta

Ya tenemos cifrado por defecto, pero ahora vamos a poner guardrails para reducir errores humanos:

  1. bloquear tráfico sin HTTPS;
  2. bloquear uploads sin SSE-KMS explícito;
  3. bloquear uploads con una KMS key diferente a la del lab.

Ojo: este guardrail exige intención explícita en el upload. Aunque el bucket tenga default encryption, vamos a pedir que se envíe los headers de SSE-KMS y la key correcta.

Crear la bucket policy:

cat > scs-lab2-bucket-guardrails.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureTransport",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::$BUCKET",
        "arn:aws:s3:::$BUCKET/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    },
    {
      "Sid": "DenyPutWithoutKMSEncryption",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::$BUCKET/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    },
    {
      "Sid": "DenyPutWithWrongKMSKey",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::$BUCKET/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption-aws-kms-key-id": "$KMS_KEY_ARN"
        }
      }
    }
  ]
}
EOF
Enter fullscreen mode Exit fullscreen mode

Aplicar la policy:

aws s3api put-bucket-policy \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --policy file://scs-lab2-bucket-guardrails.json
Enter fullscreen mode Exit fullscreen mode

Checkpoint

Valida que get-bucket-policy devuelve la policy sin error.

aws s3api get-bucket-policy \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --query Policy \
  --output text
Enter fullscreen mode Exit fullscreen mode

Evidencia


Paso 5 — Probar el guardrail

Aquí hacemos la prueba màs importante, demostrar que el bucket bloquea uploads inseguros y acepta uploads con aws:kms y la key correcta.

Intento incorrecto: debe fallar

echo "prueba sin SSE-KMS - $(date -u)" > sin-kms.txt

aws s3 cp sin-kms.txt "s3://$BUCKET/data/sin-kms.txt" \
  --region "$REGION"
Enter fullscreen mode Exit fullscreen mode

La salida esperada es un error AccessDenied.

Intento correcto: debe funcionar

aws s3 cp sin-kms.txt "s3://$BUCKET/data/con-kms.txt" \
  --region "$REGION" \
  --sse aws:kms \
  --sse-kms-key-id "$KMS_KEY_ARN"
Enter fullscreen mode Exit fullscreen mode

Evidencia final

aws s3api head-object \
  --region "$REGION" \
  --bucket "$BUCKET" \
  --key "data/con-kms.txt" \
  --query "{SSE:ServerSideEncryption,KMSKey:SSEKMSKeyId}" \
  --output table
Enter fullscreen mode Exit fullscreen mode

Checkpoint

Valida que:

  • El upload sin SSE-KMS falla.
  • El upload con --sse aws:kms funciona.
  • head-object muestra SSE = aws:kms.
  • SSEKMSKeyId apunta a la key del lab.

Evidencia

Evidencia

Evidencia

Clean up completo

Este lab deja S3 y KMS configurados. Para no dejar costos ni recursos colgados, eliminamos en este orden:

  1. objetos;
  2. bucket policy;
  3. bucket;
  4. alias;
  5. KMS key con borrado programado.

Vaciar bucket:

aws s3 rm "s3://$BUCKET" \
  --region "$REGION" \
  --recursive
Enter fullscreen mode Exit fullscreen mode

Quitar bucket policy:

aws s3api delete-bucket-policy \
  --region "$REGION" \
  --bucket "$BUCKET"
Enter fullscreen mode Exit fullscreen mode

Borrar bucket:

aws s3api delete-bucket \
  --region "$REGION" \
  --bucket "$BUCKET"
Enter fullscreen mode Exit fullscreen mode

Borrar alias:

aws kms delete-alias \
  --region "$REGION" \
  --alias-name "$KMS_ALIAS"
Enter fullscreen mode Exit fullscreen mode

Programar borrado de la KMS key:

aws kms schedule-key-deletion \
  --region "$REGION" \
  --key-id "$KMS_KEY_ID" \
  --pending-window-in-days 7
Enter fullscreen mode Exit fullscreen mode

Validar cleanup:

aws s3api head-bucket \
  --region "$REGION" \
  --bucket "$BUCKET" 2>&1 | tail -n 2
Enter fullscreen mode Exit fullscreen mode
aws kms list-aliases \
  --region "$REGION" \
  --query "Aliases[?AliasName=='$KMS_ALIAS']" \
  --output table
Enter fullscreen mode Exit fullscreen mode
aws kms describe-key \
  --region "$REGION" \
  --key-id "$KMS_KEY_ID" \
  --query "KeyMetadata.KeyState" \
  --output text
Enter fullscreen mode Exit fullscreen mode

Ojo: 404 Not Found en el bucket es esperado si ya fue eliminado. En KMS, la key debe quedar en PendingDeletion.

Evidencia


Troubleshooting

  • AccessDenied al subir a S3: revisa si falta --sse aws:kms o si estás usando una KMS key distinta a la del lab.
  • KMS alias no existe: valida con aws kms list-aliases y confirma alias/scs-lab2-data-key.
  • head-object no muestra aws:kms: el objeto pudo subirse sin SSE-KMS. Vuelve a subirlo forzando --sse aws:kms.
  • No puedes borrar el bucket: elimina primero objetos, luego bucket policy y vuelve a intentar.
  • No puedes borrar la key de inmediato: KMS requiere borrado programado, mínimo 7 días.

Well-Architected lens: ¿Qué aplicamos aquí?

  • Security: cifrado en reposo con SSE-KMS y guardrails explícitos para reducir errores humanos.
  • Operational Excellence: evidencia por CLI para auditar sin depender de “fe en la consola”.
  • Cost Optimization: lab corto y cleanup completo para controlar S3 y KMS.
  • Reliability: guardrails ayudan a evitar que datos entren mal configurados.

Resultado esperado

Al finalizar este laboratorio, Camilo puede demostrar con evidencia:

  • que un objeto en S3 quedó cifrado con aws:kms;
  • qué KMS key lo cifró usando SSEKMSKeyId;
  • que el bucket bloquea prácticas inseguras, como uploads sin SSE-KMS o con una key distinta.

La idea no era solo “activar cifrado”, sino entender cómo se ve, cómo se valida y cómo se controla.


Referencias oficiales


¿Qué viene en el próximo lab?

Camilo ya vio cómo funciona KMS, cómo se cifra un objeto, cómo se valida por CLI y cómo se aplican guardrails (básicos) en S3.

En el próximo lab subimos el nivel: trabajaremos con IAM Roles y políticas de acceso para que solo los componentes correctos puedan leer o escribir datos.

Nada de credenciales sueltas por ahí.

Nos vemos en el próximo laboratorio de Digital Café Luna.


Top comments (0)