19 KiB
v0.2 用户和权限管理规格
本文是 HWLAB v0.2 用户和权限管理的规格说明。目标是用最少概念支持真实用户使用 code agent session 和管理员分配 device pod 权限,同时避免把用户体系、Kubernetes 租户、设备授权、硬件证据链和审计系统混成一套复杂门禁。
本规格与 spec-device-pod.md 配套:用户和权限规格定义谁可以看见、创建和使用 device pod;device-pod 规格定义 profile authority、REST/job 和硬件执行边界。
实施跟踪见 pikasTech/HWLAB#531,原 docs/plan/v02-multi-user-migration.md 迁移计划全文已迁入该 issue 评论。
在系统中的职责划分
用户和权限管理不是独立微服务,权威实现收敛在 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-v02namespace 内使用独立 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。
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。
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;/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 绑定字段:
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 为准。profile 定义 device pod,因此 profile 必须由 admin 通过 cloud-api 管理,不能由 code agent 本地 .device-pod/ 文件决定。
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 的授权关系;存在即全权限。
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
设备互斥锁;不表达权限。
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 每个用户态请求都按同一顺序处理:
authenticate -> actor -> authorize(actor, action, resource) -> optional lease check -> execute
登录和 session 恢复
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 创建用户
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
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
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
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
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 状态。 |
GET /v1/auth/session、GET /v1/users/me、GET /v1/access/status、GET /v1/setup/status |
REST 状态和兼容入口;不得读取或返回 password hash、session token 原文或 Secret 值。 |
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 管理 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/device-pods/{devicePodId}/leases、GET/DELETE /v1/device-pods/{devicePodId}/leases/current |
对已授权 device pod 获取、查看和释放互斥 lease;强副作用 job 必须携带有效 lease token。 |
POST /v1/agent/chat 及 result/trace/cancel |
必须校验 agent_sessions.owner_user_id;admin 可跨用户查看和取消。 |
POST /v1/setup/first-admin 的 device-pod 初始化只用于空库首次进入系统,不能作为长期 profile 管理入口。每个 seed 必须包含 devicePodId 和 object profile;cloud-api 会写入 device_pods.profile_json/profile_hash 并创建 device_pod_grants(device_pod_id, first_admin_user_id)。响应只能返回脱敏 profile、profileHash 和 grant summary,不得返回 gatewaySessionId、hostWorkspaceRoot、password 或 session token 原文。
微服务设计
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-v02namespace,不按用户创建 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-idlabel,并通过 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 兜底时再单独设计。
当前态、差距和迁移步骤已迁入 pikasTech/HWLAB#531 评论。
测试规格
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/*、bootstrap admin、admin/user 创建和 admin-only 路由。 |
users、user_sessions、grant/lease 表 |
部分实现 | 0001 schema 和 access-control bootstrap 覆盖 users、sessions、device_pods、grants、leases 和 jobs;Device Pod 强副作用 job 已接入 lease token 校验,真实硬件执行仍依赖 gateway/device-host-cli 在线。 |
| Code Agent owner 绑定 | 已实现 | 已在 agent_sessions 写入 owner_user_id、conversation/thread/trace 和脱敏 session evidence;trace/result cache 也按 owner/admin 限制访问。 |
| device pod 授权模型 | 部分实现 | cloud-api 已实现 admin profile/grant、普通用户可见性和 job 持久化;无在线 gateway/device-host-cli 时返回 blocker。 |
| 不用 Kubernetes 表达用户权限 | 已实现/持续约束 | 规格明确禁止普通用户持有 kubeconfig 或直连 Service 权限。 |