TT Lab
开始
学习 学习路径 课程

对象存储与 S3

用用户与策略控制访问

在 TT Lab 中继续学习

目标

创建服务用户,亲手编写并附加最小权限策略,养成把权限范围收窄到前缀级别的习惯。

为什么重要

存储泄露事故大多不是来自复杂的攻击,而是来自开得过宽的权限。比如以“只有图片而已”为由,把存储桶开放为匿名读取,几个月后备份或用户上传的内容就混进了这个存储桶。把策略的 Resource 设为整个存储桶(arn:aws:s3:::bucket/*)也是同样的问题——只要一个服务的凭据泄露,整个存储桶就暴露了。如果收窄到前缀,损害就被限制在那个前缀之内。而且这同时也是对实现缺陷的防御。MinIO 的 CVE-2025-62506 是一个通过绕过会话策略而提升权限的漏洞。在那样的时刻,写窄策略的习惯就是最后一道防线。

本实验使用与 AWS 相同的 IAM API(aws iam ...)。服务器是 SeaweedFS 的 S3 网关,它以与 AWS 相同的调用接收用户、访问密钥、托管策略和附加操作。因此这里用到的命令和策略文档,可以原样带到 AWS 账号中使用。

步骤

  1. 用管理员 profile local 执行 aws iam create-user --user-name appuser,并把通过 aws iam create-access-key --user-name appuser 得到的密钥写入 profile app(同时写入 endpoint_url 和 region)。
  2. 尝试 aws --profile app s3 ls s3://lab-media,并把结果保存到 /root/pol/denied.txt。文件中必须有 AccessDenied。
  3. 在 /root/pol/readonly.json 中写入策略。允许 s3:GetObject 和 s3:ListBucket,Resource 有 arn:aws:s3:::lab-media 和 arn:aws:s3:::lab-media/* 两个。
  4. 用 aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.json 创建托管策略,并用 aws iam attach-user-policy 附加到 appuser。aws --profile app s3 ls s3://lab-media 必须成功。
  5. aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txt 必须失败。在 /root/pol/write.txt 中写入 write_denied=true。
  6. 在 /root/pol/imgonly.json 中写入把 Resource 收窄为 arn:aws:s3:::lab-media/img/* 的策略,以 labimgonly 为名创建并附加(解除较宽泛的 labreadonly)。用 appuser 查询 img/logo.png 必须成功,查询 doc/sales.csv 必须失败。在 /root/pol/prefix.txt 中写入 img=ok doc=denied。
  7. 用存储桶策略,只把 lab-media 的 public/ 前缀开放为匿名下载。匿名 GET 访问 public/ 之下的对象必须返回 200,访问 private/ 之下的对象必须返回 403。在 /root/pol/anon.txt 中写入 public=200 private=403。

参考

创建服务用户

用管理员 profile local 执行 aws iam create-user --user-name appuser,并把通过 aws iam create-access-key --user-name appuser 得到的密钥写入 profile app(同时写入 endpoint_url 和 region)。

用管理员 profile 创建用户并签发访问密钥。密钥(secret key)只会在签发响应中显示一次,立即写入 profile。

确认没有策略的用户被拒绝

尝试 aws --profile app s3 ls s3://lab-media,并把结果保存到 /root/pol/denied.txt。文件中必须有 AccessDenied。

用新 profile 尝试列出会被拒绝。默认情况下没有任何权限。

编写只读策略

在 /root/pol/readonly.json 中写入策略。允许 s3:GetObject 和 s3:ListBucket,Resource 有 arn:aws:s3:::lab-media 和 arn:aws:s3:::lab-media/* 两个。

用 JSON 写出 Effect、Action、Resource。列出内容和读取对象是不同的 Action。

创建并附加托管策略,使读取成功

用 aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.json 创建托管策略,并用 aws iam attach-user-policy 附加到 appuser。aws --profile app s3 ls s3://lab-media 必须成功。

创建策略和把它附加到用户,是两个独立的步骤。附加之后,生效可能需要几秒钟。

确认写入仍然被拒绝

aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txt 必须失败。在 /root/pol/write.txt 中写入 write_denied=true。

如果是只读策略,上传就应该失败。失败才是正常的。

创建限定前缀的策略

在 /root/pol/imgonly.json 中写入把 Resource 收窄为 arn:aws:s3:::lab-media/img/* 的策略,以 labimgonly 为名创建并附加(解除较宽泛的 labreadonly)。用 appuser 查询 img/logo.png 必须成功,查询 doc/sales.csv 必须失败。在 /root/pol/prefix.txt 中写入 img=ok doc=denied。

在 Resource 中把通配符一直加到前缀。要让 img/ 可以访问,而 doc/ 不可以。

只把特定前缀匿名公开

用存储桶策略,只把 lab-media 的 public/ 前缀开放为匿名下载。匿名 GET 访问 public/ 之下的对象必须返回 200,访问 private/ 之下的对象必须返回 403。在 /root/pol/anon.txt 中写入 public=200 private=403。

开放整个存储桶和只开放一个前缀,事故的规模不同。

留意 Resource 末尾的那一个斜杠。本实验的要点全在这里。

"Resource": "arn:aws:s3:::lab-media/public*"    → public 으로 시작하는 모든 키
"Resource": "arn:aws:s3:::lab-media/public/*"   → public/ 아래 키만

S3 中没有文件夹。策略看的只有字符串前缀,所以漏掉斜杠的话,像 public-backup.tar 这样仅仅以 public 开头的根对象也会被一起开放。用 aws s3api get-bucket-policy --bucket lab-media 确认服务器上实际生效的 Resource。

一个存储桶只有一份存储桶策略,每次执行 put-bucket-policy 都会整体替换。要更改范围时,重新提交整份文档。