策略变量与条件键:让IAM策略活起来

讲完了角色和密钥的基础绑定,咱们得聊聊怎么让策略变得智能。说白了,就是让策略能根据上下文做决策。你不能给所有人都放行是吧?

我个人习惯把策略变量和条件键叫做IAM策略的“眼睛和大脑”。没有它们,策略就是死的——要么全放行,要么全禁止。那太粗暴了。

先理解策略变量:${}

你在策略里写过 ${aws:username} 吗?这就是个变量,运行时会被替换成真实的用户名。AWS 支持很多全局变量,我挑几个最常用的列出来。

变量名 含义 典型使用场景
${aws:username} 当前IAM用户的用户名 限制用户只能操作自己名字命名的S3前缀
${aws:userid} 用户的唯一ID 与临时凭证联合使用
${aws:AccountId} 当前账号ID 跨账号策略里限制来源账号
${aws:SourceIp} 请求来源IP 条件键里配合IpAddress使用
${aws:CurrentTime} 当前UTC时间 时间窗口控制

有个坑:${aws:username} 只对IAM用户有效。角色或联合身份会返回空。我在项目里一开始没注意,导致角色下的策略全废了——嗯,查了半天才发现。

条件键是决策开关

条件键写在 Condition 块里。它负责判断“请求环境是否满足某个条件”。条件满足,策略生效;不满足,Deny或跳过。

最常见的两个条件键:IpAddress 和 StringEquals。来看个例子。

{
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::my-bucket/*",
    "Condition": {
        "IpAddress": {
            "aws:SourceIp": ["10.0.0.0/8", "192.168.1.0/24"]
        },
        "StringEquals": {
            "s3:x-amz-server-side-encryption": "AES256"
        }
    }
}

这个策略只允许来自内网IP段且强制使用AES256加密的请求。一次匹配两个条件——说白了就是“AND”逻辑。

条件键类型对比

AWS 有几十种条件键。我帮你归个类,记这个表就够了。

类型 条件键举例 比较方式 常用运算符
字符串 aws:username, s3:x-amz-acl 精确匹配 / 通配符 StringEquals, StringLike
IP地址 aws:SourceIp CIDR匹配 IpAddress, NotIpAddress
数字 s3:max-keys 范围比较 NumericEquals, NumericLessThanEquals
日期/时间 aws:CurrentTime 时间范围 DateGreaterThan, DateLessThan
布尔 aws:SecureTransport true/false Bool
二进制 aws:SourceIdentity 精确匹配 BinaryEquals

核心逻辑:Condition 块里的所有条件默认是 AND 关系——必须全部满足。如果你想用 OR,得把多个条件分到不同的 Condition 块里,或者用 ...IfExists 变体。

实战:用变量和条件键做精细权限隔离

我之前帮一个团队重构过多云存储权限。他们的需求很简单:每个开发人员只能操作自己的目录。你想想看,如果用静态策略,得为每个人单独写一条,那太蠢了。

用变量一行搞定:

{
    "Effect": "Allow",
    "Action": "s3:PutObject",
    "Resource": "arn:aws:s3:::dev-bucket/home/${aws:username}/*"
}

但是有个隐患——万一别人伪造 aws:username 呢?嗯,aws:username 来自IAM身份验证,无法伪造。不过要注意:如果用户通过角色代入,这个变量是空的。所以我通常会在条件里加一层校验:

{
    "Effect": "Allow",
    "Action": "s3:PutObject",
    "Resource": "arn:aws:s3:::dev-bucket/home/${aws:username}/*",
    "Condition": {
        "StringLikeIfExists": {
            "aws:username": "*"
        }
    }
}

StringLikeIfExists 表示:如果 aws:username 存在,就检查是否匹配任意字符;如果不存在(比如角色),则跳过该条件。这样角色就不受影响。

我曾经遇到过一个线上事故:一位运维用 ${aws:username} 做资源前缀隔离,但没加 IfExists,导致所有通过角色拉取数据的服务全部报403。所以记住——变量可能为空,策略要兜底。

SVG:策略变量与条件键决策流程

下面这张图展示了策略引擎是如何利用变量和条件键做决策的。你可以看到变量替换、条件匹配、最终放行或拒绝的完整路径。

IAM策略变量与条件键决策流程 策略变量与条件键决策流程 用户发起请求 变量替换 ${aws:username} Condition 块匹配 IpAddress / StringEquals / 其他 不匹配 → Deny 匹配 → Allow 拒绝访问 放行执行 多个 Condition 键默认 AND 逻辑 变量为空时需用 IfExists 变体兜底

避坑指南:条件键的常见误区

  • 大小写敏感:IpAddress 和 ipaddress 不是同一个东西。我见过有人手写小写,策略直接无效。
  • 条件键作用域:有些条件键只对特定服务有效。比如 s3:x-amz-server-side-encryption 只能用在S3上,别想着在EC2的Action里用。
  • NotIpAddress 的坑:配合 Deny 使用时要小心。我曾经用 Deny + NotIpAddress 限制只允许内网,结果把AWS服务自己(比如CloudFront回源)也禁了——后来加了个 aws:SourceOwner 条件才解决。
  • 字符串通配符:StringLike 支持 * 和 ?,但 StringEquals 不支持。别搞混。

调试小技巧:用IAM策略模拟器(Policy Simulator)可以查看条件键的实际匹配结果。我把这个工具放在浏览器书签里,每次写复杂策略都先跑一遍。

总结一下关键点

变量和条件键让IAM策略从静态变成了动态。你可以用 ${aws:username} 做用户级隔离,用 IpAddress 做网络级控制,用 StringEquals 做属性级校验。三者组合起来,能实现非常精细的权限边界。

记住:条件键的匹配逻辑是AND,变量可能为空。写策略时多问一句“如果变量不存在怎么办?”——养成这个习惯,能少踩一半的坑。