Files
pikasTech-HWLAB/docs/reference/spec-user-access.md
T
2026-05-29 14:17:44 +08:00

19 KiB
Raw Blame History

v0.2 用户和权限管理规格

本文是 HWLAB v0.2 用户和权限管理的规格说明。目标是用最少概念支持真实用户使用 code agent session 和管理员分配 device pod 权限,同时避免把用户体系、Kubernetes 租户、设备授权、硬件证据链和审计系统混成一套复杂门禁。

本规格与 spec-device-pod.md 配套:用户和权限规格定义谁可以看见、创建和使用 device poddevice-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-mgrhwlab-agent-worker 和 Code Agent runtime 只能消费已经由 cloud-api 判断过的 actor/session/device 权限。

Postgres 是该规格的数据持久化边界。Kubernetes namespace、ServiceAccount、Service 直连和 gateway route 都不能替代用户权限模型;普通用户不获得 kubeconfig、内部 Service 直连能力或长期 Secret。

规格目标

  • 只保留两类角色:adminuser
  • code agent session 直接归属于创建它的用户;普通用户只能查看、继续和取消自己的 session。
  • device podadmin 管理;普通用户只有在被 admin 授权后才能看到和使用对应 device pod。
  • device pod 授权不拆分 readoperate 或 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_admindevice_admindeveloperviewer adminuser
用户组 groupsgroup_members 不引入
项目隔离 projectsproject_members 不引入
Code Agent 会话 ownerUserId + projectId + sessionId owner_user_id + session id
Device Pod 管理 平台管理员和设备管理员分工 admin 统一管理
Device Pod 授权 可按 group/project/user 授权 只按具体 user_id 授权
设备权限粒度 device.viewio.readio.writedebug.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.idrole 和授权表稳定,不把外部 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)
);
  • 不包含 capabilityscopeexpires_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 校验 owneradmin 可跨用户查看和取消。

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 hashagent_sessions.owner_user_id 绑定 Code Agent sessiondevice_pods 存 profile authoritydevice_pod_grants 表示用户对 device pod 的完整使用权;device_leases 只表达物理设备互斥,不表达权限。

API 接口说明

接口 说明
POST /auth/login 校验本地账号并写入 user_sessions token hash。
GET /auth/session 从 cookie 恢复 actor、role 和 session 状态。
GET /v1/auth/sessionGET /v1/users/meGET /v1/access/statusGET /v1/setup/status REST 状态和兼容入口;不得读取或返回 password hash、session token 原文或 Secret 值。
POST /v1/setup/first-admin 仅当 users 表为空时创建第一个 admin 并建立 session;可选 devicePoddevicePods[] 一次性种下首批服务端权威 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-podsPUT /v1/admin/device-pods/{devicePodId} admin 管理 device pod profile authority。
POST /v1/admin/device-pod-grantsDELETE /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}/leasesGET/DELETE /v1/device-pods/{devicePodId}/leases/current 对已授权 device pod 获取、查看和释放互斥 lease;强副作用 job 必须携带有效 lease token。
POST /v1/agent/chat 及 result/trace/cancel 必须校验 agent_sessions.owner_user_idadmin 可跨用户查看和取消。

POST /v1/setup/first-admin 的 device-pod 初始化只用于空库首次进入系统,不能作为长期 profile 管理入口。每个 seed 必须包含 devicePodId 和 object profilecloud-api 会写入 device_pods.profile_json/profile_hash 并创建 device_pod_grants(device_pod_id, first_admin_user_id)。响应只能返回脱敏 profile、profileHash 和 grant summary,不得返回 gatewaySessionIdhostWorkspaceRoot、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-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-idhwlab.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 兜底时再单独设计。

当前态、差距和迁移步骤已迁入 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 路由。
usersuser_sessions、grant/lease 表 部分实现 0001 schema 和 access-control bootstrap 覆盖 users、sessions、device_pods、grants、leases 和 jobsDevice Pod 强副作用 job 已接入 lease token 校验,真实硬件执行仍依赖 gateway/device-host-cli 在线。
Code Agent owner 绑定 已实现 已在 agent_sessions 写入 owner_user_id、conversation/thread/trace 和脱敏 session evidencetrace/result cache 也按 owner/admin 限制访问。
device pod 授权模型 部分实现 cloud-api 已实现 admin profile/grant、普通用户可见性和 job 持久化;无在线 gateway/device-host-cli 时返回 blocker。
不用 Kubernetes 表达用户权限 已实现/持续约束 规格明确禁止普通用户持有 kubeconfig 或直连 Service 权限。