283 lines
13 KiB
Markdown
283 lines
13 KiB
Markdown
# v0.2 多用户访问模型
|
||
|
||
本文是 HWLAB `v0.2` 多用户系统的长期参考口径。目标是用最少概念支持真实用户使用 code agent session 和管理员分配 device pod 权限,同时避免把用户体系、Kubernetes 租户、设备授权、硬件证据链和审计系统混成一套复杂门禁。
|
||
|
||
## 设计目标
|
||
|
||
- 只保留两类角色:`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 MVP 方案 |
|
||
| --- | --- | --- |
|
||
| 用户角色 | `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-api;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`
|
||
|
||
设备能力单元的管理表;profile 语义仍以 [device-pod.md](device-pod.md) 为准。
|
||
|
||
```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_ref TEXT NOT NULL DEFAULT '',
|
||
gateway_ref TEXT NOT NULL DEFAULT '',
|
||
device_pod_json TEXT NOT NULL DEFAULT '{}',
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### `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 的内部调用,不做最终用户权限判断。
|
||
|
||
## 微服务设计
|
||
|
||
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)。
|