第 3 / 21 篇

第2章 人、程序与委托关系必须分别建模

谁说的,谁执行的

企业 Agent 最常见的身份错误,是把用户登录身份与程序运行身份折叠成一个字段。客服登录只说明浏览器背后的人通过了某种认证;执行工具的进程还拥有自己的软件身份、部署环境和权限。Agent 代办任务又引入第三个事实:这个程序在特定范围内代表这个人执行。三者有关联,却不能互相替代。

本书用 subject 表达业务责任主体,用 actor 表达执行程序,用 tenant 表达组织边界,用 epoch 表达主体当前授权版本。实验中 Alice 是 subject,agent-demo 是 actor,acme 是 tenant。主体和执行者同时进入审计,才能回答“谁授权了这件事”和“哪个程序实际发起了请求”。如果审计只有 agent-demo,所有用户行为会合并;如果只有 Alice,恶意或错误的执行程序会消失在记录里。

现实系统可以使用更丰富的结构:原始用户、当前代理、上游服务、会话、任务与审批人。不要把每一跳都无条件写进可无限增长的 JWT。应保存稳定的链路标识,让受控审计系统关联完整上下文,并明确哪些身份事实由签发方证明,哪些由当前业务服务确认。

服务账户不是用户代办

Keycloak 的 client credentials 实验只证明工作负载身份。agent-tool 客户端获得服务账户令牌后,被显式映射到本地 svc-reader 主体,只能读 Acme 订单。它不会变成 Alice,也没有“因为服务很可信,所以能代表所有客服”的隐含规则。该区分在导入自动化后台任务时尤其重要:一个定时清理程序可能确实使用服务权限,而一个客服代办 Agent 应受到当前用户权限约束。

失败案例发生在一个工单系统。团队为了省事,让所有 Agent 使用同一管理员账户调用后端,然后把用户名字放进 X-User。后端看到管理员凭据就执行,审计却记录头部中的用户。攻击者只需改一个头,既获得管理员能力,又能把行为归到别人身上。修复不能仅仅要求代理“诚实填写”:业务服务需要验证受信的委托事实,且不能让调用方自行选择责任主体。

本书的自定义委托端点只接受固定代理服务凭据,再验证输入令牌的受众、actor 和当前状态。它不会接受一个任意 subject 字符串并替其签名。生成业务凭据时主体来自已验签令牌,actor 来自服务器配置,tenant 与 epoch 同样保留。调用方无法在请求参数中指定一个更有权力的人。

身份映射是授权配置

组织目录中的用户 ID 应稳定且不可复用,邮箱和显示名适合展示,不适合作为长期主键。外部 issuer 与 subject 应共同组成身份键,因为不同签发方可以合法使用同一个 subject。企业合并、账号迁移与离职重入职时,应保留历史审计关联,并避免给新员工继承旧人的审批或授权。

服务身份映射也要可审阅。Keycloak 路径验证固定 realm、tool-proxy audience、agent-tool authorized party 与 agent-order-reader 角色,再映射为本地只读主体。生产系统应把这一映射放在版本化配置或受控策略存储中,记录来源和审核人。仅凭令牌中含有某个字符串就自动创建高权限主体,会把身份接入变成权限注入入口。

实验与推演

运行 go test -race -run 'TestApprovalBindingsAndReplay|TestTicketIsolationAndApproval' -v ./lab。第一个测试让另一个主体即使被临时放进同一租户,也无法使用 Alice 的审批;它还用不同 actor 重新签发凭据,验证执行程序不匹配被拒绝。第二个测试证明 Bob 可以关闭 Beta 的工单,却不能因此关闭 Acme 的工单。

这些测试里的临时签发能力只存在于测试进程,用来精确制造攻击前提。真实攻击者没有教学 issuer 私钥;测试故意模拟错误签发配置,检验业务约束能否再挡一层。这样的纵深验证有价值,但不能据此声称私钥泄漏没有影响。

练习:为你的系统列出用户、服务账户、审批人和 Agent 执行器的身份来源。写出“同名”“改邮箱”“跨租户调岗”“服务迁移”四种情况下主键是否改变,以及历史审批会发生什么。若答案依赖一个可由模型输入的显示名,应先修正模型。

一个委托链的设计评审

假设客服把任务交给协调 Agent,协调 Agent 又调用专业退款 Agent,最终由工具执行器发 HTTP。不能让每一层都把自己写成 subject,也不能让它们任意覆盖最初责任人。可以保留原始主体,使用受控 actor 链或任务执行记录表达中间节点,并规定最大跳数与允许的服务组合。链越长,撤权、审计和排障越复杂,默认允许无限递归不是合理选择。

还要区分“代表用户执行”与“根据用户提出的事件执行系统职责”。例如客户申请退款后,财务系统按独立政策自动结算,责任主体可能是受控财务服务,而不是一直假装客户拥有财务系统权限。这类系统行动应有独立授权依据和业务事件记录,不能靠交换成客户身份解决。

评审时让团队口头描述一句完整的话:“服务甲依据授权记录乙,代表主体丙,在租户丁对对象戊执行动作己。”如果其中任何一项只能从模型自由文本猜测,身份契约尚未闭合。随后再问账号删除、主体合并和服务重新部署是否改变这句话,能够更快发现主键和历史记录设计中的问题。