# v0.2 OpenFGA 授权与 Admin Access 管理规格 本文是 HWLAB `v0.2` 细粒度资源授权、OpenFGA 接入、管理员 Access WebUI 和同路径 CLI 的长期规格。Keycloak 只回答“用户是谁”;OpenFGA 回答“该用户能不能对该对象做该动作”;`hwlab-cloud-api` 仍是唯一应用层 enforcement point 和授权写入口。 本规格补充 [spec-v02-auth.md](spec-v02-auth.md) 和 [spec-user-access.md](spec-user-access.md):前者定义 Keycloak/Web session/API key 到 `AuthPrincipal` 的认证归一;后者定义用户、session owner、device pod 和工具能力的业务权限口径;本文定义 OpenFGA 如何在 `hwlab-v02` namespace 内以 Kubernetes 原生方式落地,并如何被 Cloud Web 与 `hwlab-cli client` 管理和验证。 实施跟踪 issue 必须记录 spec、GitOps、cloud-api、Cloud Web、CLI、测试、PR/CI/CD 和原入口验收进展。过程记录写 issue 评论,本文只保留稳定目标和验收口径。 ## 在系统中的职责划分 | 组件 | 职责 | | --- | --- | | Keycloak | 身份认证、注册、OIDC issuer;不直接授予 HWLAB 功能权限。 | | `hwlab-cloud-api` | `AuthPrincipal` 恢复、OpenFGA store/model bootstrap、授权 check、授权写 API、enforce 策略、AgentRun transient env 过滤和审计摘要。 | | OpenFGA | `hwlab-v02` namespace 内部 PDP 和 relationship tuple store;只接受 cloud-api 调用,不向浏览器、CLI、AgentRun runner 或公网暴露。 | | HWLAB v0.2 Postgres | 业务对象、用户、session、API key、access_tuples 摘要、OpenFGA store/model 指针和迁移 ledger 的 durable source。 | | `hwlab-cloud-web` | Admin Access 页面和同源 API proxy;不直接调用 OpenFGA。 | | `hwlab-cli client` | Web 等价非视觉授权管理入口;通过 `19666` Cloud Web 同源 path 调 cloud-api,不直连 OpenFGA。 | | AgentRun v0.1 runner | 只消费 cloud-api 根据用户权限注入的短期工具环境;不持有跨用户共享 device-pod key、GitHub token 或 UniDesk SSH token。 | OpenFGA 是 HWLAB 应用层授权基础设施,不是 Kubernetes RBAC、ServiceAccount、NetworkPolicy 或 Keycloak realm role 的替代。普通用户不获得 kubeconfig、内部 Service 直连、OpenFGA token 或 Keycloak admin 权限。 ## Kubernetes 和 GitOps 落点 OpenFGA 固定部署在 `hwlab-v02` namespace,作为稳定外部服务进入 v0.2 GitOps desired state: - `deploy/gitops/g14/runtime-v02/openfga.yaml` 声明 `Deployment`、`Service`、migration `Job` 或 init/migrate step、SecretRef 和 health/readiness。 - `deploy/gitops/g14/runtime-v02/kustomization.yaml` 必须引用 `openfga.yaml`;render 脚本如果有 service inventory 或 artifact catalog,也必须加入 OpenFGA 的外部服务记录。 - OpenFGA Service 只允许 ClusterIP,默认 DNS 为 `hwlab-openfga.hwlab-v02.svc.cluster.local`;不得配置 FRP、公网 Ingress 或 Cloud Web 直连。 - 数据存储使用 Postgres backend;禁止 production/stable runtime 使用 memory backend。可复用 `hwlab-v02-postgres` 实例,但必须使用独立 database/schema/role 和独立 SecretRef,避免和 cloud-api migration ledger 混写。 - 首次部署和版本升级必须执行 OpenFGA migrate;migration 失败时 cloud-api OpenFGA readiness 必须 degraded,不能进入 enforce。 - OpenFGA 鉴权使用 preshared key 或等价内部服务 token SecretRef;Secret 值不得出现在 ConfigMap、日志、issue、trace 或 CLI 默认输出。 - Readiness 使用 OpenFGA health endpoint;cloud-api `/health/live` 汇总 OpenFGA endpoint、store/model status、mode 和 degraded reason,但不输出 token。 OpenFGA 官方 Helm chart 可以作为 YAML 来源,但 `hwlab-v02` desired state 仍以 repo 内 GitOps render 后的 YAML 为准。任何 OpenFGA 镜像、SecretRef、Postgres URL 或 migration 变更都属于运行面/权限高风险变更,必须走 PR、CI/CD 和原入口验收。 ## 授权模型 对象命名必须稳定、可从业务 ID 直接推导,并在日志/trace 中可脱敏展示: | 对象 | 例子 | 说明 | | --- | --- | --- | | 用户 | `user:usr_123` | HWLAB `users.id`,不是 Keycloak `sub`。 | | 系统 | `system:hwlab` | 平台级 admin、access 管理和全局工具授权。 | | Device Pod | `device_pod:device-pod-71-freq` | 服务端 profile authority 对象。 | | Code Agent session | `agent_session:ags_123` | 会话 owner/collaborator/viewer 判定。 | | 工具 | `tool:hwpod`、`tool:unidesk_ssh`、`tool:github_pr`、`tool:trans_cmd` | AgentRun runner 可注入或可调用的功能能力。 | 目标关系模型: ```fga model schema 1.1 type user type system relations define admin: [user] define access_manager: admin define can_manage_users: admin define can_manage_device_pods: admin define can_manage_tools: admin type device_pod relations define owner: [user] define viewer: [user] or owner or admin from system define operator: [user] or owner or admin from system define profile_editor: [user] or owner or admin from system define job_submitter: [user] or operator define admin: admin from system type agent_session relations define owner: [user] define viewer: [user] or owner or admin from system define operator: [user] or owner or admin from system define admin: admin from system type tool relations define can_use: [user] or admin from system define admin: admin from system ``` 实现可以在第一版用等价模型名称和 relation 名称,但必须保持以下能力语义: | 功能 | Check | | --- | --- | | 管理用户和权限 | `user: access_manager system:hwlab` 或 `admin system:hwlab` | | 查看 device pod | `viewer device_pod:` | | 提交 device pod job / 使用 `hwpod` | `operator` 或 `job_submitter device_pod:`,并要求 `can_use tool:hwpod` | | 修改 device pod profile | `profile_editor device_pod:` 或 `admin system:hwlab` | | 查看自己的 Code Agent session | `viewer agent_session:` | | 继续/取消 Code Agent session | `operator agent_session:` | | 注入 UniDesk SSH 透传能力 | `can_use tool:unidesk_ssh` | | 允许 trans 透传 cmd | `can_use tool:trans_cmd`,且仍受 UniDesk route/operation 边界限制 | | GitHub PR/issue 写操作工具 | `can_use tool:github_pr` 或更细工具对象 | `admin/user` 仍保留为 UX 和 bootstrap 角色,但 runtime 授权必须逐步迁移到 OpenFGA check。`users.role='admin'` 必须镜像为 `system:hwlab#admin@user:` tuple;降级 admin 时必须同步删除 tuple。Keycloak realm role、group、claim 不能直接变成 OpenFGA tuple,除非由 HWLAB admin API 明确写入。 ## Cloud API 授权策略 `hwlab-cloud-api` 是唯一 policy enforcement point。所有用户态请求按以下顺序处理: ```text authenticate -> AuthPrincipal -> load domain object -> openfga check -> reason check for mutating device jobs -> execute ``` 配置项: | 配置 | 说明 | | --- | --- | | `HWLAB_OPENFGA_MODE=enforce` | v0.2 正式运行面固定以 OpenFGA 为准;OpenFGA 不可达或写入失败时 fail closed 并返回结构化 blocker。 | | `HWLAB_OPENFGA_API_URL` | 集群内 OpenFGA Service URL。 | | `HWLAB_OPENFGA_AUTHN_TOKEN` 或 SecretRef | cloud-api 调 OpenFGA 的内部 token。 | | `HWLAB_OPENFGA_STORE_ID` / `MODEL_ID` | 可由 env 注入,也可由 cloud-api bootstrap 后写入 Postgres runtime config。 | | `HWLAB_OPENFGA_TIMEOUT_MS` | 单次 check/write 超时;超时必须结构化返回。 | 模式语义: - `enforce` 是唯一正式运行模式:device pod、agent session 和工具能力以 OpenFGA check 为准。OpenFGA 不可达、store/model 未就绪或 check 超时时,高风险写操作 fail closed;低风险只读可以返回 degraded blocker,不得静默放行。 - `off` / `shadow` 不作为 v0.2 runtime 目标路径;旧文档、测试或 render 如再次把它们作为业务 allow source,应优先删除而不是兼容。 tuple 写入必须由 cloud-api admin API 统一完成,并和 Postgres domain state 保持事务级或可恢复一致: - 用户创建、禁用、角色提升/降级时同步写 `users` 和 `system:hwlab` tuples。 - device pod 创建/更新/删除时同步 `device_pods` 和相关 tuples;删除对象时清理 tuple 或标记不可用。 - Code Agent session 创建时写 `agent_session:#owner@user:`;取消/归档不删除 owner tuple,便于审计和 trace 回放。 - API key `scopes_json` 只能作为用户权限的收窄条件,不能授予超过 OpenFGA 的能力。 ## Admin Access API Cloud Web 和 CLI 只能通过 cloud-api 的 admin API 管理授权。第一版 API surface: | 接口 | 说明 | | --- | --- | | `GET /v1/admin/access/summary` | 返回 mode、OpenFGA readiness、storeId/modelId、user/tool/device-pod 数量和最近 mismatch 摘要。 | | `GET /v1/admin/access/users` | 列出用户、role/status、Keycloak 绑定摘要、API key 数量和 effective capability 摘要。 | | `GET /v1/admin/access/users/{userId}` | 返回单个用户的 device pod、agent session 和 tool 权限矩阵。 | | `PATCH /v1/admin/access/users/{userId}` | 更新用户 `role/status`,并同步 OpenFGA admin tuple。 | | `PUT /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}` | 授予 `viewer/operator/profile_editor/job_submitter` 等 relation。 | | `DELETE /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}` | 撤销指定 device pod relation。 | | `PUT /v1/admin/access/users/{userId}/tools/{toolId}/can-use` | 授予工具能力,例如 `hwpod`、`unidesk_ssh`、`github_pr`、`trans_cmd`。 | | `DELETE /v1/admin/access/users/{userId}/tools/{toolId}/can-use` | 撤销工具能力。 | | `POST /v1/admin/access/check` | 管理员调试单次 authorization check;响应必须标明 actor/object/relation/mode,但不得泄漏 token。 | 所有 write API 必须要求当前 actor 具备 `access_manager system:hwlab` 或 admin tuple;普通 `user` 不可调用。响应必须包含结构化 `authorization` 字段:`mode`、`allowed`、`decisionSource`、`storeId`、`modelId`、`relation`、`object` 和 redacted actor。 ## Admin Access WebUI Cloud Web 新增 ActivityRail 顶层入口 `Access`,只对具备 access manager/admin 权限的用户显示。它不是 Keycloak 管理后台,也不放在 Settings 子页里。 页面布局: - 左栏:用户列表、搜索、role/status 筛选、Keycloak 绑定摘要。 - 中栏:选中用户的权限矩阵,按 `Device Pods`、`Agent Sessions`、`Tools`、`Platform` 分组;权限用 checkbox/toggle 表达,危险工具用显式确认。 - 右栏:effective permission 预览、最近变更、OpenFGA mode/readiness、写入 blocker 和保存结果。 交互规则: - 初始加载先调用 `GET /v1/admin/access/summary` 和用户列表;OpenFGA degraded 时页面必须显示 blocker,不允许静默按旧 grant 表展示为通过。 - 修改权限时先在本地形成 pending diff;保存后逐项调用对应 PATCH/PUT/DELETE API,并刷新 effective matrix。 - 页面不暴露 OpenFGA tuple 原文作为主要操作面;需要排障时只在详情中显示 redacted object/relation。 - 普通用户或未登录用户访问 Access route 时显示授权 blocker 或跳登录,不渲染空管理表。 ## 同路径 CLI `hwlab-cli client` 必须提供与 WebUI 同一 API surface 的非视觉入口,默认由 runtime namespace/lane 解析到 `19666` Cloud Web origin: | CLI | Web/API 等价 | | --- | --- | | `client access summary` | `GET /v1/admin/access/summary` | | `client access users list` | `GET /v1/admin/access/users` | | `client access users inspect USER` | `GET /v1/admin/access/users/{userId}` | | `client access users set-role USER --role admin|user` | `PATCH /v1/admin/access/users/{userId}` | | `client access device-pods grant USER POD --relation viewer|operator|profile_editor|job_submitter` | `PUT /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}` | | `client access device-pods revoke USER POD --relation ...` | `DELETE /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}` | | `client access tools grant USER TOOL` | `PUT /v1/admin/access/users/{userId}/tools/{toolId}/can-use` | | `client access tools revoke USER TOOL` | `DELETE /v1/admin/access/users/{userId}/tools/{toolId}/can-use` | | `client access check --user USER --relation REL --object OBJECT` | `POST /v1/admin/access/check` | CLI 输出必须是 JSON,包含 `runtimeEndpoint`、HTTP route、actor 摘要、mode、decision 和变更后的 effective matrix 摘要。它不能直接调用 OpenFGA API,不能手动传 OpenFGA token,不能把 `--base-url 19667` 作为 WebUI 等价验收路径。 ## AgentRun 工具能力边界 Cloud API 在创建 AgentRun command/runner 时必须按 OpenFGA 决策装配 transient env 和工具说明: - 用户没有 `can_use tool:hwpod` 时,不注入 `HWLAB_API_KEY` 给 `hwpod`,也不在 prompt/tools 中声明 device pod 操作可用。 - 用户没有目标 device pod 的 `operator`/`job_submitter` 时,即使拥有 `tool:hwpod`,具体 device pod job 也必须被 cloud-api 拒绝。 - 用户没有 `can_use tool:unidesk_ssh` 时,不注入 UniDesk SSH client token、workspace route 或相关 alias。 - `tool:trans_cmd` 只代表允许通过受控 UniDesk route 调用透传命令;它不绕过 UniDesk CLI 的 route/operation 安全边界,不允许 pod 内任意 Secret 读取或 Kubernetes 写操作。 - GitHub issue/PR 写入能力必须单独由工具对象授权;拥有 Code Agent session 不等于拥有 GitHub 写权限。 ## 测试规格 ## T1 阅读 docs/reference/spec-v02-openfga-authorization.md,然后检查 `deploy/gitops/g14/runtime-v02` 和渲染结果,确认 OpenFGA 以 ClusterIP-only 服务部署在 `hwlab-v02`,使用 Postgres backend、migration job/step 和 SecretRef;`kustomization.yaml` 引用 OpenFGA 资源,且没有 FRP/Ingress 公网暴露。 ## T2 阅读 docs/reference/spec-v02-openfga-authorization.md,然后访问 `GET /health/live` 和 `GET /v1/admin/access/summary`,确认响应显示 `openfga.mode`、readiness、storeId/modelId 摘要和 degraded reason;响应不得包含 OpenFGA token、Postgres URL 或 Secret 值。 ## T3 阅读 docs/reference/spec-v02-openfga-authorization.md,然后用 admin 通过 CLI 或 WebUI 给普通用户授予某个 device pod 的 `viewer` 但不授予 `operator`,确认该用户可以看到 device pod 摘要,但提交 device job 返回 403;再授予 `operator/job_submitter` 后 job 可提交。 ## T4 阅读 docs/reference/spec-v02-openfga-authorization.md,然后撤销普通用户的 `tool:hwpod` 或 `tool:unidesk_ssh`,创建新的 Code Agent session 并检查 trace/runner env 摘要,确认对应工具 alias/env 未注入;尝试调用时返回结构化 authorization blocker。 ## T5 阅读 docs/reference/spec-v02-openfga-authorization.md,然后在 `enforce` 运行面授予和撤销普通用户的 device pod relation,确认 `/v1/device-pods`、status、job 和 Access summary 都按 OpenFGA 结果执行;OpenFGA 写失败时 Admin Access API 必须返回结构化 5xx blocker,不能更新本地 access matrix。 ## T6 阅读 docs/reference/spec-v02-openfga-authorization.md,然后从浏览器打开 Access 页面,确认只有 admin/access manager 可见 ActivityRail 入口;普通用户访问 route 显示授权 blocker。保存一次权限变更后,CLI `client access users inspect USER` 必须看到同一 effective matrix。 ## T7 阅读 docs/reference/spec-v02-openfga-authorization.md,然后在 runtime endpoint locked 环境运行 `hwlab-cli client access summary/users/check/grant/revoke`,确认全部走 Cloud Web 同源 `19666` path,输出 JSON、route、actor、mode、decision 和 effective matrix;不手动传 OpenFGA URL/token,不直连 `19667` 作为最终 Web 等价验收。 ## 规格的实现情况 | 规格项 | 状态 | 说明 | | --- | --- | --- | | OpenFGA 作为 v0.2 内部授权服务 | 目标状态 | 需要 GitOps resource、Postgres backend、migration 和 cloud-api readiness。 | | Cloud API OpenFGA client/bootstrap/check/write | 目标状态 | 需要 `enforce`、store/model 指针、tuple write 和 structured decision。 | | 细粒度 device pod / session / tool 授权 | 目标状态 | 替代旧“device pod grant 即全权限”口径。 | | Admin Access WebUI | 目标状态 | 新增 admin-only ActivityRail 页面,不直接调用 OpenFGA。 | | 同路径 CLI | 目标状态 | 新增 `client access ...`,走 Cloud Web 同源 path。 | | AgentRun 工具注入按用户权限过滤 | 目标状态 | `hwpod`、`unidesk_ssh`、`trans_cmd`、GitHub 写工具都必须独立授权。 |