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

22 KiB
Raw Blame History

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

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

本规格与 spec-device-pod.md 配套:用户和权限规格定义谁可以看见、创建和使用 device poddevice-pod 规格定义 profile authority、REST/job 和硬件执行边界。

登录入口、Keycloak OIDC、Web session、CLI API key 和 AuthPrincipal 归一见 spec-v02-auth.md。OpenFGA、Admin Access WebUI 和同路径 CLI 细节见 spec-v02-openfga-authorization.md。本文只定义认证完成后的角色、资源归属、device pod capability、tool capability 和 code agent owner 授权;正式用户鉴权只有 Web session 与 CLI/API key 两类。

实施跟踪见 pikasTech/HWLAB#531,原 docs/plan/v02-multi-user-migration.md 迁移计划全文已迁入该 issue 评论。

在系统中的职责划分

用户和权限管理不是独立微服务,权威实现收敛在 hwlab-cloud-api:它消费 spec-v02-auth.md 产出的 AuthPrincipal,负责角色、OpenFGA check/write、用户 API key、device pod capability、tool capability 和 code agent session owner 校验。hwlab-cloud-web 只提供浏览器 UI 和同源代理;hwlab-device-pod 只执行设备语义;AgentRun v0.1 只消费 cloud-api 按用户权限注入的 actor/session/device/tool 上下文,不成为 HWLAB 用户权限 authority。

Postgres 是用户、session、业务对象和迁移 ledger 的持久化边界;OpenFGA 是细粒度授权关系与授权判定边界。Kubernetes namespace、ServiceAccount、Service 直连和 gateway route 都不能替代用户权限模型;普通用户不获得 kubeconfig、内部 Service 直连能力、OpenFGA token 或长期 Secret。

v0.2 本地 bootstrap 管理员账号固定为 admin,默认登录密码固定为 hwlab2026,只作为空库初始化和 legacy fallback。Keycloak 接入后的目标 Web 登录以 OIDC 为准,CLI 以 HWLAB_API_KEY 为准。 运行时仍只通过 hwlab-v02-bootstrap-admin/password-hash SecretRef 注入本地 fallback password hashPostgres、API 响应、日志、CLI session 和文档不得保存或输出 password hash、session token 原文或 Secret 值。 如果 live Secret 需要重建或旋转,必须保持 /auth/login 这个 legacy/bootstrap 入口可用,但不得把它重新写成目标登录体验。

规格目标

  • 只保留两类角色:adminuser
  • code agent session 直接归属于创建它的用户;普通用户只能查看、继续和取消自己的 session。
  • device podadmin 或被授予 profile_editor 的用户管理;普通用户只有在被授权后才能看到、操作或提交对应 device pod job。
  • device pod 授权按 vieweroperatorprofile_editorjob_submitter 等 OpenFGA relation 表达。
  • 工具能力必须独立授权,例如 hwpodunidesk_sshtrans_cmd 和 GitHub 写工具;拥有 Code Agent session 不等于拥有这些工具。
  • MVP 不新增产品级 audit_events 用户审计表,也不把用户权限依赖到 audit。现有硬件 trace/evidence/audit 字段属于硬件闭环证据,不是多用户权限模型的一部分。
  • 强副作用 device-pod job 只额外要求业务 reason;设备互斥由 executor、gateway 和硬件 host 串行化或返回 blocker,不进入用户权限模型。
  • 普通用户不获得 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_id 授权,relation 由 OpenFGA 表达
设备权限粒度 io.readio.write 等硬件寄存器级 capability vieweroperatorprofile_editorjob_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。

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/legacy 账号;OIDC identity 扩展字段、api_keys 表和 API key 规则见 spec-v02-auth.md。接入 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、OIDC callback、API key 认证和 /auth/logout 的最终 authority 是 cloud-api;本地 /auth/login 只作为 bootstrap/legacy fallback。/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

权限矩阵

操作 admin user
管理用户和权限 可以;应镜像为 system:hwlab#adminaccess_manager 只有被授予 access_manager 才可以
创建自己的 code agent session 可以 可以
查看、继续、取消自己的 code agent session 可以 拥有该 session 的 viewer/operator 时可以
查看、取消别人的 code agent session 可以 需要该 session 的显式 relation
创建、更新、删除 device pod 可以 需要目标 device pod 的 profile_editor
给用户授权或撤销 device pod/tool 可以 需要 access_manager
查看 device pod 可以查看全部 需要目标 device pod 的 viewer 或更高 relation
使用 device pod 的 workspace/debug/io 能力 可以使用全部 需要 tool:hwpod#can_use 且目标 device pod 具备 operator/job_submitter
提交强副作用 device-pod job 必须填写 reason 被授权后仍必须填写 reason
调用 UniDesk SSH / trans cmd / GitHub 写工具 可以,但仍受工具边界约束 需要对应 tool:*#can_use

请求链路

cloud-api 每个用户态请求都按同一顺序处理:

authenticate -> actor -> authorize(actor, action, resource) -> reason check for mutating device jobs -> execute

