Agent 准入策略
谁能用 Cloud Agent,由网关说了算。Cloud Agent 用用户 ID 加签发方(issuer)来标识一个账号;每个受保护的所有者/管理员请求,以及 /v1/session/me,都会再向网关确认一次。
要拿到最新的判定,Agent 会带上自己的 dispatch 凭据,通过 HTTP 请求 GET /internal/users/{user_id}/agent-access。响应里有 user_id、allowed 和 permissions。账号角色和个人资料字段都不参与授权判断。
权限与请求检查
权限 |
允许访问的范围 |
|---|---|
|
所有者 API,每个资源仍要单独检查所有权 |
|
经网关授权的管理操作,包括 runner 主机相关的 API;必须同时拥有 |
allowed 必须和有没有 agent.use 保持一致。权限列表不能重复,只能用认得的取值,并且有 agent.admin 就必须有 agent.use。如果判定针对的是另一个用户,或者响应格式不对、前后矛盾,一律不放行。
按最初上线时记录的 FreeInference 策略,这两项权限都授予状态正常的管理员账号。其他部署可以注册自己的策略,放行别的账号,不用改 Agent。没有注册任何策略的中立网关会一律拒绝。请在你运维的那套部署里核实实际策略;本仓库只负责执行网关给出的判定。
状态正常、但没有 Agent 权限的账号仍然可以登录,登录后看到的是"需要访问权限"页面。不存在或已停用的账号无法建立会话。已有的会话在每个受保护的所有者/管理员请求和 /v1/session/me 上都会重新检查权限。所有者请求可以沿用网关 10 秒内给出的放行结果(AGENT_ACCESS_ALLOW_TTL_S):打开一个页面会同时发出一批所有者请求,同一账号并发的查询只调用一次网关。只有"放行"会被缓存;拒绝、账号不存在、网关不可用这几种情况,每个请求都会重新问一遍。登录、/v1/session/me、管理接口,以及轮次开始时的检查,都不用缓存的放行结果;而且只要 /v1/session/me 收到一次拒绝,缓存的放行结果立刻作废。会话响应里带有 agent_access: {allowed, permissions}。如果没有这个字段(包括旧版后端返回的响应),Web 应用会拒绝访问。
这些检查管的是每个请求能不能进来。策略变了,并不会自动关掉已经打开的流,也不会取消正在跑的任务。所有者的流(任务事件、账号的变更推送、终端输出)在打开时检查一次,大约 15 分钟后自动结束。客户端会从断开的地方重新连接,这个新请求要再检查一次,所以权限被收回或会话过期后,流最多在这个时间内停止。Agent 不在会话里存权限,所有者请求缓存的放行结果最多保留 10 秒。不过,网关自己的账号缓存在过期之前,可能还看不到直接改数据库造成的变化;通过网关正常的账号管理操作修改,则会让这份缓存立即失效。
出错时的表现
情况 |
结果 |
|---|---|
策略明确拒绝 |
受保护的 API 拒绝访问;状态正常的账号可以看到"需要访问权限"页面 |
网关返回 403,类型为 |
拒绝新的登录;已有会话收到"拒绝访问"的判定 |
缺少身份配置、缺少访问端点,或 dispatch 凭据被拒绝 |
HTTP 503,类型为 |
网络或服务端故障、判定无法解析,或者没有类型的 403、代理返回的 403 |
HTTP 503;等网关恢复后重试 |
登录流程的状态已过期或无效 |
回到登录页,重新走一遍登录流程 |
/me 无法确认当前的访问权限时,Web 应用会先把受保护的内容藏起来,并提供重试按钮,不强制重新登录。如果回调时服务出了故障,会回到公开的登录页,带上原来要去的地址和一个重试按钮,并清掉已经用过的登录流程 cookie。配置错误会提示运维人员修正部署。/me 返回的数据格式不对时,有专门的说明。没有配置 Web 跳转地址时,API 就直接返回 HTTP 错误。未经验证的状态和用过的授权码,永远不会再次使用。
基础设施凭据
即使网关不可用,ADMIN_TOKEN 也能单独授权基础设施管理。值班用的 dispatch 凭据在创建任务前,还必须通过所配置服务账号的 agent.use 检查;它查询和取消任务的权限始终保留,方便清理。主机/worker 凭据、attempt 租约栅栏和所有者检查是彼此独立的几道控制。
会话只携带最少的身份信息。cookie 里有什么、签发方如何绑定、升级时有哪些限制,见最小身份。
验证网关契约
schema 在 internal-lookups.openapi.yaml。本地测试会拿其中的错误类型去核对 Agent 客户端和权限解析器。要通过 HTTP 验证一个隔离的、或者其他已获授权的网关,运行:
# Set both values securely in the environment before running; never commit them.
export AGENT_CONTRACT_GATEWAY_URL
export AGENT_CONTRACT_DISPATCH_TOKEN
uv run pytest -q -m external tests/external/test_gateway_access_contract.py
这个探测会查询一个随机命名、实际不存在的账号,然后拿真实返回的 403 去对照 schema 和调用方。它不创建账号,也不打开任何一方的数据库。它需要显式提供网关凭据,默认的 CI 不会跑它。以前的验证是在回环 HTTP 上,用一个隔离的空存储夹具,跑了 c7987680(hybridInference#1408)里生产网关的路由和错误处理;那次并没有验证线上部署。
上线步骤
一套部署第一次改用基于权限的访问控制时:
在网关上注册好策略。
把网关的策略钩子和访问端点,连同兼容的发行版前后端产物一起发布。分别用一个已放行的账号、一个状态正常但没有权限的账号,以及一个不存在或已停用的账号验证一遍。确认部署的代理能把内部访问路径转发到正确的地方。
Agent 后端和 Web 应用一起发布。检查全新登录、已有会话、所有者 API 和管理功能。
没有访问端点的网关不兼容,受保护的请求会一律失败并拒绝访问。不要为了绕过这一点加基于角色的兜底逻辑。改用最小身份之后,要遵守它的升级与回滚限制:旧版 Agent 不会校验数据库里的签发方,而带有个人资料的旧 cookie 需要重新登录。光改源代码不会更新固定的镜像版本,也不会部署任何一个服务。
实现代码在 agent_access.py、deps.py 和 session.py。