Files
pikasTech-HWLAB/docs/reference/spec-user-access.md
T
2026-05-28 20:57:22 +08:00

336 lines
17 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 和管理员分配 device pod 权限,同时避免把用户体系、Kubernetes 租户、设备授权、硬件证据链和审计系统混成一套复杂门禁。
本规格与 [spec-device-pod.md](spec-device-pod.md) 配套:用户和权限规格定义谁可以看见、创建和使用 device poddevice-pod 规格定义 profile authority、REST/job 和硬件执行边界。
## 在系统中的职责划分
用户和权限管理不是独立微服务,权威实现收敛在 `hwlab-cloud-api`:它负责登录、session、角色、device pod grant、device lease 和 code agent session owner 校验。`hwlab-cloud-web` 只提供浏览器 UI 和同源代理;`hwlab-device-pod` 只执行设备语义;`hwlab-agent-mgr``hwlab-agent-worker` 和 Code Agent runtime 只能消费已经由 cloud-api 判断过的 actor/session/device 权限。
Postgres 是该规格的数据持久化边界。Kubernetes namespace、ServiceAccount、Service 直连和 gateway route 都不能替代用户权限模型;普通用户不获得 kubeconfig、内部 Service 直连能力或长期 Secret。
## 规格目标
- 只保留两类角色:`admin``user`
- `code agent session` 直接归属于创建它的用户;普通用户只能查看、继续和取消自己的 session。
- `device pod``admin` 管理;普通用户只有在被 `admin` 授权后才能看到和使用对应 device pod。
- device pod 授权不拆分 `read``operate` 或 capability;授权关系存在即代表该用户拥有该 device pod 的完整使用权限。
- MVP 不新增产品级 `audit_events` 用户审计表,也不把用户权限依赖到 audit。现有硬件 trace/evidence/audit 字段属于硬件闭环证据,不是多用户权限模型的一部分。
- `device lease` 可以保留,但它只解决物理设备并发互斥,不表达用户权限。
- 普通用户不获得 Kubernetes 用户、kubeconfig、namespace 管理权或直接访问 device pod 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` |
| Device Pod 管理 | 平台管理员和设备管理员分工 | `admin` 统一管理 |
| Device Pod 授权 | 可按 group/project/user 授权 | 只按具体 `user_id` 授权 |
| 设备权限粒度 | `device.view``io.read``io.write``debug.reset` 等 capability | 授权即全权限 |
| 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 本地账号;未来接 OIDC 时仍保留 `users.id``role` 和授权表稳定,不把外部 IdP subject 直接暴露给业务授权。
- `disabled` 用户不能创建 session、继续 session 或使用 device pod。
### `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``/auth/login``/auth/logout` 的最终 authority 是 cloud-apicloud-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`
设备能力单元的管理表;正式 profile authority 和执行语义以 [spec-device-pod.md](spec-device-pod.md) 为准。profile 定义 device pod,因此 profile 必须由 `admin` 通过 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`
### `device_pod_grants`
普通用户对 device pod 的授权关系;存在即全权限。
```sql
CREATE TABLE IF NOT EXISTS device_pod_grants (
device_pod_id TEXT NOT NULL REFERENCES device_pods(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_by_admin_id TEXT NOT NULL REFERENCES users(id),
created_at TEXT NOT NULL,
PRIMARY KEY (device_pod_id, user_id)
);
```
- 不包含 `capability``scope``expires_at`
- 撤销授权就是删除对应行;如果用户仍持有该 device pod 的活动 lease,撤销流程必须先释放或标记失效 lease。
### `device_leases`
设备互斥锁;不表达权限。
```sql
CREATE TABLE IF NOT EXISTS device_leases (
device_pod_id TEXT PRIMARY KEY REFERENCES device_pods(id) ON DELETE CASCADE,
holder_session_id TEXT NOT NULL REFERENCES agent_sessions(id) ON DELETE CASCADE,
holder_user_id TEXT NOT NULL REFERENCES users(id),
lease_token_hash TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
released_at TEXT
);
```
- 下载、复位、长时间采样、串口独占等会占用真实物理设备的操作必须先拿 lease。
- 读短状态可以不拿 lease,但仍必须通过 device grant 授权。
## 权限矩阵
| 操作 | `admin` | `user` |
| --- | --- | --- |
| 管理用户 | 可以 | 不可以 |
| 创建自己的 code agent session | 可以 | 可以 |
| 查看、继续、取消自己的 code agent session | 可以 | 可以 |
| 查看、取消别人的 code agent session | 可以 | 不可以 |
| 创建、更新、删除 device pod | 可以 | 不可以 |
| 给用户授权或撤销 device pod | 可以 | 不可以 |
| 查看 device pod | 可以查看全部 | 只能查看被授权的 device pod |
| 使用 device pod 的 workspace/debug/io 能力 | 可以使用全部 | 只能使用被授权的 device pod |
| 获取 device lease | 可以 | 只能对被授权的 device pod 获取 |
## 请求链路
cloud-api 每个用户态请求都按同一顺序处理:
```text
authenticate -> actor -> authorize(actor, action, resource) -> optional lease check -> execute
```
### 登录和 session 恢复
```text
browser
-> cloud-web /auth/login
-> cloud-api auth controller
-> users password_hash/status/role check
-> user_sessions insert token hash
-> Set-Cookie httpOnly sameSite
-> browser
```
`/auth/session` 使用 cookie 查 `user_sessions`,再查 `users` 得到 `actor``/auth/logout` 标记 `user_sessions.revoked_at`
### 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 授权 device pod
```text
browser admin UI
-> cloud-api POST /v1/admin/device-pod-grants
-> authenticate admin
-> validate users.status='active'
-> validate device_pods.status='active'
-> upsert device_pod_grants(device_pod_id, user_id)
-> return grant summary
```
撤销授权走 `DELETE /v1/admin/device-pod-grants/{devicePodId}/{userId}`,先释放或失效该用户对该 device pod 的活动 lease。
### 用户列出 device pod
```text
browser or code agent tool
-> cloud-api GET /v1/device-pods
-> authenticate actor
-> if admin: list all active device_pods
-> if user: inner join device_pod_grants by actor.id
-> return visible device pod summaries
```
未授权普通用户看到空列表或对单个未授权 device pod 收到 `403`;不得回退到 fake default device pod。
### 用户创建或继续 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 使用 device pod
```text
code agent turn
-> cloud-api device operation route
-> authenticate actor from owning session
-> verify agent_sessions.owner_user_id == actor.id
-> authorize device_pod_grants or admin
-> acquire or validate device_leases when operation is exclusive
-> cloud-api -> hwlab-device-pod internal Service
-> gateway/device-host-cli/hardware path
```
code agent prompt、runner 或 worker 不得直接绕过 cloud-api 调用 device pod Service。device pod 服务只信任来自 cloud-api 的内部调用,不做最终用户权限判断。
## 内部架构
`hwlab-cloud-api` 内部应按 auth/session、authorization、agent session owner、device-pod grant、device lease 和 admin API 模块分层。所有模块共享同一 Postgres runtime store 和 migration ledger,避免拆出早期 `hwlab-user-api` 造成跨服务一致性成本。
`user_sessions` 存浏览器 session token hash`agent_sessions.owner_user_id` 绑定 Code Agent session`device_pods` 存 profile authority`device_pod_grants` 表示用户对 device pod 的完整使用权;`device_leases` 只表达物理设备互斥,不表达权限。
## API 接口说明
| 接口 | 说明 |
| --- | --- |
| `POST /auth/login` | 校验本地账号并写入 `user_sessions` token hash。 |
| `GET /auth/session` | 从 cookie 恢复 actor、role 和 session 状态。 |
| `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 管理 device pod profile authority。 |
| `POST /v1/admin/device-pod-grants``DELETE /v1/admin/device-pod-grants/{devicePodId}/{userId}` | admin 授权或撤销普通用户使用 device pod。 |
| `GET /v1/device-pods` 和 device-pod 操作 API | 按 actor role 和 grant 过滤可见/可用 device pod。 |
| `POST /v1/agent/chat` 及 result/trace/cancel | 必须校验 `agent_sessions.owner_user_id`;admin 可跨用户查看和取消。 |
## 微服务设计
v0.2 不新增独立用户管理微服务。用户管理、登录、session、device pod grant 和 code agent owner 校验全部放在 `hwlab-cloud-api` 内,理由是:
- 当前权限模型只有 `admin/user`、session owner 和 device grant 三类判断,拆服务会增加网络、部署、Secret、迁移和一致性成本。
- cloud-api 已经是 `/v1/agent/*``/v1/device-pods/*` 和 runtime store 的统一入口,最适合做应用层授权收口。
- 后续若出现组织、计费、外部 IdP、批量用户导入或跨产品用户中心,再把 cloud-api 内的 user/auth 模块抽成 `hwlab-user-api`;抽服务前接口和表结构仍以本文为准。
各服务职责如下:
| 服务 | v0.2 职责 |
| --- | --- |
| `hwlab-cloud-web` | 登录页、普通用户工作台、admin 用户/授权 UI;浏览器 `/auth/*` 可由 cloud-web 代理到 cloud-api。 |
| `hwlab-cloud-api` | 用户、session、授权、device grant、lease、code agent owner 校验和对 device pod 的受控转发。 |
| `hwlab-agent-mgr` / `hwlab-agent-worker` | 执行 code agent session;接收 owner/session label 或 env 方便观测,但不作为最终权限 authority。 |
| `hwlab-device-pod` | 暴露设备语义 API;不保存用户权限,不直接面向浏览器或普通用户 session Pod。 |
| `hwlab-edge-proxy` | 公网/FRP 入口和 HTTP 转发;不做业务权限,只转发 cookie/header,不注入伪 actor。 |
| Postgres | v0.2 用户、session、授权、device pod、lease 和既有 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`
- device pod 工作负载必须带 `hwlab.pikastech.local/device-pod-id` label,并通过 Service 暴露稳定内部地址。
- code agent 到 device pod 的访问应收敛到 `code agent -> cloud-api -> device-pod`,避免普通 session Pod 直接调用 device pod Service 绕过应用层授权。
- 第一轮不引入 Keycloak、Dex、oauth2-proxy、OpenFGA、Capsule、vCluster、Kyverno 或 service mesh;需要正式外部身份源或集群 admission 兜底时再单独设计。
当前态、差距和迁移步骤见 [../plan/v02-multi-user-migration.md](../plan/v02-multi-user-migration.md)。
## 测试规格
## T1
阅读 docs/reference/spec-user-access.md,然后用 cli 手动测试以下内容:创建或确认 bootstrap admin,登录后访问 `/auth/session`,确认响应包含 actor、role 和 active session,不包含 password hash、session token 原文或 Secret 值。
## T2
阅读 docs/reference/spec-user-access.md,然后用 cli 手动测试以下内容:用 admin 创建普通用户和 device pod grant,再用普通用户列出 `/v1/device-pods`,确认只能看到被授权 device pod;撤销授权后同一用户不能再看到或使用该 device pod。
## T3
阅读 docs/reference/spec-user-access.md,然后用 cli 手动测试以下内容:普通用户创建 Code Agent session 后,只能读取、继续和取消自己的 trace/result;另一个普通用户访问该 session 必须失败,admin 可以跨用户查看或取消。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| admin/user 两角色模型 | 未完全实现 | 表结构和链路已定义,完整 cloud-api auth/authorization 仍待收敛。 |
| `users``user_sessions`、grant/lease 表 | 未完全实现 | 推荐迁移已定义,需实现 migration 和 runtime store。 |
| Code Agent owner 绑定 | 未完全实现 | `agent_sessions` 已存在,owner 字段和鉴权链路仍需落地。 |
| device pod 授权模型 | 未完全实现 | grant 语义已定义;当前 device-pod 仍主要是 fake/只读 payload。 |
| 不用 Kubernetes 表达用户权限 | 已实现/持续约束 | 规格明确禁止普通用户持有 kubeconfig 或直连 Service 权限。 |