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 策略评估逻辑:一张图让你彻底搞懂

上面讲了四个元素,但怎么组合起来做决策的?很多人搞混。我画了一张流程图,你看完就明白了。

IAM 策略评估流程 收到 API 请求 匹配账号 & 主体 & 资源 检查显式 Deny 有 Deny → 拒绝 检查显式 Allow 评估 Condition 决定:Allow(显式) 没有显式 Allow → 隐式 Deny;有 Deny 则立即拒绝,不执行后续检查

核心逻辑:

  1. 先检查是否有显式 Deny 匹配,有就直接拒绝(最后一道防线)
  2. 再检查是否有显式 Allow 匹配,有就进入 Condition 判断
  3. Condition 通过就 Allow,没通过就拒绝
  4. 没有任何显式 Allow 匹配,就是隐式 Deny

3.6 实战中的常见坑

最后分享几个我踩过的坑:

  • Action 大小写敏感:s3:listbucket 和 s3:ListBucket 是不同的,前者无效。我见过有人手写小写,结果策略有 Allow 但就是没权限。
  • Resource 的 * 和 /* 区别:S3 桶的 ARN arn: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 是灵活聪明的守卫。