多 Agent 权限治理:统一入口与审计
教程将单 Agent 授权方案扩展到企业场景,通过 AgentCore Gateway 统一入口、兑换 Okta 授权,并以 Cedar 策略绑定工具与 Agent。方案还接入运行于 EKS 的第二个 Agent,在 CloudWatch 中关联访问决策与身份日志。
AI 深度解读
一个 AI agent 能替用户调用工具,不代表一百个 agent 接入后,权限还管得住。AWS 这篇 Part 2 把上一版的授权链扩成统一入口:由 AgentCore Gateway 换取目标系统令牌,Cedar 检查 agent 能用哪些工具,再把 Okta 授权和工具端判定放进同一条审计链。
它最值得读的地方,是把三个问题分开了:谁允许 agent 代办、agent 能调用什么、当前用户是否有权完成这次操作。三层各管一段,才方便扩大接入范围和追查拒绝原因。
统一入口接管的是换票,不是所有权限判断
Part 1 的结构很小:一个 agent、两个 Model Context Protocol(MCP)服务器,各服务器自行检查权限。到了数百个 agent、数千个工具的规模,继续逐个接线、复制检查逻辑,配置和排障都会变重。
Part 2 保留 Okta 配置、AgentCore Identity credential provider 和 MCP 服务器,把公共控制集中到 Gateway。
用户登录后,前端后端层(BFF)用 ID token 换取 Okta 签发的 Identity Assertion JWT Authorization Grant(ID-JAG)。可以把它理解为一张委托凭证:某个 agent 获准代表某个用户访问指定资源。
Agent A 把 ID-JAG 作为 MCP 会话的 bearer token 交给 Gateway。入口通过 customJWTAuthorizer 校验:discoveryUrl 指向 Okta org authorization server,allowedAudience 指向资源的 custom authorization server,allowedClients 锁定该 agent,allowedScopes 配置为 xaa:read、xaa:write。
只有 Cedar 允许的调用,才进入目标系统的令牌交换。下面这段配置让 Gateway 通过 Part 1 的 provider 执行交换:
"credentialProviderConfigurations": [{
"credentialProviderType": "OAUTH",
"credentialProvider": {"oauthCredentialProvider": {
"providerArn": provider_arn, # Part 1 的 JWT_AUTHORIZATION_GRANT provider
"scopes": ["xaa:read", "xaa:write"], # 必须与 ID-JAG 的 scopes 相同
"grantType": "TOKEN_EXCHANGE",
}},
}]
要盯住 scopes:作者要求它与 ID-JAG 的 scopes 相同,不能在这里随意裁剪。被 Cedar 拒绝的调用不会兑换凭证。
AgentCore Identity 保存交换得到的 token,因此单次使用的 ID-JAG 可以支撑会话内多次工具调用。作者还测到,它在 5 分钟有效期内可以开启第二个会话。单次使用的委托凭证,不等于只能调用一次工具。
Agent A 随后移除执行角色中的换票和 secret 权限,相关权限转到 Gateway。由于一个 provider 以一个 Okta agent 身份认证,这套方案为每个 agent 分配独立 Gateway 和 provider。「统一入口」因此是一套统一控制方式,并非全企业只有一个实例。
Cedar 管 agent 的边界,工具端仍要检查用户
Policy in AgentCore 以 ENFORCE 模式挂到 Gateway,在工具列表和工具调用阶段执行 Cedar 策略。作者采用仅包含 permit 的策略,没有匹配许可就拒绝。
下面这段把两个写工具绑定到指定 Gateway、agent 和委托 scope:
permit(
principal is AgentCore::OAuthUser,
action in [AgentCore::Action::"taskmanager___create_task",
AgentCore::Action::"taskmanager___update_task_status"],
resource == AgentCore::Gateway::"<gateway-arn>"
) when {
principal.hasTag("client_id") && principal.getTag("client_id") == "<agent-client-id>" &&
principal.hasTag("scope") &&
(principal.getTag("scope") == "xaa:write" ||
principal.getTag("scope") like "xaa:write *" ||
principal.getTag("scope") like "* xaa:write" ||
principal.getTag("scope") like "* xaa:write *")
};
注意每次 getTag 前的 hasTag。读取缺失 tag 会报错并跳过策略;跳过 permit 不会授予权限,但跳过 forbid 也不会阻止调用。
四种 scope 匹配是在空格分隔的字符串里匹配完整条目,避免把 xaa:write-admin 误认成 xaa:write。读工具另写对应 xaa:read 的许可。
接入时还有三个容易漏的地方:工具应配置 listingMode=DEFAULT 和内联 mcpToolSchema,固定策略引用的工具名;客户端必须跟随 tools/list 的分页游标,否则后面 target 的工具会消失;挂载引擎前要确认策略都是 ACTIVE,策略创建失败可能让所有调用被拒绝。动态列举 target 无法挂接具名工具策略,是作者测试中的发现,不能泛化成所有配置的永久限制。
这些检查没有取消工具端授权。MCP 服务器仍根据用户的 groups 和 scope 决定具体操作。Alice 可以拿到任务 agent 的读写委托,却仍然没有创建任务的业务权限。
第二个 reporting agent B 运行在 Amazon EKS,只获 xaa:read,使用自己的 Okta 注册、Gateway 和 provider。作者实测中,A 拒绝 Alice、Carol 创建任务,允许 Bob 创建;B 则告诉 Bob 没有创建任务的工具。
EKS pod 前没有 AgentCore runtime authorizer,因此调用模型前要自行验证签名、issuer、audience、oauth-id-jag+jwt、expiry,以及 client_id 是否属于自己。保留工具端 agent 检查时,还要把 B 加入 REGISTERED_AGENTS。部署上,作者遇到的两个坑是 Auto Mode 通用节点池仅运行 amd64,以及 Python SDK 需要设置 DOCKER_CONTAINER=true,否则默认绑定 localhost。
审计要串起授权与拒绝,撤权则要区分两条链路
Runtime、Gateway、Identity 和 Policy 开启 trace delivery 后输出 OpenTelemetry spans,自建 BFF、MCP 服务通过 AWS Distro for OpenTelemetry(ADOT)补齐。工具端把用户、agent、决定和原因写进 authz.check。
Okta System Log 经 Amazon EventBridge 进入 CloudWatch Logs,再由 CloudWatch Omni 与 Transaction Search 收集的 spans 一起查询。作者测到,事件在 Okta 发布后约 1 秒到达。
示例查询把最近 1 小时内的 app.oauth2.token.grant.id_jag 事件,与 view.xaa_authz_decisions 关联:匹配用户和 agent,并限制决定发生在授权后 5 分钟内。结果显示 Alice 获得 xaa:read xaa:write 后,16.7 秒发生创建任务拒绝,之后列举任务获准。
这能解释「拿到委托为什么还失败」,但时间窗口关联不是唯一请求标识。同一用户和 agent 短时间多次授权时,仍要检查关联是否存在歧义。
插桩也有实际前提:缺少 botocore 会让作者的 MCP 服务无法导出 spans;ADOT agent mode 默认关闭部分 HTTP 客户端插桩,BFF 需要调整 OTEL_PYTHON_DISABLED_INSTRUMENTATIONS,让使用的 requests 保持插桩,否则 trace context 传不到 agent。
性能数据来自 Gateway A 的 24 小时 spans:身份和策略 span 的中位数相加约 0.2 秒,工具端 authz.check 小于 1 毫秒;Gateway B 的 GetResourceOauth2Token 中位数为 408 毫秒。原文输入缺少所引用的完整表格,无法补出 90 分位数,也不能据此推算所有目标系统的开销。
撤权则取决于目标应用。接受 ID-JAG 的应用可以走 XAA,由 Okta 管理员决定委托关系,每次请求 token 时评估策略,用户无需逐个同意。GitHub 这类使用自身授权服务器的目标,则走用户 OAuth consent。
作者用 07-consent-portal-auth-code-flow-targets 做了对照:XAA 的控制会阻止下一次 ID-JAG 签发,已签发 token 约 5 分钟内过期;用户 consent 链路中,Okta 只控制登录,存储的 grant 在暂停用户后仍然存在,直到断开连接。暂停登录不能自动等同于撤销第三方授权。
我的判断
这套方案合理的地方,是把委托、工具访问和业务权限拆开,同时保留跨系统证据。对已有 Okta、AWS 和多个 MCP 服务的团队,它提供了明确的接入边界。
但「机制能扩展」不等于已经证明大规模运营成本可控。逐 agent 配置 Gateway、provider 和策略,仍需要自动化;原文没有给数百 agent、数千工具的压测,也没有与服务端自行换票、各工具维护授权的方案做量化比较。
作者估计增量搭建约 90 分钟,EKS 控制面为每小时 $0.10,另计节点、Auto Mode 管理费和 AgentCore 用量。这个时间建立在 Part 1 已完成的前提上。基础示例 08-okta-xaa-delegation-chain 与文中方案的换票实现也不同,后者代码只是概念示例,并非完整可部署方案。
对工具很少的团队,先保留清楚的工具端校验,可能更容易维护。是否引入统一网关,应看重复配置、凭证管理和审计排障已经造成多少实际负担。
能拿来做什么
- 做 BFF 到 agent 的接入时,分别记录登录身份、agent 身份和目标应用委托范围,避免只凭「用户登录成功」就放行工具调用。
- 维护 MCP 客户端时,检查
tools/list分页是否完整,并为策略引用的工具名固定 schema,避免列表缺失和策略失配。 - 给工具端补齐可关联的授权日志,至少包含用户、agent、操作、允许或拒绝及原因,让前端失败提示有证据可查。
- 为每个目标应用单独验证撤权行为,分别测试停止签发委托、已有 token 过期和断开 OAuth grant,明确暂停账号后仍可能访问多久。