Files
pikasTech-HWLAB/docs/reference/spec-user-access.md
T
2026-06-05 16:21:04 +08:00

337 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# v0.2 用户和权限管理规格
本文是 HWLAB `v0.2` 用户和权限管理的规格说明。目标是用最少概念支持真实用户使用 code agent session,并让管理员能按用户独立调整 HWPOD/profile、Code Agent session 和工具功能权限,同时避免把用户体系、Kubernetes 租户、设备授权、硬件证据链和审计系统混成一套复杂门禁。
本规格与 [spec-hwpod-harness.md](spec-hwpod-harness.md) 配套:用户和权限规格定义谁可以看见、创建和使用 HWPOD;HWPOD 规格定义当前 `hwpod``hwpod-spec`、node-ops 和硬件执行边界。[spec-device-pod.md](spec-device-pod.md) 只作为旧 API/table 命名的迁移对照。
登录入口、Keycloak OIDC、Web session、CLI API key 和 `AuthPrincipal` 归一见 [spec-v02-auth.md](spec-v02-auth.md)。OpenFGA、Admin Access WebUI 和同路径 CLI 细节见 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md)。本文只定义认证完成后的角色、资源归属、HWPOD capability、tool capability 和 code agent owner 授权;正式用户鉴权只有 Web session 与 CLI/API key 两类。
实施跟踪见 [pikasTech/HWLAB#531](https://github.com/pikasTech/HWLAB/issues/531),原 `docs/plan/v02-multi-user-migration.md` 迁移计划全文已迁入该 issue 评论。
## 在系统中的职责划分
用户和权限管理不是独立微服务,权威实现收敛在 `hwlab-cloud-api`:它消费 [spec-v02-auth.md](spec-v02-auth.md) 产出的 `AuthPrincipal`,负责角色、OpenFGA check/write、用户 API key、hwpod capability、tool capability 和 code agent session owner 校验。`hwlab-cloud-web` 只提供浏览器 UI 和同源代理;HWPOD 执行节点只执行受控硬件语义;AgentRun v0.1 只消费 cloud-api 按用户权限注入的 actor/session/hwpod/tool 上下文,不成为 HWLAB 用户权限 authority。
Postgres 是用户、session、业务对象和迁移 ledger 的持久化边界;OpenFGA 是细粒度授权关系与授权判定边界。Kubernetes namespace、ServiceAccount、Service 直连和 gateway route 都不能替代用户权限模型;普通用户不获得 kubeconfig、内部 Service 直连能力、OpenFGA token 或长期 Secret。
`v0.2` 本地 bootstrap 管理员账号固定为 `admin`,默认登录密码固定为 `hwlab2026`,只用于空库初始化。Keycloak 接入后的目标 Web 登录以 OIDC 为准,CLI 以 `HWLAB_API_KEY` 为准。
运行时仍只通过 `hwlab-v02-bootstrap-admin/password-hash` SecretRef 注入本地 bootstrap password hashPostgres、API 响应、日志、CLI session 和文档不得保存或输出 password hash、session token 原文或 Secret 值。
如果 live Secret 需要重建或旋转,必须保持目标 OIDC/Web session 与 API key 登录链路可用。
## 当前实现状态
- 当前 v0.2 runtime 的权限相关组件包含 `hwlab-cloud-api``hwlab-cloud-web`、HWPOD 相关实现、OpenFGA、v0.2 Postgres 和 Keycloak 外部 issuer。`hwlab-cloud-api` 是应用层用户身份恢复、OpenFGA check/write、Admin Access 写入、hwpod/profile/job 和 Code Agent owner 校验的收口点。
- 用户、session、API key、account workspace、HWPOD profile、HWPOD job 和访问摘要保存在 v0.2 Postgres;细粒度 relation 的判定 authority 是 OpenFGA tuple。Postgres 中的摘要或缓存不能在 OpenFGA 不可用时变成独立 allow source。
- 当前授权管理入口是 Admin Access API、Cloud Web Access 页面和同路径 `hwlab-cli client access ...`。管理员或具备 `access_manager` 的用户通过这些入口调整 role/status、HWPOD relation 和 tool capability。
- 迁移期 hwpod/profile 的创建和修改仍通过 `POST /v1/admin/device-pods``PUT /v1/admin/device-pods/{devicePodId}``profile_editor`/admin 授权完成;这些 path 名称属于实现残留。执行节点只执行 cloud-api 已授权的内部请求,不拥有用户权限判断或 profile 修改权。
- AgentRun runner 只能收到 cloud-api 按当前 Code Agent session owner 装配的用户级 `HWLAB_API_KEY` 和已授权工具面。`trans_cmd` 是独立 tool capability,表示允许进入受控 UniDesk passthrough 命令面,不授予 Kubernetes Secret、OpenFGA token、残留执行链路内部 token 或任意控制面写权限。
## 当前状态收敛规则
- 代码、测试和长期文档只能表达当前 Web session/API key/OpenFGA/Admin Access 权限系统。发现绕过当前 `AuthPrincipal -> OpenFGA/Admin Access` 判定的授权入口、共享用户绕过凭据、兼容写分支或只为已移除路径存在的断言时,处理方式是删除或改写为当前合同。
- 不把历史授权路径迁移成 feature flag、legacy mode、兼容表、负向测试清单或新增门禁。需要证明当前状态时,优先写当前 allow/deny 行为、当前 authority 和当前用户入口验收。
- issue/PR 评论可以保留排障证据;`docs/reference/` 只保留当前状态、目标状态和稳定判定标准,不保存过程流水账或已移除对象名称清单。
## 规格目标
- 只保留两类角色:`admin``user`
- `code agent session` 直接归属于创建它的用户;普通用户只能查看、继续和取消自己的 session。
- HWPOD/profile 由 `admin` 或被授予 `profile_editor` 的用户管理;普通用户只有在被授权后才能看到、操作或提交对应 HWPOD job。
- HWPOD 授权按 `viewer``operator``profile_editor``job_submitter` 等 OpenFGA relation 表达。
- 工具能力必须独立授权,例如 `hwpod``unidesk_ssh``trans_cmd` 和 GitHub 写工具;拥有 Code Agent session 不等于拥有这些工具。
- MVP 不新增产品级 `audit_events` 用户审计表,也不把用户权限依赖到 audit。现有硬件 trace/evidence/audit 字段属于硬件闭环证据,不是多用户权限模型的一部分。
- 强副作用 HWPOD job 只额外要求业务 `reason`;设备互斥由 executor、gateway 和硬件 host 串行化或返回 blocker,不进入用户权限模型。
- 普通用户不获得 Kubernetes 用户、kubeconfig、namespace 管理权或直接访问残留执行 Service 的权限;所有用户权限判断在 cloud-api 应用层完成。
- v0.2 权限数据必须与 `hwlab-dev`/`hwlab-prod` 运行数据隔离。优先在 `hwlab-v02` namespace 内使用独立 Postgres StatefulSet/PVC;若未来显式复用共享 Postgres 实例,也必须使用独立 database 或 schema、独立 Secret 和独立 migration ledger,不得直接复用 `hwlab-dev` 的 pgdata。
## 简化边界
| 模块 | 不采用的复杂方案 | v0.2 规格方案 |
| --- | --- | --- |
| 用户角色 | `platform_admin``device_admin``developer``viewer` | `admin``user` |
| 用户组 | `groups``group_members` | 不引入 |
| 项目隔离 | `projects``project_members` | 不引入 |
| Code Agent 会话 | `ownerUserId + projectId + sessionId` | `owner_user_id + session id` |
| HWPOD 管理 | 平台管理员和设备管理员分工 | `admin` 统一管理 |
| HWPOD 授权 | 可按 group/project 授权 | 第一版只按具体 `user_id` 授权,relation 由 OpenFGA 表达 |
| 设备权限粒度 | `io.read``io.write` 等硬件寄存器级 capability | `viewer``operator``profile_editor``job_submitter` 等产品级 relation |
| Viewer | 单独只读角色 | 不引入 |
| Audit | 独立用户审计表 | 不引入 |
| Lease | 权限和互斥可能混用 | 只作为设备互斥锁 |
## 表结构
v0.2 多用户实现复用 cloud-api 和现有 Postgres runtime store,不新增独立用户管理微服务。推荐新增一个 `0002_multi_user_access_v1` 数据库迁移,保持现有 `0001_cloud_core_skeleton.sql` 的硬件 runtime 表不被误用为用户权限表。
### `users`
用户身份和角色 source of truth。
```sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL DEFAULT '',
role TEXT NOT NULL CHECK (role IN ('admin', 'user')),
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
password_hash TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
- bootstrap 阶段必须至少有一个 `admin`
- `password_hash` 只用于 v0.2 本地 bootstrap 账号;OIDC identity 扩展字段、`api_keys` 表和 API key 规则见 [spec-v02-auth.md](spec-v02-auth.md)。接入 OIDC 后仍保留 `users.id``role` 和授权表稳定,不把外部 IdP subject 直接暴露给业务授权。
- `disabled` 用户不能创建 session、继续 session 或使用 HWPOD。
### `user_sessions`
浏览器 server session/cookie 的持久状态。cookie 中只保存不可逆 session token;数据库保存 token hash。
```sql
CREATE TABLE IF NOT EXISTS user_sessions (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
session_token_hash TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
revoked_at TEXT
);
```
- `/auth/session`、OIDC callback、API key 认证和 `/auth/logout` 的最终 authority 是 cloud-api。`/v1/auth/session``/v1/users/me``/v1/access/status``/v1/setup/status` 只作为同一 authority 的 REST 状态入口。cloud-web 可以保留同名浏览器路由,但只能作为静态 UI 或代理层。
- logout 是设置 `revoked_at`,不是仅删除浏览器本地状态。
### `agent_sessions`
复用现有 `agent_sessions` 作为 code agent session 归属记录,不再新增同义的 `code_agent_sessions` 表。v0.2 迁移应在现有表上增加 owner 和 chat/session 绑定字段:
```sql
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS owner_user_id TEXT REFERENCES users(id);
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS conversation_id TEXT;
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS thread_id TEXT;
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS last_trace_id TEXT;
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS session_json TEXT NOT NULL DEFAULT '{}';
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS updated_at TEXT;
CREATE INDEX IF NOT EXISTS idx_agent_sessions_owner ON agent_sessions(owner_user_id);
CREATE INDEX IF NOT EXISTS idx_agent_sessions_conversation ON agent_sessions(conversation_id);
```
- 新建 code agent session 必须写入 `owner_user_id`
- 历史 ownerless session 在迁移时可以一次性归属 bootstrap `admin` 或直接标记为 `expired`;迁移完成后不再允许 ownerless 活跃 session。
### `device_pods`
迁移期 hwpod/profile 管理表;字段名仍是实现残留,目标概念以 [spec-hwpod-harness.md](spec-hwpod-harness.md) 为准。profile/spec 必须由 `admin` 或具备 `profile_editor` 的用户通过 cloud-api 管理,不能由 code agent 本地 `.device-pod/` 文件决定。
```sql
CREATE TABLE IF NOT EXISTS device_pods (
id TEXT PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
profile_json TEXT NOT NULL DEFAULT '{}',
profile_hash TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
`profile_json` 中的 gateway route、host workspace、probe UID、串口端口和 host CLI 都是服务端权威字段;普通用户响应只能看到脱敏 profile 摘要和 `profile_hash`
## 权限矩阵
| 操作 | `admin` | `user` |
| --- | --- | --- |
| 管理用户和权限 | 可以;应镜像为 `system:hwlab#admin``access_manager` | 只有被授予 `access_manager` 才可以 |
| 创建自己的 code agent session | 可以 | 可以 |
| 查看、继续、取消自己的 code agent session | 可以 | 拥有该 session 的 `viewer/operator` 时可以 |
| 查看、取消别人的 code agent session | 可以 | 需要该 session 的显式 relation |
| 创建、更新、删除 HWPOD/profile | 可以 | 需要目标 HWPOD 的 `profile_editor` |
| 给用户授权或撤销 HWPOD/tool | 可以 | 需要 `access_manager` |
| 查看 HWPOD | 可以查看全部 | 需要目标 HWPOD 的 `viewer` 或更高 relation |
| 使用 HWPOD 的 workspace/debug/io 能力 | 可以使用全部 | 需要 `tool:hwpod#can_use` 且目标 HWPOD 具备 `operator/job_submitter` |
| 提交强副作用 HWPOD job | 必须填写 reason | 被授权后仍必须填写 reason |
| 调用 UniDesk SSH / trans cmd / GitHub 写工具 | 可以,但仍受工具边界约束 | 需要对应 `tool:*#can_use` |
## 请求链路
cloud-api 每个用户态请求都按同一顺序处理:
```text
authenticate -> actor -> authorize(actor, action, resource) -> reason check for mutating device jobs -> execute
```
### 登录、API key 和 session 恢复
```text
browser
-> Keycloak OIDC login/callback
-> cloud-api auth controller
-> users issuer+sub/status/role mapping
-> user_sessions insert token hash
-> Set-Cookie httpOnly sameSite
-> browser
cli
-> Authorization: Bearer hwl_live_...
-> cloud-api api_keys lookup
-> users status/role check
-> AuthPrincipal(authMethod='api-key')
```
登录、Keycloak issuer、CLI API key 和 24 小时 Web session 规则见 [spec-v02-auth.md](spec-v02-auth.md)。资源授权模块只消费已经恢复出的 actor/AuthPrincipal。`/auth/session` 使用 cookie 查 `user_sessions`,再查 `users` 得到 `actor`API key 认证直接从 `api_keys -> users` 得到同一 actor。`/auth/logout` 标记 `user_sessions.revoked_at`。Keycloak access token、refresh token、realm role 和内部 service token 都不能绕过这里的 actor 恢复与资源授权。
### admin 创建用户
```text
browser admin UI
-> cloud-web proxy
-> cloud-api POST /v1/admin/users
-> authenticate admin
-> insert users(role='user')
-> return redacted user record
```
普通用户请求该接口必须返回 `403`。响应不得返回 `password_hash`、session token 或 Secret 值。
### admin 授权 HWPOD
```text
browser admin Access UI
-> cloud-api PUT /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}
-> authenticate admin
-> validate users.status='active'
-> validate device_pods.status='active'
-> write OpenFGA tuple
-> return effective permission matrix
```
撤销授权走 `DELETE /v1/admin/access/users/{userId}/device-pods/{devicePodId}/{relation}`。管理员只能通过 Admin Access API 写入或删除 OpenFGA relation。
需要给用户开通 HWPOD 或工具能力时,统一使用 Admin Access API、Admin Access WebUI 或同路径 `hwlab-cli client access ...`,并按具体 relation 或 `tool:*#can_use` 写入 OpenFGA tuple。
### 用户列出 HWPOD
```text
browser or code agent tool
-> cloud-api GET /v1/device-pods
-> authenticate actor
-> if admin/access manager: list all active device_pods
-> if user: check OpenFGA viewer/operator relation for each active device_pod
-> return visible HWPOD summaries
```
未授权普通用户看到空列表或对单个未授权 HWPOD 收到 `403`;不得回退到 fake default HWPOD。
### 用户创建或继续 code agent session
```text
browser
-> cloud-api POST /v1/agent/chat
-> authenticate actor
-> find or create agent_sessions(owner_user_id=actor.id, conversation_id)
-> run code agent turn
-> persist last_trace_id/thread_id/session_json
-> return 202 trace/result polling pointer
```
`GET /v1/agent/chat/result/{traceId}``GET /v1/agent/chat/trace/{traceId}``POST /v1/agent/chat/cancel` 必须通过 `agent_sessions.owner_user_id` 校验 owner`admin` 可跨用户查看和取消。
### code agent 使用 HWPOD
```text
code agent turn
-> cloud-api device operation route
-> authenticate actor from owning session or user API key in runner
-> verify agent_sessions.owner_user_id == actor.id
-> authorize OpenFGA tool:hwpod and device_pod relation
-> require reason for mutating operations
-> cloud-api -> HWPOD execution path
-> gateway/device-host-cli/hardware path
```
code agent prompt、runner 或 worker 不得直接绕过 cloud-api 调用残留执行 Service。残留执行服务只信任来自 cloud-api 的内部调用,不做最终用户权限判断。Cloud API 给 AgentRun runner 注入 `hwpod`、UniDesk SSH、`trans_cmd` 或 GitHub 写工具前,必须先按 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) 检查对应 `tool:*#can_use`
## 内部架构
`hwlab-cloud-api` 内部应按 auth/session/API key、authorization、agent session owner、HWPOD relation 和 admin API 模块分层。所有模块共享同一 Postgres runtime store 和 migration ledger,避免拆出早期 `hwlab-user-api` 造成跨服务一致性成本。
`user_sessions` 存浏览器 session token hash`api_keys` 存映射到用户的 CLI/runner API key`agent_sessions.owner_user_id` 绑定 Code Agent session;迁移期 `device_pods` 存 HWPOD profile authorityOpenFGA tuple 表示用户对 HWPOD、agent session 和工具的细粒度能力。cloud-api 调用残留执行服务使用内部 service token,该 token 不参与用户鉴权、不写入 runner env,也不产生 actor。
## API 接口说明
| 接口 | 说明 |
| --- | --- |
| `GET /auth/oidc/login``GET /auth/oidc/callback` | Web 登录入口;按 [spec-v02-auth.md](spec-v02-auth.md) 接入 Keycloak 并写入 24 小时 `user_sessions` token hash。 |
| `GET /auth/session` | 从 cookie 恢复 actor、role 和 session 状态;API key actor 摘要见 `/v1/users/me`。 |
| `GET /v1/auth/session``GET /v1/users/me``GET /v1/access/status``GET /v1/setup/status` | REST 状态和兼容入口;不得读取或返回 password hash、session token 原文或 Secret 值。 |
| `GET/POST /v1/api-keys...` | 用户 API key 管理入口;CLI `HWLAB_API_KEY` 规则见 [spec-v02-auth.md](spec-v02-auth.md)。 |
| `POST /v1/setup/first-admin` | 仅当 `users` 表为空时创建第一个 `admin` 并建立 session;可选 `devicePod``devicePods[]` 一次性种下首批服务端权威 profile 并授权给首个 admin;一旦已有用户必须返回 `409 setup_already_completed`。该入口不读取 Kubernetes Secret,不替代正常 admin API。 |
| `POST /auth/logout` | 设置 `revoked_at`,撤销当前 browser session。 |
| `POST /v1/admin/users` | admin 创建用户,响应不得返回 `password_hash` 或 token。 |
| `POST /v1/admin/device-pods``PUT /v1/admin/device-pods/{devicePodId}` | admin 管理 HWPOD profile authorityURL path 是迁移期实现名。 |
| `GET/PATCH/PUT/DELETE /v1/admin/access...` | admin Access API,读写 OpenFGA 授权、tool capability、role/status 和 effective matrix;见 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md)。 |
| `GET /v1/device-pods` 和 HWPOD 操作 API | 按 actor role、OpenFGA relation 和 tool capability 过滤可见/可用 HWPOD。 |
| `POST /v1/agent/chat` 及 result/trace/cancel | 必须校验 `agent_sessions.owner_user_id`;admin 可跨用户查看和取消。 |
`POST /v1/setup/first-admin` 的 HWPOD/profile 初始化只用于空库首次进入系统,不能作为长期 profile 管理入口。每个 seed 必须包含迁移期 `devicePodId` 和 object `profile`cloud-api 会写入 `device_pods.profile_json/profile_hash`,并通过 Admin Access/OpenFGA relation 授权给首个 admin。响应只能返回脱敏 profile、profileHash 和授权摘要,不得返回 `gatewaySessionId``hostWorkspaceRoot`、password 或 session token 原文。
## 微服务设计
v0.2 不新增独立用户管理微服务。Keycloak 是独立身份提供方,不是 HWLAB 应用层授权服务;OpenFGA 是内部授权 PDP,不对用户暴露独立 API;用户映射、session/API key 消费、OpenFGA check/write、HWPOD relation、tool capability 和 code agent owner 校验全部放在 `hwlab-cloud-api` 内,理由是:
- 当前权限入口必须和 `/v1/agent/*``/v1/device-pods/*`、AgentRun transient env 注入和 runtime store 保持强一致,拆出新的 HWLAB 用户微服务会增加网络、部署、Secret、迁移和一致性成本。
- cloud-api 已经是 `/v1/agent/*`、迁移期 `/v1/device-pods/*` 和 runtime store 的统一入口,最适合做应用层授权收口。
- 后续若出现组织、计费、批量用户导入或跨产品用户中心,再把 cloud-api 内的 user/auth 模块抽成 `hwlab-user-api`;抽服务前接口和表结构仍以本文和 [spec-v02-auth.md](spec-v02-auth.md) 为准。
各服务职责如下:
| 服务 | v0.2 职责 |
| --- | --- |
| `hwlab-cloud-web` | Keycloak 登录入口、普通用户工作台、API key 管理入口和 Admin Access 授权 UI;浏览器 `/auth/*``/v1/admin/access*` 由 cloud-web 代理到 cloud-api。 |
| `hwlab-cloud-api` | 用户映射、Web session/API key 消费、OpenFGA 授权、HWPOD relation、tool capability、code agent owner 校验和对 HWPOD 执行路径的受控转发。 |
| OpenFGA | `hwlab-v02` 内部稳定授权服务,只接受 cloud-api 调用,不向普通用户或公网暴露。 |
| AgentRun v0.1 runner | 执行 code agent session;接收 cloud-api 提供的 owner/session/HWPOD 上下文方便观测,但不作为最终权限 authority。 |
| HWPOD execution path | 执行受控硬件语义;不保存用户权限,不直接面向浏览器或普通用户 session Pod。 |
| `hwlab-edge-proxy` | 公网/FRP 入口和 HTTP 转发;不做业务权限,只转发 cookie/header,不注入伪 actor。 |
| Postgres | v0.2 用户、session、授权、HWPOD/profile 和既有 runtime durable state。 |
## Kubernetes 落点
Kubernetes 只做运行时隔离和资源兜底,不承载 HWLAB 用户权限模型:
- `v0.2` 使用 `hwlab-v02` namespace,不按用户创建 namespace。
- 普通用户不直接持有 Kubernetes RBAC、ServiceAccount token 或 kubeconfig。
- `hwlab-v02` 优先拥有独立 Postgres StatefulSet/PVC,例如 `data-hwlab-v02-postgres-0`;不得把 `hwlab-dev/data-hwlab-g14-postgres-0` 当作 v0.2 权限数据源。
- code agent worker、session Pod/PVC/Job 必须带稳定 label,例如 `hwlab.pikastech.local/owner-user-id``hwlab.pikastech.local/session-id`
- 迁移期执行工作负载如果仍承载多 HWPOD,必须带能映射 HWPOD/profile 的稳定 label;普通用户不以该 label 作为授权来源。
- code agent 到 HWPOD 的访问应收敛到 `code agent -> cloud-api -> HWPOD execution path`,避免普通 session Pod 直接调用残留执行 Service 绕过应用层授权。
- Keycloak 按 [spec-v02-auth.md](spec-v02-auth.md) 作为独立 `keycloak` namespace 的外部身份源接入;OpenFGA 按 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) 部署在 `hwlab-v02` namespace 作为内部授权服务;Kubernetes 租户隔离第一轮仍不引入 Dex、oauth2-proxy、Capsule、vCluster、Kyverno 或 service mesh。
当前态、差距和迁移步骤已迁入 [pikasTech/HWLAB#531](https://github.com/pikasTech/HWLAB/issues/531) 评论。
## 测试规格
## T1
阅读 docs/reference/spec-user-access.md 和 docs/reference/spec-v02-auth.md,然后用 Web session 或 `HWLAB_API_KEY` 访问 `/v1/users/me`,确认响应包含 actor、role 和 active auth method,不包含 password hash、session token 原文、完整 API key 或 Secret 值。空库 bootstrap fallback 可额外验证 `/auth/login`,但不得把它作为目标登录体验。
## T2
阅读 docs/reference/spec-user-access.md 和 docs/reference/spec-v02-openfga-authorization.md,然后用 cli 手动测试以下内容:用 admin 给普通用户授予某个 HWPOD 的 `viewer` 但不授予 `operator/job_submitter`,确认普通用户只能看到 HWPOD 摘要,提交 job 返回 403;授予 `operator/job_submitter` 后 job 可提交;撤销 relation 后同一用户不能再看到或使用该 HWPOD。
## T3
阅读 docs/reference/spec-user-access.md,然后用 cli 手动测试以下内容:普通用户创建 Code Agent session 后,只能读取、继续和取消自己的 trace/result;另一个普通用户访问该 session 必须失败,admin 可以跨用户查看或取消。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| admin/user 两角色模型 | 部分实现 | cloud-api 已实现 `/auth/*`、bootstrap admin、admin/user 创建和 admin-only 路由。 |
| `users``user_sessions`、OpenFGA relation 和 job 表 | 部分实现 | access-control bootstrap 覆盖 users、sessions、迁移期 device_pods、access_tuples 和 jobsHWPOD 强副作用 job 已接入 reason 校验,真实硬件执行仍依赖 gateway/device-host-cli 在线。 |
| Code Agent owner 绑定 | 已实现 | 已在 `agent_sessions` 写入 `owner_user_id`、conversation/thread/trace 和脱敏 session evidencetrace/result cache 也按 owner/admin 限制访问。 |
| OpenFGA 细粒度授权模型 | 核心已实现/持续约束 | v0.2 enforce runtime 已通过 Admin Access API 和同路径 CLI 管理 HWPOD relation 与 tool capability;后续扩展仍必须按 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) 保持同一 authority。 |
| 不用 Kubernetes 表达用户权限 | 已实现/持续约束 | 规格明确禁止普通用户持有 kubeconfig 或直连 Service 权限。 |