3. IAM 策略语言详解:JSON 结构、Effect、Action、Resource、Condition
策略(Policy)是 IAM 的“灵魂”。没有策略,用户和权限就是一盘散沙。说白了,策略就是用 JSON 写的一段规则,告诉 AWS“谁可以干什么”。这段 JSON 看着简单,我见过不少人在里面栽跟头。
咱们先看一个典型的策略长什么样:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::example-bucket"
}
]
}
这个策略允许某个主体列出 example-bucket 里的对象。结构很清晰:一个 Version、一个 Statement 数组。Statement 数组里可以放多个规则,每个规则有 Effect、Action、Resource,还可以带 Condition。
3.1 Effect:你是“允许”还是“拒绝”?
Effect 只有两个值:Allow 和 Deny。它决定了这条规则是“放行”还是“拦截”。
你可能会想:“都用 Allow 不就行了?为啥还要 Deny?”我做过一个项目,客户要求“除了财务部门,其他人都不能访问账单数据”。这听起来只要一个 Deny 就搞定了?不对。默认情况下所有请求都是隐式 Deny(Implicit Deny),你其实不用显式写 Deny。但为什么还要显式 Deny 呢?
因为显式 Deny 优先级最高。当一个主体同时被 Allow 和 Deny 命中时,Deny 说了算。这叫“Deny 优先”原则。我曾经在给一家金融客户做审计时,发现他们的 S3 存储桶策略写了一个 Allow + 一个 Deny,Deny 是用来拦着某个 IP 段的。结果因为 Deny 的 Action 写错了,该拦的没拦住,不该拦的反倒拦了。所以 Deny 不要乱用,但它确实是“最后一道防线”。
Effect 优先级总结:
- 显式 Deny > 显式 Allow > 隐式 Deny(默认)
- 只要有一条 Deny 匹配,请求就被拒绝
- 没有显式 Allow 等于隐式 Deny
3.2 Action:你到底能干点啥?
Action 是一个字符串数组,定义了允许或拒绝的操作。每个服务都有自己的一套 Action,比如 S3 的 s3:ListBucket、s3:GetObject,EC2 的 ec2:StartInstances。Action 支持通配符 *,比如 s3:* 表示所有 S3 操作。
我个人习惯尽量写精确的 Action。为什么?一是安全,二是可审计。我在一个电商平台的权限梳理中遇到过这样的情况:有人把 Action 写成 s3:*,结果一个本该只读的用户不小心删了对象。虽然版本控制能救回来,但那一刻还是吓出一身汗。
Action 的写法遵循“服务前缀:操作名称”的格式:
| 服务 | 示例 Action | 说明 |
|---|---|---|
| Amazon S3 | s3:GetObject |
读取对象 |
| Amazon EC2 | ec2:RunInstances |
启动实例 |
| AWS KMS | kms:Decrypt |
解密数据 |
| Amazon SQS | sqs:SendMessage |
发送消息 |
注意:当你用通配符 * 时,会匹配所有操作。这在开发环境图方便还行,生产环境我建议逐项列出。
3.3 Resource:你的权限在哪生效?
Resource 定义了 Action 能作用在哪些资源上。没写 Resource,策略就相当于废纸一张。Resource 的格式是 ARN(Amazon Resource Name)。
ARN 的通用结构是:
arn:partition:service:region:account-id:resource-type/resource-id
举个例子:
arn:aws:s3:::example-bucket/*:桶内所有对象arn:aws:ec2:us-east-1:123456789012:instance/*:某个账号下所有 EC2 实例arn:aws:kms:us-west-2:123456789012:key/mrk-1234abcd:某个具体的 KMS 密钥
Resource 也可以使用通配符 *,但我不建议你动不动就用 *。我见过一个内部工具,策略里 Resource 写的是 *,结果一个普通开发可以访问所有 KMS 密钥。你想想看,这要是哪个人误删了一个关键密钥……所以,Resource 要像写支票一样,精确到具体资源。
3.4 Condition:什么时候生效?
Condition 是策略的“条件守卫”。它让你能在 Effect、Action、Resource 的基础上加一层限制。比如“只允许在上班时间访问”、“只允许来自公司的 IP 访问”。
Condition 的语法长这样:
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.1.0/24"
}
}
关键元素有三个:
- 条件运算符:比如
IpAddress、StringEquals、DateGreaterThan - 条件键:比如
aws:SourceIp、aws:RequestedRegion - 条件值:你要匹配的值
常见的条件运算符我整理了一个表:
| 类别 | 运算符 | 使用场景 |
|---|---|---|
| 字符串 | StringEquals |
精确匹配,如标签键值对 |
| 字符串 | StringLike |
模糊匹配,支持通配符 |
| IP 地址 | IpAddress |
限制源 IP 范围 |
| 数值 | NumericLessThanEquals |
限制大小,比如对象大小 |
| 日期 | DateGreaterThan |
限制有效时间 |
| 布尔值 | Bool |
是否开启 MFA |
我的经验:Condition 常用的一个场景是“强制 MFA”。我建议你给高权限用户加上 aws:MultiFactorAuthPresent 的 Condition,这样就算泄露了密钥,没有 MFA 也干不了坏事。
3.5 策略评估逻辑:一张图让你彻底搞懂
上面讲了四个元素,但怎么组合起来做决策的?很多人搞混。我画了一张流程图,你看完就明白了。
核心逻辑:
- 先检查是否有显式 Deny 匹配,有就直接拒绝(最后一道防线)
- 再检查是否有显式 Allow 匹配,有就进入 Condition 判断
- Condition 通过就 Allow,没通过就拒绝
- 没有任何显式 Allow 匹配,就是隐式 Deny
3.6 实战中的常见坑
最后分享几个我踩过的坑:
- Action 大小写敏感:
s3:listbucket和s3:ListBucket是不同的,前者无效。我见过有人手写小写,结果策略有 Allow 但就是没权限。 - Resource 的
*和/*区别:S3 桶的 ARNarn:aws:s3:::bucket只代表桶本身,arn:aws:s3:::bucket/*代表桶内对象。如果你想操作桶和对象,需要两条 Resource。 - Condition 的
aws:SourceIp不适用于非 API 调用:如果用户是通过控制台操作的,SourceIp 不一定是你想要的值。控制台请求来自 AWS 的代理 IP。 - 跨账号访问:当使用角色跨账号时,Condition 里
aws:SourceArn或aws:SourceAccount可以限制信任方,防止被任意账号滥用。
我的习惯:每写完一条策略,我都用 IAM Policy Simulator 模拟一遍。这样能提前发现写错的 Action 或 Resource。别等上线后才发现“诶,怎么没权限?”
嗯,这一章的内容就这么多。JSON 结构很简单,但四个元素的组合方式决定了你的权限模型是否安全、可控。记住:Deny 是最后一道防线,Condition 是灵活聪明的守卫。