使用 KMS 密钥保护 S3 对象

AWSBeginner
立即练习

介绍

应用需要读取存储在 S3 中的私有导出文件。你将使用客户密钥加密新上传的对象,为读取者授予访问权限,并通过实际下载测试对象权限与密钥权限的影响。

请先完成 在 S3 中存储和检索文件、使用 KMS 加密和解密私有导出文件,以及它们要求的 IAM 前置实验。本实验的全新 VM 提供模拟导出文件、读取者角色会话和独立的参考资源。

相关认证考点

本实验为以下认证考点提供动手练习。

准备加密的对象存储

本步骤中,你将创建客户 KMS 密钥,并配置一个新存储桶默认使用它。存储桶容纳对象;每个对象都有一个对象键,例如 exports/private-export.json。使用 KMS 的服务器端加密(SSE-KMS) 让 S3 使用你选择的密钥加密存储的对象。默认加密会应用于新上传的对象,无需每个客户端重复指定加密选项。

使用提供的模拟文件 private-export.json;export-reader 角色会话最初没有对象或密钥的访问权限。保留参考存储桶、对象和密钥。在 Terminal 旁打开 AWS View,对比密钥和存储桶状态、读取者授权,以及不暴露数据的字节比较结果。

先进入项目目录,确认提供的操作员身份。cd 用于切换目录;调用者查询会返回身份 ARN,而不会暴露凭证。

cd /home/labex/project
aws sts get-caller-identity --query Arn --output text

预期看到 labex-sec02-operator 用户。创建对称客户密钥和一个易读的别名。--query 选择 ARN,--output text 去除 JSON 引号,$(...) 将结果保存到 shell 变量中。

KEY_ARN=$(aws kms create-key \
  --description labex-sec02-owned-export \
  --query KeyMetadata.Arn \
  --output text)
aws kms create-alias \
  --alias-name alias/labex-sec02-private-export \
  --target-key-id "$KEY_ARN"

使用带时间戳后缀的临时存储桶名称,避免名称冲突。labex-sec02-owned- 前缀可以区分你的存储桶和提供的参考存储桶。本实验使用 us-east-1,因此创建存储桶时不需要位置约束。

BUCKET="labex-sec02-owned-$(date +%s)"
aws s3api create-bucket \
  --bucket "$BUCKET" \
  --region us-east-1 \
  --query Location \
  --output text

使用精确的密钥 ARN 配置 SSE-KMS。S3 Bucket Key 可以减少重复的 KMS 请求;这里保持关闭,让每个对象使用自己的 KMS 加密上下文。将默认规则写入普通 JSON 文件。这里的 here-document 会展开 $KEY_ARN;file:// 用于加载保存的配置。

cat > bucket-encryption.json <<EOF
{
  "Rules": [
    {
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "$KEY_ARN"
      },
      "BucketKeyEnabled": false
    }
  ]
}
EOF

aws s3api put-bucket-encryption --bucket "$BUCKET" \
  --server-side-encryption-configuration file://bucket-encryption.json
aws s3api get-bucket-encryption \
  --bucket "$BUCKET" \
  --query ServerSideEncryptionConfiguration

预期看到 aws:kms、你的密钥 ARN 和 BucketKeyEnabled: false。在此 Terminal 中保留 KEY_ARN 和 BUCKET,供后续命令使用。在 AWS View 中,你创建的密钥处于启用状态,存储桶中还没有对象。

为读取者授予两项必需权限

本步骤中,你将上传导出文件,并让角色恢复其原始字节。上传需要所选密钥的 kms:GenerateDataKey 权限。SSE-KMS 使用数据密钥加密对象字节。KMS 保护该数据密钥;S3 存储加密的对象和封装后的数据密钥,然后在授权读取者下载对象时请求解密。密钥材料无需出现在 CLI 输出中。

S3 对象权限与密钥权限

读取这个 SSE-KMS 对象,需要同时具备对象访问权限和密钥解密权限。

使用 s3api put-object 上传提供的文件。存储桶默认配置会提供加密设置;--body private-export.json 将指定的本地文件作为对象字节读取。对于这个小型导出文件,使用此单部分上传 API。

aws s3api put-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json \
  --body private-export.json \
  --query '{Encryption:ServerSideEncryption,Key:SSEKMSKeyId,BucketKey:BucketKeyEnabled}'
aws s3api head-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json \
  --query '{Encryption:ServerSideEncryption,Key:SSEKMSKeyId,Bytes:ContentLength}'

预期看到 aws:kms、你创建的密钥 ARN 和原始文件大小。仅凭元数据无法判断读取者能否解密。先只授予对象访问权限。对象 ARN 包含存储桶和对象键,与存储桶 ARN 不同。下面的 here-document 会写入策略文件,并展开其中的 $BUCKET。

cat > read-object.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::$BUCKET/exports/private-export.json"
    }
  ]
}
EOF
aws iam put-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name ReadExportObject \
  --policy-document file://read-object.json

使用 --profile export-reader 选择准备好的读取者会话。此次尝试必须以 AccessDenied 失败:角色可以读取这个对象,但尚未获得 KMS 解密授权。

aws \
  --profile export-reader s3api get-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json reader-export.json

