第3章 IAM策略语法:从结构到条件的一站式拆解
说实话,刚接触IAM策略那会儿,我也被那一堆JSON搞得头晕。Version、Statement、Effect……这都什么鬼?后来踩的坑多了,慢慢就摸清了门道。今天咱们就把IAM策略语法从头到尾捋一遍。嗯,我保证,看完这章你也能自己写出一手漂亮策略。
3.1 策略长什么样?
先看一个最简策略,感受一下骨架:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket"
}
]
}
你看,核心就两个键:Version 和 Statement。Statement 是一个数组,里面放一条或多条具体的权限规则。每条规则又由 Effect、Action、Resource、Condition 这四个基本元素组成。
说白了,IAM 策略本质上就是一个权限声明清单。你告诉 AWS:“谁,能对什么资源,做什么操作,在什么条件下。”就这么简单。
3.2 Version:版本不是摆设
目前官方推荐的版本号只有一个:"2012-10-17"。为什么非要写这个?因为 AWS 不同时期支持的策略语法有差异。比如早期版本不支持 Condition 里的某些操作符。我个人习惯每次都写最新版,省得以后出兼容性问题。
"2008-10-17" 那个老版本。很多新特性(比如跨账户信任策略)根本不认它。我见过有人从旧项目里复制策略,忘了改版本号,结果折腾了一下午。
3.3 Statement:权限规则的核心集合
每个 Statement 是一条独立的权限声明。你可以写多条,它们之间是“或”的关系——只要有一条匹配,权限就生效。不过要注意,Effect 为 Deny 的优先级永远高于 Allow。这个后面会细说。
Statement 内部的结构是这样的:
| 字段 | 是否必填 | 说明 |
|---|---|---|
| Effect | 是 | Allow 或 Deny |
| Action | 是 | 允许或拒绝的操作列表,支持通配符 |
| Resource | 大部分情况是 | 操作针对的资源 ARN,支持通配符 |
| Condition | 否 | 附加条件,进一步缩小权限范围 |
| Sid | 否 | 可选标识符,方便日志审计时定位 |
你看,Sid 不是必须的。但我建议你每条 Statement 都加上一个可读的 ID,比如 "AllowS3ReadOnly"。为什么?我曾在排错时看到 CloudTrail 日志里只有策略 ARN 和 Sid,没有 Sid 你根本不知道哪条规则触发的。加一行 Sid,排障效率翻倍。
3.4 Effect:点明是允许还是拒绝
就两个值:Allow 和 Deny。逻辑很简单——但陷阱在默认规则。当一个请求没有匹配到任何显式的 Allow 时,默认就是 Deny(隐式拒绝)。而显式的 Deny 可以覆盖任何 Allow。
举个例子:你给一个用户附加了管理员权限(Allow *),然后又单独加了一条策略 Deny 删除 S3 存储桶。那么那个用户就确实无法删除存储桶。显式 Deny 是铁律,谁都绕不过。
3.5 Action:定义允许或拒绝的操作
Action 是一个数组,每个元素是一个服务操作标识符。格式是 服务前缀:操作名,比如 s3:GetObject、ec2:DescribeInstances。支持通配符 *,比如 s3:* 表示所有 S3 操作。
写 Action 时我有一条经验:尽量用最小权限原则。不要一上来就写 "*:*"。你想想看,一个只需读取日志的 Lambda 执行角色,给它 logs:GetLogEvents 就够了,没必要给 logs:*。你不知道哪天 AWS 推出个新操作,万一那个操作能写日志呢?
另外要注意,有些 Action 是“资源级”的,有些是“服务级”的。比如 iam:CreateUser 不支持指定资源 ARN(因为它创建的是全局资源),你只能写成 "Resource": "*"。这属于设计上的限制,记住就行。
3.6 Resource:指定操作目标
Resource 的值是 ARN(Amazon Resource Name)。ARN 的格式是:arn:partition:service:region:account-id:resource-type/resource-id。比如一个 S3 存储桶的 ARN 是 arn:aws:s3:::my-bucket。
支持通配符 * 和 ?。比如 arn:aws:s3:::my-bucket/* 表示该桶下的所有对象。但注意,桶本身和桶内对象是两个不同的资源,策略里需要分别授权。
我踩过一个坑:想给用户列出某个桶里所有对象的权限,写了 "arn:aws:s3:::my-bucket/*",但忘了加 "arn:aws:s3:::my-bucket"(不带斜杠的版本)。结果用户能看对象列表,但无法执行 s3:ListBucket 操作——因为 s3:ListBucket 作用于桶本身,不是对象。这个细节很容易被忽略。
3.7 Condition:精细化的条件魔法
Condition 是我认为最强大也最容易翻车的部分。它让你在 Allow 或 Deny 的基础上增加额外限制。比如只允许从特定 IP 访问、只允许使用 HTTPS、只允许在指定时间段操作。
Condition 的写法是:
"Condition": {
"条件运算符": {
"条件键": "条件值"
}
}
举个实际例子:
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
这条条件表示只有当请求来源 IP 属于 203.0.113.0/24 网段时,规则才生效。
常见的条件运算符有这些:
| 运算符 | 作用 | 典型场景 |
|---|---|---|
StringEquals |
字符串精确匹配 | 限制只能使用某个特定标签 |
StringLike |
字符串通配符匹配 | 限制只能访问以 prod- 开头的资源 |
IpAddress |
IP 地址匹配(CIDR) | 只允许公司内网访问 |
Bool |
布尔值判断 | 检查是否使用了 MFA |
DateLessThan |
日期/时间比较 | 设置临时访问有效期 |
ArnEquals |
ARN 精确匹配 | 限制只能从特定角色访问 |
条件键分两类:aws 全局条件键(比如 aws:SourceIp、aws:RequestedRegion)和服务特定条件键(比如 s3:x-amz-server-side-encryption)。全局键对所有服务都有效,服务键只对该服务生效。
ForAnyValue:StringEquals 和 ForAllValues:StringEquals 的区别很大,用错了会放行非法请求或误拦合法请求。我的习惯是先做正向测试(只允许什么),再做逆向测试(拒绝什么),保证不遗漏。
3.8 一张图看懂策略结构
下面这张 SVG 把策略的层级关系画清楚了,看一眼就能记在脑子里:
3.9 综合示例:一条带条件的只读策略
咱们把上面讲的知识点串起来,写一个真实场景下的策略:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3ReadOnlyInOffice",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::company-docs",
"arn:aws:s3:::company-docs/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "10.0.0.0/8"
},
"Bool": {
"aws:SecureTransport": "true"
}
}
},
{
"Sid": "DenyDeleteForAll",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::company-docs/*"
}
]
}
这个策略做了两件事:第一,允许用户在办公网段(10.x.x.x)内通过 HTTPS 读取 company-docs 桶里的对象和列表;第二,明确禁止任何人删除该桶里的任何对象。注意 Deny 没有加 Condition,所以全局生效,包括管理员(除非有更高优先级的策略绕过)。
aws:SourceIp 而不是 aws:SourceVpc。后者用于 VPC Endpoint 场景,容易搞混。我曾经把这两个键用反了,结果策略死活不生效,查了半天才发现是键名写错了。
好了,以上就是 IAM 策略语法的全貌。Version、Statement、Effect、Action、Resource、Condition——每个都有自己的脾气。但只要你多写几遍,把这些概念像搭积木一样组合起来,就能构建出既安全又灵活的权限体系。嗯,我现在写策略已经很少翻文档了,希望你也早日达到这个手感。