第3章 桶策略的JSON语法结构
桶策略说白了就是一个JSON文件。你把它挂到存储桶上,云服务就按这个规则来判定谁可以做什么。我刚开始接触的时候觉得这东西不就是一段JSON嘛,写几条规则就行了……结果第一次上线就出问题了,一条错误的条件直接让整个团队的构建流程炸了。后来我花了整整一下午查文档,才把语法搞明白。
嗯,这一章我就带你彻底捋一遍桶策略的JSON骨架。你看懂了这块,后面写策略的时候心里就有底了。
3.1 桶策略的整体框架
每个桶策略JSON只有三个顶层字段:
- Id(可选)——策略的唯一标识,建议写上,方便审计时追溯
- Version(必选)——策略语法版本,当前固定用
"2012-10-17" - Statement(必选)——规则数组,至少一条,每条就是一个独立的权限声明
核心要点: Statement 是一个数组。哪怕你只写一条规则,也要用方括号包起来。我见过有人图省事直接写对象,结果策略被当成了非法JSON,整个桶变公开了——差点出大事。
3.2 Statement 的六要素
每条 Statement 里最多可以出现6个字段,我按常用顺序给你排:
| 字段 | 必填 | 说明 |
|---|---|---|
| Sid | 可选 | 规则的别名,用来做日志关联。我习惯每条都写上,比如“AllowGetObjectFromDevIP” |
| Effect | 必选 | 就两个值:Allow 或 Deny。注意 Deny 优先级高于一切 Allow |
| Principal | 必选 | 谁可以被影响。可以是 AWS 账号、IAM 用户、匿名用户("*")或者联合身份 |
| Action | 必选 | 允许或拒绝的操作列表,比如 s3:GetObject |
| Resource | 必选 | 操作针对的具体资源,通常是桶 ARN 或对象 ARN |
| Condition | 可选 | 附加条件,比如来源IP、时间范围、是否使用HTTPS等 |
为什么我把 Condition 放最后?因为这是最容易出坑的地方。我个人建议,除非你真的明白条件的工作原理,否则不要轻易加——我曾因为写错条件键名,导致所有人都不能下载文件,排查了半天才发现多打了个冒号。
3.3 完整的结构示例
下面是一段真实项目中我常用的模板。你注意看缩进和引号:
{
"Id": "BucketPolicy-2024",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGetForDevTeam",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "10.0.0.0/8"
}
}
}
]
}
💡 小技巧: 如果你写多条规则,注意 Effect: Deny 要放在 Effect: Allow 之前。因为云服务逻辑是先扫描所有 Deny,只要有一条命中就直接拒绝,不会再看后面的 Allow。反过来如果你把 Allow 写前面,Deny 写后面,效果一样(Deny 权重最高),但可读性差。我团队内部约定:Deny 统一放第一个 Statement。
3.4 Principal 的三种常见写法
- 匿名用户:
"Principal": "*"或"Principal": { "AWS": "*" }。注意:"*"不带 AWS 键时,表示所有身份(包括未认证用户)。我曾经把它用在公开读取的桶上,结果被爬虫扫了一遍,流量费暴增。后来我加了 Referer 条件才挡住。 - 指定账号:
"Principal": { "AWS": "arn:aws:iam::123456789012:root" }。可以给多个账号,写成数组。 - 服务主体:比如给 CloudFront 写
"Principal": { "Service": "cloudfront.amazonaws.com" }。这个在 CDN 回源场景很常见。
3.5 Action 和 Resource 的配对关系
Action 必须和 Resource 对应。比如 s3:GetObject 只能作用在对象级别(arn:aws:s3:::bucket/*),不能作用在桶级别。反过来 s3:ListBucket 只能作用在桶级别(arn:aws:s3:::bucket)。你如果写错了 Resource 范围,策略会直接保存失败。
⚠️ 常见坑: 很多人写 Resource 时只写一条 "arn:aws:s3:::bucket/*",结果 ListBucket 操作被隐式拒绝了。因为 ListBucket 需要的 resource 是 "arn:aws:s3:::bucket"(不带 /*)。我建议你在 Resource 里同时写两条:一条带 /*,一条不带。这样不管是读对象还是列举桶,都能覆盖到。
3.6 Condition 的核心逻辑
Condition 本质是布尔表达式:只有条件为真时,这条规则才生效。常用的条件键包括:
- IpAddress / NotIpAddress:限制来源 IP
- StringEquals / StringNotEquals:比如限制请求必须来自某个 Referer
- Bool:比如
aws:SecureTransport是否为 true(强制 HTTPS) - NumericLessThanEquals:限对象大小
记得这点: Condition 可以写在 Allow 语句里,也可以写在 Deny 语句里。如果你想让某个用户只能在特定 IP 段内操作,就在 Allow 里加 IpAddress 条件。如果你想让所有人强制使用 HTTPS,就在 Deny 里写 aws:SecureTransport 等于 false 时拒绝。我更喜欢后一种方式——它像一道防火墙:先拒绝所有不安全请求,再给正常用户开绿灯。
3.7 为什么说 JSON 结构是策略的“骨架”?
你看看下面这张图就明白了。整个桶策略从根节点往下分叉,每一条 Statement 都是独立的分支。理解这个树状结构,你才能写出没有逻辑漏洞的策略。
看清这个结构了吗?核心就是 Statement 数组里的每一条规则都包含那几个字段。你写策略时,脑子里要先画这个树,再填参数,基本上就不会漏字段了。
💡 个人习惯: 我每次写好桶策略,都会用 AWS Policy Simulator 模拟一遍。虽然那是后话了,但你先记住——别直接往生产桶上怼。我吃过一次亏,写了一条有漏洞的 Condition,结果内网的人反而比外网权限小。模拟器能帮你把所有条件组合跑一遍,省得到时候半夜被报警电话叫醒。
3.8 我再强调几个容易忽略的细节
- 引号必须是英文双引号,不能用单引号。我在帮同事修bug时发现他把整个策略写成了单引号,s3直接报MalformedPolicy,完全看不出来。
- 数组内最后一个元素不要加逗号。虽然部分编辑器容错,但s3严格解析。我曾经因为多打了一个逗号,策略保存失败,CI/CD卡了半小时。
- Resource 中的 ARN 区分大小写。桶名一般全小写,但如果你写错了大小写,策略会作用于不存在的资源,等于白写。
⚠️ 重要提醒: 如果你给匿名用户("Principal": "*")配了写操作(比如 s3:PutObject),相当于你的桶变成了公共上传区。我曾经在测试环境忘了一条这样的策略,结果被机器人上传了几百个加密文件,存储费翻了三倍。所以强烈建议:凡是涉及匿名用户的策略,一定要用 Condition 把来源IP、HTTPS 这些硬条件卡死。
好,这一章就到这里。你只要把那棵 JSON 结构树刻在脑子里,桶策略就没什么神秘的了。接下来写策略的时候,每写一行都问自己:这条 Statement 的 Effect 是什么?Principal 是谁?Action 和 Resource 配对了没有?Condition 加得对不对?问完这四步,基本不会出错。