分析 Agent 在 DevOps 中的安全风险不是新问题,而是原有 IAM 缺陷的加速暴露,阐述过度行动、缺审批、权限冲突三类问题。
我之前曾提出,Agentic AI 正在让 ClickOps 卷土重来,我现在依然认为这个判断成立。但那篇文章里的另一个观点,我已经不再认同:对 Agent 来说,设计最小权限比对人类更困难,因为在设计阶段,你无法预测 Agent 最终会决定采取哪些操作。
这个说法本身没错,但它并非 Agent 独有的问题。它真正反映的是我们授予权限的方式,而且这个问题早已存在于被授予权限的人类身上。
我听到的关于 Agentic DevOps 的大多数安全担忧,都值得认真对待。但其中几乎没有新问题。最常出现的有三个:Agent 会超出任务范围,破坏性地使用自己的凭证;没有任何机制强制要求人类批准 Agent 的行为;如果缩小 Agent 的访问范围,又会破坏那些确实需要更多权限的场景。这些问题无一例外,都是早已存在的身份与访问管理(IAM)缺陷。只是如今 Agent 执行得足够快,终于让它们暴露了出来。
最小权限并不是什么新近出现的建议。Saltzer 和 Schroeder 早在 1975 年的《The Protection of Information in Computer Systems》中就写道:“系统中的每个程序和每个用户,都应当仅使用完成工作所必需的最小权限集合。”这个道理已经显而易见了五十年。
Microsoft 的权限遥测数据展示了实际落实得如何。在其 2024 年多云风险状况报告中,2023 年,人类身份和工作负载身份所拥有的权限中,实际使用的仅占 2%;与此同时,超过 50% 的云身份可以访问所有权限和所有资源。
把这两个数字放在一起看:组织授予了近乎完整的访问权限,实际却只使用了大约五十分之一。
未使用的那 98% 从来都不安全,只是尚未被动用而已。当持有这些权限的身份是人类时,这一区别并不那么重要。因为人类以人的速度工作,会在执行破坏性命令前犹豫,而且大多数时候都会待在既定的 runbook 范围内。权限授予得很宽,实际使用得很窄,两者之间的差距靠人的习惯掩盖了过去。
Agent 没有这种习惯。给它 AdministratorAccess 和一个任务,它的操作空间取决于权限授予范围,而不是 runbook。
这就是完整的运行机制。Agent 并没有扩大任何人的权限,而是抹平了“已授予的权限”与“过去实际使用的权限”之间的鸿沟。所有源自“Agent 可能做得比我要求的更多”的担忧,实际上都在复述一个更古老的问题:这个身份一直都能做得比我要求的更多。
而且,这从一开始也不只是人类身份的问题。同一份报告发现,工作负载身份占全部云身份的 83%;平均每个组织中,每拥有三个具备超级权限的人类身份,就有七个同等级别的工作负载身份。在有人把语言模型接入云 API 之前,持有无限制访问权限的非人类主体就已经占了多数。
这道鸿沟之所以长期存在,是因为最小权限意味着一笔必须预先支付的成本:授予访问权限的人要承受额外摩擦,而回报却只是“一场没有发生的事故”。对于那些季度绩效以交付了多少功能衡量的人来说,这是一笔糟糕的交易。因此,访问权限被宽泛地授予,前提是假设没人会把它们全部用上。过去从来没有什么东西迫使组织支付这笔账单。
关于 Agentic pipeline,我最常听到的诉求是:在 Agent 执行破坏性操作之前,应当由人类进行批准。
这就是 step-up authentication。它早已被写入规范并投入使用多年。
NIST SP 800-63B 要求每个已认证会话都必须具有明确的重新认证超时时间;对于 AAL2,建议最长不超过 24 小时,并设置一小时的非活动超时。该标准已经假定会话的认证有效性会随时间衰减,必须由当场的人类重新完成认证。
云 IAM 也提供了强制执行这一要求的基础机制。AWS 提供了 aws:MultiFactorAuthPresent 和 aws:MultiFactorAuthAge 两个全局条件键,而且 AWS 自己的文档就给出了相应示例:允许所有 EC2 操作,但只要没有 MFA,就拒绝 StopInstances 和 TerminateInstances。在 Agent 所承担角色的 policy 中加入类似下面的 statement,破坏性调用就会失败,除非有人在最近五分钟内完成过 MFA:
{
"Sid": "RequireRecentMFAForDestructiveActions",
"Effect": "Deny",
"Action": [
"ec2:TerminateInstances",
"rds:DeleteDBInstance",
"s3:DeleteBucket"
],
"Resource": "*",
"Condition": {
"NumericGreaterThanIfExists": {
"aws:MultiFactorAuthAge": "300"
}
}
}
真正承担关键作用的是 IfExists 后缀。当请求使用长期凭证到达时,aws:MultiFactorAuthAge 会完全不存在,而 NumericGreaterThanIfExists 会把条件键不存在视为匹配。因此,无论 MFA 已经过期,还是根本没有 MFA,这条 statement 都会拒绝请求。这正是你想要的行为,也是普通 NumericGreaterThan 版本会悄无声息地 fail open 的原因。AWS 在自己的示例 policy 中也记录了 BoolIfExists 的同类陷阱,足以说明这道门槛有多容易写反。
重点不在于这份具体的 policy。重点是,“除非有人在最近五分钟内完成过身份认证,否则任何 Agent 都不得删除生产数据”这一要求,完全可以使用你已经部署在生产环境中的访问控制语言表达出来。它只是一个条件键。这里并不缺少技术,缺少的是一条从来没人写过的 policy。
这才是相关担忧真正站得住脚的地方,也是我需要反驳自己先前那种宽慰说法的地方。
在会话开始时执行 MFA,只能认证一个人类,并不能授权某项具体操作。一旦 Agent 持有通过 MFA 签发的会话凭证,它拿到的就是代表你同意的 bearer token:按照上面的 statement,有效期是五分钟;按照 AAL2 的上限,则最长可以达到 24 小时。在这个时间窗口内,它所做的一切都会携带你的认证。这只是让人出现在循环开始之前,而不是让人真正处于循环之中。
这个问题同样有现成的答案,只是答案并非来自我们的行业。欧洲支付监管曾遇到完全相同的问题:客户向银行完成身份认证,随后执行一笔交易,此时必须有某种机制把后者与前者绑定起来。欧盟委员会授权条例 Commission Delegated Regulation (EU) 2018/389 第 5 条给出的答案是 dynamic linking。认证码必须与具体金额和具体收款人绑定,只要其中任意一项发生变化,认证码就会失效。这个原则在该行业中有一个名字:what you see is what you sign。
把 dynamic linking 应用到 Agent 上,意味着不要批准一个会话,然后任由 Agent 在会话中行动。你批准的是终止实例 i-0abc123def456,这次批准只能覆盖这项操作,不能覆盖其他任何内容。一旦实例 ID 发生变化,批准就会失效。
云 IAM 并不提供这种能力。条件键只能根据 MFA 是否发生以及发生在多久之前设置门槛,无法判断某个人是否真正看过并同意了这次携带特定参数的具体调用。这确实是一项空白。但它是移植现有机制时留下的空白,而不是有待发明的新问题:自 2019 年以来,欧洲银行就被要求采用这种方式,而且这套基础机制已经在整个受监管行业的生产环境中运行。
第三个反对意见是:某些任务有时确实需要提升权限。当每个月都会出现需要宽泛权限的场景时,你无法把一个身份限制在狭窄的权限范围内。
没错,而这也是三个问题中最早得到解决的一个。大约在 1980 年,Bob Coggeshall 和 Cliff Spencer 在 SUNY/Buffalo 的一台运行 4.1BSD 的 VAX-11/750 上编写了 sudo,它的设计本身就是完整答案。你并不常驻持有 root 权限,而只是具备获得 root 权限的资格。你针对一条命令临时提升权限。提升后的权限有时间限制,窗口过期后就必须重新认证——当前的 sudoers 通过 timestamp_timeout 暴露了这个时间窗口。
我们把这套模型搬到了云基础设施上,却把它的核心原则丢在了身后。对应方案其实都已存在。Microsoft Entra Privileged Identity Management 区分 eligible role assignment 和 active role assignment。AWS 发布了一套需要自行部署、面向 IAM Identity Center 的临时提权解决方案。Vault 会签发带有 TTL 的动态凭证,并在凭证过期时撤销它们。然而,真正部署这些方案的组织仍然是少数。
“Agent 偶尔可能需要更宽泛的访问权限”,支持的是 eligibility,而不是 standing grant。一个请求临时提权、仅在一次操作期间持有权限,并在操作结束后自动失去权限的 Agent,其 blast radius 会被限制在那次操作之内。这并不是什么 Agent 专属控制手段,只是把 sudo 应用到那些你原本打算永久授予的访问权限上。
这些担忧中有两个无法归结为尚未完成的 IAM 工作。
第一个是 prompt injection。这里必须准确说明,为什么 MFA 和最小权限对它无能为力。在 injection attack 中,Agent 已经正确完成了身份认证,也得到了正确授权。它使用自身的合法权限,执行第三方诱导它采取的操作。访问控制系统会问:“这个主体是否被允许这样做?”它正确地回答“是”,攻击也因此成功。
这种失败有一个名字,其历史比我们可能用来应对它的大多数工具都要悠久。Norm Hardy 在 1988 年描述了 confused deputy:一个拥有特权的程序受到诱骗,代表一个本不具备相应权限的调用者滥用自己的 authority。新出现的只是攻击面。编译器可能会因为文件名受到迷惑;读取日志、工单和 pull request 描述的 Agent,则可能被任何进入其 context window 的内容迷惑。
已知的缓解手段,是让 authority 随请求传递,而不是随身份传递:使用仅能调用一次、且参数已经固定的 token 来授权某次调用,而不是给 Agent 一个可以持有并任意指向目标的角色。这又回到了 dynamic linking,只不过是从另一个方向抵达。Capability-based system 已经用这种方式建模了几十年,但几乎没有生产环境中的云 IAM 真正采用这种工作方式。
第二个问题是归因。当 Agent 做出变更时,变更管理框架要求对应一名人类操作者和一项明确的批准意图。如果是人类向 Agent 发出 prompt,这个 prompt 算不算批准?如果 Agent 是在自行读取某项指标后采取行动,又是谁签了字?我曾在那篇讨论 ClickOps 的文章中写过这会如何破坏审计轨迹,而现在我仍然没有比当时更好的答案。
清单上的其他一切问题——过于宽泛的凭证、不受限制的 blast radius、破坏性调用缺少批准门槛、永久持有提升后的权限——都是五年前做一次 IAM 审计就会发现的问题。它们原本会被记录下来,排在当前 sprint 之后,然后以 won't fix 关闭,因为当时没有任何东西会真正动用我们授予的那些权限。
Agentic DevOps 就是那场审计。它持续不断、以机器速度在生产环境中运行,而且不会提交报告。
请认真对待这些担忧,但也要诚实面对它们究竟来自哪个年代。大多数正在选购 Agent governance platform 的团队,真正需要的是一些更乏味的东西:条件键、eligible-instead-of-active role assignment,以及那份他们从未缩小过范围的 permission grant。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。