登录、API key 和 session 恢复

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。资源授权模块只消费已经恢复出的 actor/AuthPrincipal。/auth/session 使用 cookie 查 user_sessions,再查 users 得到 actorAPI key 认证直接从 api_keys -> users 得到同一 actor。/auth/logout 标记 user_sessions.revoked_at。Keycloak access token、refresh token、realm role 和内部 service token 都不能绕过这里的 actor 恢复与资源授权。

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 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。

需要给用户开通 device pod 或工具能力时,统一使用 Admin Access API、Admin Access WebUI 或同路径 hwlab-cli client access ...,并按具体 relation 或 tool:*#can_use 写入 OpenFGA tuple。

用户列出 device pod

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 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 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 -> 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 的内部调用,不做最终用户权限判断。Cloud API 给 AgentRun runner 注入 hwpod、UniDesk SSH、trans_cmd 或 GitHub 写工具前,必须先按 spec-v02-openfga-authorization.md 检查对应 tool:*#can_use

内部架构

hwlab-cloud-api 内部应按 auth/session/API key、authorization、agent session owner、device-pod relation 和 admin API 模块分层。所有模块共享同一 Postgres runtime store 和 migration ledger,避免拆出早期 hwlab-user-api 造成跨服务一致性成本。

user_sessions 存浏览器 session token hashapi_keys 存映射到用户的 CLI/runner API keyagent_sessions.owner_user_id 绑定 Code Agent sessiondevice_pods 存 profile authorityOpenFGA tuple 表示用户对 device pod、agent session 和工具的细粒度能力。cloud-api 调用 device-pod 内部执行服务使用内部 service token,该 token 不参与用户鉴权、不写入 runner env,也不产生 actor。

API 接口说明

接口
GET /auth/oidc/loginGET /auth/oidc/callback Web 登录入口;按 spec-v02-auth.md 接入 Keycloak 并写入 24 小时 user_sessions token hash。
POST /auth/login 本地账号密码 bootstrap/legacy fallback;不作为目标 Web/CLI 登录体验。
GET /auth/session 从 cookie 恢复 actor、role 和 session 状态;API key actor 摘要见 /v1/users/me
GET /v1/auth/sessionGET /v1/users/meGET /v1/access/statusGET /v1/setup/status REST 状态和兼容入口;不得读取或返回 password hash、session token 原文或 Secret 值。
GET/POST /v1/api-keys... 用户 API key 管理入口;CLI HWLAB_API_KEY 规则见 spec-v02-auth.md
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。
GET/PATCH/PUT/DELETE /v1/admin/access... admin Access API,读写 OpenFGA 授权、tool capability、role/status 和 effective matrix;见 spec-v02-openfga-authorization.md
GET /v1/device-pods 和 device-pod 操作 API 按 actor role、OpenFGA relation 和 tool capability 过滤可见/可用 device pod。
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,并通过 Admin Access/OpenFGA relation 授权给首个 admin。响应只能返回脱敏 profile、profileHash 和授权摘要,不得返回 gatewaySessionIdhostWorkspaceRoot、password 或 session token 原文。

微服务设计

v0.2 不新增独立用户管理微服务。Keycloak 是独立身份提供方,不是 HWLAB 应用层授权服务;OpenFGA 是内部授权 PDP,不对用户暴露独立 API;用户映射、session/API key 消费、OpenFGA check/write、device pod 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 为准。

各服务职责如下:

服务 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 授权、device relation、tool capability、code agent owner 校验和对 device pod 的受控转发。
OpenFGA hwlab-v02 内部稳定授权服务,只接受 cloud-api 调用,不向普通用户或公网暴露。
AgentRun v0.1 runner 执行 code agent session;接收 cloud-api 提供的 owner/session/device 上下文方便观测,但不作为最终权限 authority。
hwlab-device-pod 暴露设备语义 API;不保存用户权限,不直接面向浏览器或普通用户 session Pod。
hwlab-edge-proxy 公网/FRP 入口和 HTTP 转发;不做业务权限,只转发 cookie/header,不注入伪 actor。
Postgres v0.2 用户、session、授权、device pod 和既有 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 按 spec-v02-auth.md 作为独立 keycloak namespace 的外部身份源接入;OpenFGA 按 spec-v02-openfga-authorization.md 部署在 hwlab-v02 namespace 作为内部授权服务;Kubernetes 租户隔离第一轮仍不引入 Dex、oauth2-proxy、Capsule、vCluster、Kyverno 或 service mesh。

当前态、差距和迁移步骤已迁入 pikasTech/HWLAB#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 给普通用户授予某个 device pod 的 viewer 但不授予 operator/job_submitter,确认普通用户只能看到 device pod 摘要,提交 job 返回 403;授予 operator/job_submitter 后 job 可提交;撤销 relation 后同一用户不能再看到或使用该 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、OpenFGA relation 和 job 表 部分实现 access-control bootstrap 覆盖 users、sessions、device_pods、access_tuples 和 jobsDevice Pod 强副作用 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 管理 device pod relation 与 tool capability;后续扩展仍必须按 spec-v02-openfga-authorization.md 保持同一 authority。
不用 Kubernetes 表达用户权限 已实现/持续约束 规格明确禁止普通用户持有 kubeconfig 或直连 Service 权限。