Principal 元素详解:用户、角色、服务

讲完了 Action 和 Resource,我们来看看策略里另一个关键角色——Principal。说白了,Principal 就是那个「谁」的问题。你允许谁进来?你拒绝谁访问?这个「谁」就是 Principal。

我刚开始搞云安全那会儿,总觉得 Principal 不就是个用户嘛,有什么好研究的。后来被坑过几次才明白,Principal 的水很深。它可以是 AWS 用户、IAM 角色,甚至是一个 AWS 服务。而且不同场景下,Principal 的写法差别很大。

一句话总结:Principal 就是「你是谁」的身份证。策略里写清楚它,桶才知道该不该放你进门。

三类 Principal 的底层逻辑

我先给你拆开讲。Principal 在桶策略里主要有三种形式:

  • AWS 用户:一个具体的 IAM 用户或根用户。格式像 AWS: arn:aws:iam::123456789012:user/zhangsan。
  • IAM 角色:一个角色实体,比如 EC2 实例上挂的角色。格式类似 AWS: arn:aws:iam::123456789012:role/ec2-read-role。
  • AWS 服务:某个 AWS 服务代表你发出的请求,比如 S3 的日志写入、 CloudFront 的源站访问。写法是 Service: s3.amazonaws.com 这种。

这三种看起来差不多,但背后的授权逻辑完全不同。嗯,这里要注意,别混用。

我的经验:权限最小化原则在 Principal 这里同样适用。能写具体 ARN 就别写通配符,能用角色就别用根用户。我曾经见过有人直接把 Principal: "*" 写进策略,结果桶对外公开了三天才发现。别问我怎么知道的。

1. 用户 Principal:最基础的「身份证」

用户 Principal 是最直观的。你指定一个用户的 ARN,策略就只对该用户生效。但这里有几个坑:

  1. 主体与授权人是两码事:你写了一个用户 A 允许写桶的策略,但用户 A 的权限还不够,因为桶策略和用户策略是「与」关系。换句话说,用户 A 必须同时有 s3:PutObject 的权限才能真正上传。
  2. 跨账号用户需要写完整 ARN:本账号内可以写简写,但跨账号场景下,必须用 AWS: arn:aws:iam::其他账号ID:user/用户名。少一个字段都可能被拒。
  3. 根用户 vs. IAM 用户:根用户的 ARN 不带 user/,而 IAM 用户带。写错了,策略直接不认。
{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:user/zhangsan"
  },
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::my-bucket/*"
}

上面这段,说白了就是只允许 zhangsan 这个用户读桶里的所有对象。其他人来了,就算他有权显式允许,也要看这个策略的脸色。

避坑提醒:我曾经犯过一个低级错误:把 AWS 拼成了 aws(小写)。大小写敏感,AWS 策略引擎直接忽略这条。排查了半天才发现是大小写不对。所以写 Principal 时,AWS 必须大写,Service 首字母大写。

2. 角色 Principal:适合临时身份的场景

角色比用户更灵活。为什么?因为角色可以跨账号继承,也可以被多个实体扮演。我项目里最常用的场景是:让 EC2 实例读取 S3 桶中的数据。

怎么做?给 EC2 实例附加一个 IAM 角色,然后在桶策略里允许该角色的 ARN。这里有个关键点:

  • 角色 ARN 与角色会话 ARN 不同:你写策略时用的是角色本身的 ARN(arn:aws:iam::account-id:role/role-name)。当 EC2 实例扮演角色后,它会获得一个临时凭证,但这个凭证的来源仍然是角色 ARN。策略匹配的是源头,不是会话 ID。
  • 跨账号角色需要显式信任:如果角色在账号 A,桶在账号 B,那账号 B 的桶策略里要写 AWS: arn:aws:iam::账号A:role/角色名。同时,角色本身也要有信任策略允许账号 B 访问。
{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::123456789012:role/ec2-read-role"
  },
  "Action": [
    "s3:GetObject",
    "s3:ListBucket"
  ],
  "Resource": [
    "arn:aws:s3:::my-bucket",
    "arn:aws:s3:::my-bucket/*"
  ]
}

这段路的意思很直接:只允许扮演了 ec2-read-role 这个角色的实体来读桶。其他实体,包括这个账号下的其他用户,只要没扮演这个角色,都进不来。

我建议:角色 Principal 是权限最小化的最好工具。每个应用组件用独立角色,桶策略里只允许这些角色。这样万一某个角色泄露了,不会波及整个桶。

3. 服务 Principal:隐式授权与信任

服务 Principal 最容易被忽视。它指的是 AWS 服务代表你发起的请求。比如 S3 的日志写入、SNS 的推送通知、CloudFront 回源。

为什么需要服务 Principal?因为这些服务在内部会调用你账号下的资源。如果不显式允许,服务可能会报权限错误。举个例子:

  • S3 服务写入日志:你开启了 S3 访问日志,日志要写入另一个桶。这时 S3 服务自己会扮演一个角色来写。你需要在目标桶的策略里允许 Service: s3.amazonaws.com。
  • CloudFront 回源 S3:你用 CloudFront 加速 S3 内容,CloudFront 需要从 S3 拉取数据。同样需要在 S3 桶策略里允许 Service: cloudfront.amazonaws.com。
{
  "Effect": "Allow",
  "Principal": {
    "Service": "s3.amazonaws.com"
  },
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::log-bucket/AWSLogs/123456789012/*"
}

这段的意思是:允许 S3 服务把我的日志写入 log-bucket 桶里。注意,这里没有指定具体用户,而是授权给了一个服务。服务代表谁?代表你这个账号。

避坑指南:服务 Principal 的权限要清晰。我记得有一次,我写了个允许 Service: s3.amazonaws.com 的策略,但忘了限制 Resource 路径。结果 S3 的日志写到了桶的根目录,和其他数据混在一起。后来我加上了具体的前缀路径,这才干净。

Principal 的矩阵对比:一句话说清差异

Principal 类型 典型场景 权限范围 跨账号支持 常见问题
AWS 用户 单个 IAM 用户访问桶 仅该用户 支持 大小写写错、ARN 写错
IAM 角色 EC2 实例读桶 扮演该角色的实体 支持(需信任策略) 角色与角色会话混淆
AWS 服务 S3 日志写入、CloudFront 回源 服务代表账号执行 不支持(服务只能在同账号内授权) 忘记限制 Resource 路径

这张图,把 Principal 的关系讲清了

Principal 三要素关系图 目标 S3 桶 IAM 用户 IAM 角色 AWS 服务 注意事项 1. 用户 ARN 大小写敏感 2. 角色需要信任策略配合 3. 服务 Principal 限同账号 箭头含义 → 代表 Principal 请求访问桶 策略中的 Principal 决定谁可以

实战中怎么选?我的一些原则

其实没有的对错,主要看场景。我给你几个我的判断标准:

  • 如果是个人或固定团队:用 IAM 用户 Principal。简单直接,权限好管理。
  • 如果是自动化程序或跨账号:用 IAM 角色 Principal。角色可以轮换凭证,跨账号时也能保持安全。
  • 如果是日志、通知等系统服务:用服务 Principal。不要手动写入 IP 或凭证,让 AWS 服务代劳。

我个人的习惯:能写显式 ARN 绝不写 *,能分组授权绝不一把抓。Principal 越小,安全边界越清晰。写完策略后,我会用 IAM 模拟器或 aws s3api 命令测试一下,确保权限刚刚好,不松不紧。