只为该角色授予精确密钥的 kms:Decrypt 权限。下载不需要密钥管理权限,也不需要 kms:GenerateDataKey。

cat > decrypt-key.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "$KEY_ARN"
    }
  ]
}
EOF
aws iam put-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name DecryptExportKey \
  --policy-document file://decrypt-key.json
aws \
  --profile export-reader s3api get-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json reader-export.json \
  --query '{Encryption:ServerSideEncryption,Key:SSEKMSKeyId}'
chmod 600 reader-export.json
cmp private-export.json reader-export.json

当字节内容一致时,cmp 不会输出任何内容,并返回成功。AWS View 此时应显示存储的密文,以及读取者的 GetObject 返回字节与提供的导出文件一致。这是两个独立的观察结果:加密存储保护静态字节,而限定范围的权限控制数据获取。

授予精确密钥权限后的 AWS View 示例:存储的密文与读取者恢复的原始字节。

诊断权限与密钥状态导致的失败

本步骤中,你将区分缺少解密授权与密钥被禁用这两种情况。保留对象权限,让每次只改变一个条件。

移除读取者的密钥授权,然后重新下载同一个对象。角色仍然拥有 s3:GetObject,但请求必须以 AccessDenied 失败。

aws iam delete-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name DecryptExportKey
aws \
  --profile export-reader s3api get-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json reader-export.json

检查保留下来的对象策略。它应仍然只指定这个导出对象。

aws iam get-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name ReadExportObject \
  --query PolicyDocument

恢复精确密钥授权,并禁用你的密钥。被禁用的密钥仍然存在,但不能执行密码学操作。不要更改无关的参考密钥。

aws iam put-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name DecryptExportKey \
  --policy-document file://decrypt-key.json
aws kms disable-key --key-id "$KEY_ARN"
aws \
  --profile export-reader s3api get-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json reader-export.json

预期出现密钥被禁用导致的失败。拥有 IAM 权限也不能让被禁用的密钥变得可用。确认其原生状态,然后启用密钥并再次获取对象。

aws kms describe-key --key-id "$KEY_ARN" --query KeyMetadata.KeyState --output text
aws kms enable-key --key-id "$KEY_ARN"
aws \
  --profile export-reader s3api get-object \
  --bucket "$BUCKET" \
  --key exports/private-export.json reader-export.json \
  --query ServerSideEncryption \
  --output text
chmod 600 reader-export.json
cmp private-export.json reader-export.json

预期看到 aws:kms,且比较成功。AWS View 会同时保留失败请求与成功恢复的记录,而参考存储桶和密钥仍然可用。AWS View 示例:撤销权限和禁用密钥造成失败,随后成功读取。

下载失败后,旧的本地文件可能仍然存在,因此仅凭文件存在不能证明请求成功;经过签名的读取请求与比较结果才能证明恢复成功。

移除自己创建的资源并安排删除密钥

本步骤中,你将清理指定的导出资源。先完成前面的功能检查。S3 存储桶必须清空后才能删除;客户 KMS 密钥需要经过删除等待期,不会立即消失。

只删除你指定的导出对象,然后删除其存储桶。成功列出资源清单,确认你的 $BUCKET 已不存在,而 labex-sec02-reference 仍然保留。

aws s3api delete-object --bucket "$BUCKET" --key exports/private-export.json
aws s3api delete-bucket --bucket "$BUCKET"
aws s3api list-buckets --query 'Buckets[].Name'

移除你添加的两项权限和自己创建的别名。提供的读取者角色是会话配套资源;不要删除它或无关的参考资源。

aws iam delete-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name ReadExportObject
aws iam delete-role-policy \
  --role-name labex-sec02-export-reader \
  --policy-name DecryptExportKey
aws iam list-role-policies --role-name labex-sec02-export-reader --query PolicyNames
aws kms delete-alias --alias-name alias/labex-sec02-private-export

使用最短的七天等待期安排删除你的密钥,然后读取其原生状态。安排删除会立即使密钥不可用,但不能证明密钥已经被删除。

aws kms schedule-key-deletion \
  --key-id "$KEY_ARN" \
  --pending-window-in-days 7 \
  --query DeletionDate \
  --output text
aws kms describe-key --key-id "$KEY_ARN" --query KeyMetadata.KeyState --output text
aws kms describe-key \
  --key-id alias/labex-sec02-reference \
  --query KeyMetadata.KeyState \
  --output text

预期你的密钥为 PendingDeletion,参考密钥为 Enabled。移除指定的本地文件;这些命令不会影响无关的项目文件。

rm -f private-export.json reader-export.json read-object.json decrypt-key.json bucket-encryption.json

在移除临时 CLI 配置档之前,运行本步骤的验证。成功的原生资源清单查询才能证明清理完成,身份验证错误不能作为清理证据。

rm -f /home/labex/.aws/credentials /home/labex/.aws/config
unset KEY_ARN BUCKET

总结

你将 SSE-KMS 配置为存储桶默认加密方式,上传了加密导出文件,并通过权限范围限定为一个对象和一把密钥的角色恢复了原始字节。撤销 kms:Decrypt 和禁用密钥都会阻止读取,而无需更改对象授权。你恢复了访问,只删除自己创建的 S3 资源,并安排删除自己创建的 KMS 密钥,同时保留参考存储桶和密钥。