周五下午 5 点 27 分,群里弹出一张截图
DevOps 的老王在群里丢了张截图:我们一个内部仓库的 .env,第 12 行赫然躺着 ALIYUN_AK=LTAI5t...。配文只有一句:"这玩意儿应该在 code repo 里吗?"
那一瞬间我脑子里的画面不是"完了",而是"这个号上有多少东西"。很多人第一反应是删文件、改密码,但真正该先搞清楚的,是这把钥匙到底能开几扇门。当时我还挺自信——生产环境用了 KMS 加密,问题不大。打开 RAM 控制台之后,这份自信存活了大概十一秒。
顺藤摸瓜,越挖越凉
第一,那是主账号的 AccessKey。 它默认握着账号里最大的一把枪,策略上写着 Action: "*"、Resource: "*"。第二,它四年没换过。第三——这个最难受—— 三个团队、七八个服务共用同一把 AK,日志里根本答不出"谁在调 API"。
泄露的路子也比我想的多:
- 硬编码进代码,还有人贴心地写了
# TODO: 挪到环境变量,这个 TODO 挂了两年; - 配置中心、K8s ConfigMap、Jenkins 全局变量,一个都没落下;
- CI 里
echo调试完忘了删,构建日志永久留存; - 镜像里删了
.env,但docker history和各层里还躺着一份——那只是盖了块白布。
长期 AK 的危险不在难破解,而在于它是一张没有有效期、没有身份、没有用途边界的通行证。一旦流出,攻击者不用绕过任何认证,照着文档调 OpenAPI 就能拉数据、开机挖矿、加一个自己的后门子账号。
第一步:止血——先禁用,再换钥匙
我们没直接删那把 AK,而是先禁用。原因很朴素:直接删,你会同时收获一次线上事故和一堆追问。禁用之后挂一天,谁的调用报 InvalidAccessKeyId,谁就自己暴露了——成本最低的资产盘点。
盘完所有调用方,才发新凭证,顺手立两条规矩:
- 不再共用。 每个调用方一个 RAM 用户,命名
svc-{业务}-{环境},出事一秒定位到服务。 - 控制台登录一律关闭,只留编程访问。 确需登录的人走独立 RAM 用户,强制绑 MFA。
这一步没什么技术含量,但它把"匿名钥匙"换成了"实名钥匙",后面的治理才有抓手。
第二步:收权限——最小权限不是"少给点"
换钥匙只是止血,旧策略还是 *。权限我们收成三层。
用用户组做批量管理。 按业务建组,权限策略挂在组上,人来人往只动成员关系——不然每次有人离职,都得在一堆内联策略里玩俄罗斯方块。
按需授权,Action 和 Resource 一起收。 一个只往某个 OSS 目录写报表的服务,策略大概这样:
{
"Effect": "Allow",
"Action": ["oss:PutObject"],
"Resource": ["acs:oss:*:*:report-bucket/daily/*"]
}
关键在 Resource。只把 Action 收窄却留着 Resource: "*",等于给万能钥匙换了个钥匙扣。能用条件(Condition)的再叠一层,限制来源 IP 或 VPC。
能不用系统策略就不用。 AliyunXXXFullAccess 上手快、埋雷也快。我们全量盘了一遍,逐个换成自定义策略,顺便发现有三分之一的权限从来没被调用过。
第三步:让密钥从代码里彻底消失
前两步是把密钥管好,第三步是根本不需要密钥。
ECS 上跑的服务一律绑定 RAM 角色,也就是实例角色。应用不再保存任何 AK,SDK 自己去实例元数据服务(100.100.100.200)取 STS 临时凭证,到期自动刷新,配置文件里那行 accessKeyId 直接删掉。临时凭证默认一小时有效期,即使被截获窗口也就这么长,而且每份都能回溯到"哪台实例、扮演了哪个角色"。
跨账号用 AssumeRole:A 账号要读 B 账号的数据,不是把 B 的 AK 复制一份过去,而是 B 建一个角色、把角色 ARN 授给 A 的 RAM 身份,再配合 ExternalId 和收窄的会话策略,把能干什么卡死在会话级别。
改完这一轮,代码库里的 LTAI 从"必需品"变成了"可疑物"——好信号。(RAM 本身是免费服务,创建 RAM 用户的入口在 访问控制 RAM 产品页,点「RAM 管理控制台」就能进。)
改造之后,我给自己列了张自查清单
代码库里还有没有硬编码凭证?
- 用 AK 特征前缀在全量 git 历史里搜,别只搜 HEAD——文件删了,提交记录还在;
- 上 gitleaks / trufflehog 一类工具,扫历史提交和各个分支;
- 在 CI 加卡点,pre-commit 或流水线先扫再构建;
- 镜像也翻一遍:
docker history、导出 tar 看各层有没有残留的.env。
权限是不是真的收敛了?
- 收紧策略后跑一遍业务回归,再去操作审计里翻这段 API 调用,看实际用到的 Action 和策略对不对得上;
- 操作审计是主力:谁、何时、从哪个 IP、调了什么、成功还是失败,一目了然;
- 定期拉一次"90 天未使用的权限"和长期未用的 AK;
- 故意做一个越界操作,确认它真的被 Deny,而不是你以为它被 Deny。
临时凭证链路通不通?
- 在实例上确认能取到 STS 凭证,且有明确过期时间;
- 代码里再搜一遍
LTAI和accessKeySecret,确认没留"兜底"的本地密钥——很多改造就栽在这个兜底上。
另外,AK 定期轮换别设成"下次一定",把 AK 泄露检测接上,让它主动告警,而不是等同事截图。
一点心得
这次最大的收获不是记住几个产品名词,而是想明白一件事:权限治理的核心不是加密,是让每一次调用都能对应到一个具体的身份和一个具体的用途。
长期 AK 危险,是因为它同时抹掉了这两样:没有身份,出事查不到人;没有用途边界,一出事就是全域。RAM 用户、最小权限策略、实例角色、STS,本质都是在把这两样一件一件加回去。
至于最小权限,它不是能画勾的配置项,而是每季度都要重做一遍的动作。发截图的老王,后来成了我们权限审计的常驻轮值——最早发现问题的人,最有动力盯着它别复发。