第4章:S3存储桶SSE-KMS配置
好,这一章我们聊聊S3和KMS的三件套。说白了就是:你上传文件时,怎么保证数据一落地就被KMS加密?有3种玩法,我按自己的习惯分成三层。
- 第一层:存储桶默认加密——自动化兜底
- 第二层:PutObject时显式指定KMS——控制力最强
- 第三层:Bucket Policy强制加密——策略铁门
这三层可以叠加使用,互不冲突。我个人建议:至少开前两层,第三层看安全等级。先看一张关系图,把这三步串起来。
4.1 默认加密设置
先讲最简单的:存储桶默认加密。你在S3控制台打开存储桶属性,找到"Default encryption",选SSE-KMS,指定一个KMS密钥ID(或别名)。
这层的作用是:任何没有显式指定加密头部的PutObject请求,都会被自动套用KMS加密。说白了就是兜底,不怕漏掉。
我的一个小习惯:新建存储桶时,第一件事就是开默认加密。别指望每个工程师都记得加头部。我见过生产环境里,就因为开发忘了指定,对象以明文形式存了一整天。
用AWS CLI配置默认加密的命令长这样:
aws s3api put-bucket-encryption \
--bucket my-secure-bucket \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "1234abcd-12ab-34cd-56ef-1234567890ab"
}
}
]
}'
注意:KMSMasterKeyID 可以填密钥ID、别名(alias/xxx)、或ARN。我一般用别名,方便轮换。
关键点:默认加密只影响未来上传的对象。已存在的对象不会自动重新加密。需要手动拷贝或使用S3 Batch Operations。
4.2 PutObject时指定KMS
第二层比较灵活。你可以在每个PutObject请求的头部里明确指定加密方式。举个例子,如果你要针对某个重要文件单独用密钥A,而其他文件用密钥B,就可以靠这个实现。
对应的请求头部是:
x-amz-server-side-encryption: aws:kmsx-amz-server-side-encryption-aws-kms-key-id: 密钥ID/别名/ARN(可选,不指定则用默认KMS key)
CLI的话这样写:
aws s3api put-object \
--bucket my-secure-bucket \
--key confidential.pdf \
--body /tmp/confidential.pdf \
--server-side-encryption aws:kms \
--ssekms-key-id alias/my-app-key
嗯,这里要注意:如果你指定了 ssekms-key-id,但IAM角色没有 kms:GenerateDataKey 权限,请求会报403。我踩过这个坑——当时以为只要密钥一致就行,结果忘了加KMS权限。
重要:SDK自动加签时,如果同时使用了默认加密和显式指定,显式指定的优先级更高。但如果显式指定的加密类型与默认加密不一样(比如默认是KMS,你写了AES256),那么默认加密会被跳过,使用请求指定的方式。这往往是安全审计发现漏洞的地方。
4.3 Bucket Policy强制加密
最后这层,才是真正的铁门。Bucket Policy里写一条Deny规则,条件键是 s3:x-amz-server-side-encryption,要求必须等于 aws:kms 或指定的某个KMS密钥。
为什么需要这个?你想想,用户就算上传时忘了加头部,默认加密会兜底。但如果用户恶意绕过了默认加密呢?比如他上传时指定了 aws:kms 但使用了不被允许的外部密钥?
一条典型的强制策略:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyIncorrectEncryption",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-secure-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
},
{
"Sid": "DenyUnencryptedObjectUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-secure-bucket/*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "true"
}
}
}
]
}
解释一下两个Statement:
- 如果
x-amz-server-side-encryption的值不是aws:kms,Deny。 - 如果请求头部完全没带这个字段(Null条件为true),也Deny。
这样一来,所有PutObject请求必须显式携带 aws:kms 头部。默认加密只是辅助,最终强制权在Policy手里。
我曾经遇到过的事:某次审计发现一个存储桶有几千个对象使用了SSE-S3而不是SSE-KMS。原因就是没有Policy强制,开发团队直接传了对象,默认加密虽然开着,但被请求头覆盖了。后来加了上面的Deny策略,再也没出过问题。
4.4 三者组合的最佳实践
这三种方式不是互斥的。我通常建议这样组合:
| 配置 | 推荐做法 | 说明 |
|---|---|---|
| 默认加密 | 开启SSE-KMS,使用自定义CMK | 避免明文漏洞,兜底策略 |
| PutObject指定 | 在脚本/SDK中显式添加头部 | 细粒度控制,密钥可区分 |
| Bucket Policy强制 | Deny未指定SSE-KMS的请求 | 安全底线,不可绕过 |
个人建议:如果你需要严格的合规要求(如PCI DSS),三种全开。如果只是常规数据保护,默认加密 + 政策强制就够了。
最后提一嘴:Policy中还可以更进一步,要求特定的KMS密钥ID,而不仅仅是 aws:kms。比如:
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:us-east-1:123456789012:key/abcd1234-..."
}
}
这样别人即使用了KMS,但不是你的CMK,也会被拒绝。我个人在金融场景下常用这种写法。
好,这一章就到这里。这三层配置吃透了,S3的KMS加密基本就稳了。