Formal 如何用 OIDC 让云端编码智能体受控查询生产环境
We let our cloud agents query prod (with OIDC, not secrets)
Formal 通过 OIDC 集成让云端编码智能体以短期身份令牌访问生产数据与云基础设施,替代长期静态凭据。智能体会话可绑定发起工程师、代码仓库和会话 ID,生产访问保持只读且需人工审批;该方案已用于产品分析、异步工作流调试和云原生功能测试。
以 OIDC 将每次智能体运行映射为独立工作负载身份,并说明如何用策略与审批限制生产访问,方法可迁移到其他云端编码智能体。
在 Formal,我们讨厌机密。从第一天起,我们就一直在努力将基础设施中长期存在的机密数量降至零。
我们希望我们的产品以及客户与产品交互的方式也能体现这一理念。因此,几个月前我们构建了 OIDC Integrations:让任意机器工作负载能够凭借自身的基础设施身份向 Formal 进行身份验证,并可选地在此过程中证明一个人类身份。
除了产品和 CI/CD 工作负载之外,我们还发现,支持 OIDC 对让智能体在我们的基础设施中完成更多工作极为有用。诸如 Cursor 和 Devin 这样的云端编程智能体本身就是 OIDC 颁发者,这意味着我们现在可以通过 Formal 向它们授予可审计、受策略控制的只读权限,使其能够访问我们的生产数据和云基础设施。
OpenID Connect(OIDC)
简单回顾一下,对于机器工作负载,OIDC 身份验证流程如下。目标是从工作负载的身份提供商(IdP)获取一个短期有效的令牌,并将其作为身份证明用于称为依赖方(RP)的外部服务。依赖方通常已预先配置好该 IdP 的相关信息。
- 工作负载从其环境中获取 OIDC JWT 令牌。有时,该令牌以环境变量的形式提供(例如,GitHub、GitLab);有时,工作负载需要调用元数据端点的 API 来获取令牌(例如,AWS、GCP、Azure)。通常,令牌的作用域会限定为该 RP(例如,通过设置 Audience
aud声明),以防止令牌在不同 RP 之间重用。 - 工作负载调用 RP 的 API,并将获取的 OIDC 令牌作为 Bearer 令牌使用(即
Authorization: Bearer eyJ...)。 - RP 根据其配置查找 JWKS 端点(通常类似于
https://idp.com/.well-known/openid-configuration),并获取 IdP 的公钥,以验证 JWT。 - RP 验证 JWT 是否由 IdP 的私钥签名。RP 还会将嵌入 JWT 中的一部分声明作为身份验证条件进行检查——这一点很重要,因为 IdP 通常采用多租户架构。这些声明通常还用于将传入身份映射到 RP 上的机器身份。
- 如果所有检查都通过,该 API 调用即可继续执行。有时,对于通过 OIDC 进行身份验证的 API 调用,还会应用额外的授权条件。
Formal 中的 OIDC
我们的 OIDC 集成遵循上述流程,但包含几个关键的额外细节:
- 客户可以为每个集成指定一个 CEL 条件,只有当该条件对给定令牌求值为 true 时,才允许该令牌进行身份验证。此外,OIDC 声明字段也会作为输入项提供给 Permissions,从而让客户能够针对特定的控制平面 API 请求定义更细粒度的授权条件。
- 通过 OIDC 进行身份验证的调用方可以调用的一个端点是
CreateFederationToken,该端点返回一个短期有效的 Connector 访问令牌,其中嵌入了源 OIDC 声明字段。这意味着,工作负载可以将 Connector 用作凭据注入代理,而无需自行存储长期有效的凭据。 - SPIFFE 工作负载也能原生运行:Formal 会像验证其他 OIDC 令牌一样,验证来自 SPIRE 的 JWT-SVID,同时 SPIFFE ID 可供 Policies 和 Permissions 使用。
- OIDC 还可用于对 Formal Endpoint 和 Kubernetes Egress Agent 进行身份验证。
证明人类身份
每个 OIDC 集成都映射到一个机器用户。对于大多数传统机器工作负载来说,这是一个合理的默认模型;但我们关注的许多工作负载并非代表自身行动。例如,CI 作业通常可以归属于推送某次提交的人,云端智能体则往往是因为有人从 Slack 启动才运行。因此,每个集成都可以选择定义一个令牌声明,Formal 会将其解析为一个人类身份;该身份会显示在日志中,并作为策略的输入。
这里涉及的两类身份具有不同类型的信任关系。Formal 可以对机器身份进行加密身份验证,但人类身份仅由下游工作负载声明。我们接受该声明,是因为客户信任作出此声明的系统,并明确配置 Formal 也信任它。
从第一天起,我们就支持这种模式,只不过最初服务于完全不同的用例:许多客户使用 Formal 控制商业智能(BI)工具与其数据源之间的数据库流量。Metabase 等工具对每个数据库只能使用一套凭据,但客户往往会根据登录 Metabase 的用户,对控制措施实施差异化管控。我们最初构建终端用户身份传播,是为了允许 Metabase 在数据库查询中声明人类身份,并让 Formal 据此评估策略。
事实证明,这一模式几乎完全适用于云端编程智能体,尤其是由整个团队共享、任何人都可以从 Slack、Linear 或 GitHub 启动的多人协作型智能体。云端智能体平台无法合理地为每个可能启动智能体的人分别生成一套凭据。不过,它可以签发一个令牌,说明这是哪个智能体会话、正在处理哪个仓库,以及是谁启动了它。我们的客户已经信任其智能体平台作出这些声明,就像他们信任 Metabase 报告当前登录用户一样。因此,客户可以使用同一个 Formal 原语,既在 Metabase 中屏蔽数据库列,也可根据所有者所在团队控制云端智能体对基础设施的访问。
内部使用 OIDC 集成
在 Formal,我们广泛在自己的产品中实践这些功能——我们部署了一个 Connector,位于生产数据库、S3 存储桶和日志集群之前;我们还使用 Formal Endpoint 来监控和控制开发者笔记本电脑上的编程智能体使用情况。我们的生产租户控制平面完全由 Terraform 管理,因此,OIDC 集成最初几个内部用例之一,就是将 Terraform Cloud 对 Formal 控制平面的身份验证方式从长期有效的 API 密钥切换为 OIDC。
我们是少数几个云端编程智能体平台的重度用户,并且一直在逐步将开发工作从笔记本电脑转移到云端沙箱。此前,我们不太愿意让这些云端智能体访问我们的基础设施,因为那意味着分发静态凭据,而且几乎无法进行细粒度限定。
不过,OIDC 提供了解决方案——我们不再需要为编程智能体分配静态凭据,并且可以根据云端智能体的具体信息(例如由谁启动、正在处理哪个 GitHub 仓库)来评估策略。
以下是我们发现的、能够让云端智能体提高效率的一些用例,列表并不完整。
产品分析
很多时候,产品分析工作流依赖来自生产数据库的数据(我们会将其复制到分析数据库)。例如,要衡量新产品对象的采用率,就需要查看哪些客户实际上为该产品对象拥有数据库条目。
在不久之前,这个流程过去必须手动完成——工程师需要手动编写 SQL 查询来实现这些工作流。编程代理既擅长编写 SQL,也擅长解读结果,但我们此前没有一种安全的方式,让它们获得受控访问权限。因此,我们不得不手动运行查询,将结果导出为 CSV,然后再粘贴回代理的上下文中。
然而,有了 OIDC 后,我们现在有了一种将每个云代理会话视为独立身份的方式。借鉴我们帮助 Cursor 的朋友们创建的一种流程,我们为云代理建立了一种机制:向其人类所有者请求批准,以获得对生产数据库的短期访问权限。该流程使用我们的 OIDC 集成,以及其他几个现有的平台原语,包括 Workflows 和 Policy Suspensions。
具体来说,代理可以通过 Slack 告诉人类它究竟为什么需要访问权限,以及需要访问多长时间。一旦人类批准,访问权限就会限定在云代理会话 ID 和所请求的特定数据库范围内。
异步工作流调试
我们的工作流编排提供商没有提供远程 MCP 服务器,因此此前无法在不发放长期有效凭据的情况下让云代理获得访问权限。这限制了我们利用编程代理根据真实运行历史来优化工作流性能的能力。
我们可以将工作流编排器注册为 Formal 资源,并将只读 API 密钥存储为 Native User。使用 OIDC 时,云代理可以像处理上述数据库一样请求短期访问权限并获得批准。请求始终会记录在 Formal 中,并同时归属于启动该工作流的云代理和工程师。
测试云原生功能
Formal 平台支持 AWS、GCP 和 Azure 的多项原生功能。例如,Connector 可以使用 IAM 对数据库和 Kubernetes 集群进行身份验证。此外,控制平面可以与每个云提供商集成,以自动发现资源并将日志发送到存储桶。
在迭代这些功能时,我们现在可以让云代理使用 OIDC,同时向我们的 Formal 开发租户以及某个云提供商上的开发账号进行联合身份验证。这使代理能够确保其代码更改在真实且具有代表性的云环境中按预期运行,从而帮助它完成闭环,而无需依赖脆弱的模拟。
例如,最近我让一个 Cursor 云代理扩展我们的 Kubernetes operator,使其能够使用 OIDC 对 Formal 控制平面进行身份验证(例如使用 Azure Workload Identity)。在开发过程中,我授予该云代理访问一个 Azure 开发账号的权限;它在该账号中部署了一个新的 AKS 集群,以便从其 PR 分支运行 Kubernetes operator。该代理能够通过连接到我们的 Formal 开发租户来验证:当它在集群中创建了一个 FormalPolicy CRD 后,策略已同步到该开发租户——这证明代码更改按预期生效。
结论
当云端代理能够自行完成闭环时,它们的实用价值会大幅提升:它们应当能够收集产品使用数据、调查故障,并在真实环境中验证自己的工作。然而,这不应需要发放可重复使用且长期有效的凭据。
OIDC 帮助我们将单次代理运行建模为独特的工作负载身份。当代理通过 Formal 向外部服务发出请求时,访问权限可以与启动它的工程师、它正在处理的代码仓库,以及发起该请求的具体代理会话相关联。生产环境访问仍保持只读并需要审批,而开发环境中的权限可以更广泛,但不会变成永久权限。
从内部使用中得到的更广泛启示是,代理访问权限应建模为一种临时委派——而不仅仅是把代理视为又一个机器工作负载,再配上一把长期有效的密钥。一旦明确这一区别,让代理访问真实基础设施就会少得多令人担忧,而且实用得多。
来源:Hacker News · formal.ai