Files
qinglong/docs/adr/ADR-0030-legacy-panel-authentication-and-stable-user-identity.md
T

9.2 KiB
Raw Blame History

ADR-0030Legacy Panel 认证与稳定 User Identity

  • 状态:Proposed
  • 日期:2026-07-19
  • 关联:QL-RFC-0001、ADR-0028、ADR-0029

上下文

QingLong 2.x 是单用户面板,但没有稳定的 User ID。AuthsauthConfig JSON 保存可修改 username、密码和当前 tokenJWT payload 只有随机 dataiatexp,不包含 subject。Express 先验证 HS384 JWT,再按 desktop/mobile 到当前 token 列表中查找。仅把 username 当 3.0 ActorRef 会在改名后漂移;仅验证 JWT 签名会让已经 logout、被 token 列表移除的 credential 重新获得权限;把 system App token 当 User 则会混淆 API App 与交互用户。

ADR-0029 的 owner claim 只接受 AuthenticatedPrincipal,因此必须先建立一个不复制 credential、不自动授予角色、能够在 edge 上点查并在 cluster 中替换 adapter 的稳定身份映射。

同时,当前部署并不存在可靠的在线“本机管理员”边界。Alpine 镜像和大量存量部署让控制面与用户脚本以同一 UID、同一文件系统运行;即使 Debian 镜像使用 qinglong 用户,控制面和脚本通常仍共享该用户。TTY、loopback、0600 文件、环境变量或 Unix peer UID 都可能被同权限任务读取或调用,不能直接产生 assurance=local_console

决策

1. Legacy 单用户映射为固定内部 User,不使用 username

0019-identity-directory 新增:

  • IdentitySubjects:稳定 subject、active|disabled、version 和时间;
  • IdentityAuthenticationBindings(provider,provider_subject,version) append-only binding,状态为 active|revoked
  • current 和 subject 反向索引均为有界点查。

baseline 固定为:

IdentitySubject: user/usr_legacy_primary
AuthenticationBinding: legacy_panel/singleton@1 -> usr_legacy_primary

usr_legacy_primary 是实例内 opaque identifier,不是用户名、数据库行号、JWT data、IP 或设备名。migration 不读取或复制 username、password、two-factor secret、JWT、system App credential 或 token list,也不创建任何 Project RoleBinding。改名只改变 2.x 显示/登录字段,不改变 3.0 ActorRef。

未来原生多用户可以创建新的 opaque User ID 和 provider binding,但不得重用 username 作为主键。cluster-control 必须提供保持相同 current-version、disable/revoke 和 fail-closed 语义的 PostgreSQL adapter。

2. Legacy session authority 是三个条件的交集

LegacyPanelAuthenticationService 只有同时满足以下条件才返回 principal:

  1. token 是有界 canonical JWT,签名算法精确为 HS384
  2. payload 精确包含 data/iat/exp,签名、iat <= now < exp 均有效;
  3. token 仍存在于当前 2.x primary token 或请求 platform 对应的 token list
  4. legacy_panel/singleton 最新 binding active
  5. 对应 IdentitySubject 为 active user。

签名有效但已 logout、platform 错位、binding revoked、subject disabled 或 identity 损坏都 fail closed。JWT/request 不得自报 subject、role、scope、assurance 或 Project。authenticationIdlegacy_panel: 加 token SHA-256,只用于非 Secret 关联;原 token 不进入 identity 表、RoleBinding、错误或审计字段。

session source 兼容 2.x primary string、platform string 和 TokenInfo[] 三种现存格式;候选数量和单 token 字节数有硬上限,损坏/超载返回 unavailable,不能降级成“只要签名正确”。比较使用 SHA-256 后的 constant-time equality。

3. Legacy assurance 永远是 single_factor

2.x token 没有不可篡改的 amr/acr 或“本次签发已完成 TOTP”声明。当前 twoFactorActivated=true 只描述账号现在启用了 TOTP,不能证明一个较早签发且尚未 logout 的 token 完成过第二因素。

因此所有 legacy panel principal 固定为 assurance=single_factor。未来只有新认证协议在 credential 内绑定并验证 factor evidence 后,才能返回 multi_factor;不得根据当前设置、UI 路径或调用者参数推导。

4. Identity disable 与 authentication binding revoke 分离

  • disable IdentitySubject:停止该 User 的所有 provider authentication
  • revoke AuthenticationBinding:只停止某 provider/external subject 的映射;
  • logout/token removal:只停止对应 session
  • RoleBinding revoke:只撤销某 Project 权限,不改变身份认证。

四者不得互相替代。当前切片只实现 baseline、current resolve 和认证读取,不开放 identity mutation API;未来 mutation 必须 append binding version、使用 subject CAS 并追加低敏审计。

5. 不提供伪安全的在线 local-console issuer

本 ADR 明确拒绝在当前同 UID 执行模型中用下列信号直接构造 system/owner-bootstrap + local_console

  • 请求来自 loopback
  • CLI 进程有 TTY
  • capability 文件模式为 0600
  • 调用者 Unix UID 与控制面相同;
  • 某个环境变量或命令行 flag 存在。

这些条件同样可能被用户脚本满足。可接受的后续实现必须选择并验证至少一种真实隔离:

当前 2.x 启动顺序也不存在可直接复用的 HTTP claim 安全窗口:cluster master 完成 migration 后先 fork gRPC worker,再 fork HTTP workerHTTP worker 在 listen 前执行 initData()initTask(),其中 system token task 可 runImmediately=true,已有 Cron 也会在 loader 阶段注册/启动。也就是说,在首个 HTTP 请求到达前,同 UID 任务已经可能运行。可信首装 ceremony 必须移动到 cluster master 的任何 worker pre-fork gate,或先完成 Executor 独立 UID 隔离;不能只新增一个“仅首次启动可访问”的 HTTP route。

  1. 在任何用户任务/Executor 启用前完成一次性安装 ceremony,并在启用任务前销毁 issuer capability
  2. 使用用户任务不可达的独立 UID/host agent,通过 peer credential 提供一次性签发;
  3. 服务完全停止且持有可验证的 offline maintenance lease,保证没有任务进程和控制面竞争;
  4. deployment orchestrator 注入硬件/平台证明,且该证明不暴露给 Executor。

在上述边界落地前,owner bootstrap issue 继续 production unreachable。legacy panel authentication core 只能为未来 claim/wire adapter提供稳定 user,不能自行签发 challenge。

6. 当前切片保持 production unreachable

本切片实现 0019、schema ownership、Identity Directory SQLite resolver、legacy Auth snapshot parser 和 authentication service,但没有:

  • shareStore/Express 到 session source 的生产装配;
  • /api/v3 middleware 或 bootstrap claim route
  • local-console issuer、安装向导或 offline maintenance lease
  • identity disable/revoke 管理 API 和审计事件;
  • PostgreSQL adapter 与跨方言 contract suite
  • 原生多用户、MCP/API App/Agent authentication adapter。

因此现有 /api/* 行为不变,default Project 仍没有 owner。新增 baseline identity 只是稳定映射候选,不产生任何权限或外部可达能力。

影响

正面影响:

  • legacy 用户改名不会改变 3.0 审计主体;
  • logout、token platform 和 identity revoke/disable 都能独立 fail closed
  • 不复制 credential,edge 只增加两张小表和一次有界点查;
  • 2FA assurance 不会被当前设置错误提升;
  • 明确暴露同 UID 部署的信任限制,避免不安全 CLI 进入生产。

代价与风险:

  • 所有 2.x panel session 暂时映射到同一 singleton User,无法区分共享账号背后的自然人;
  • 每次 3.0 认证在现有 session 检查外增加一次 identity 点查;
  • 当前 core 不接路由,用户仍不能完成 owner claim
  • 真正 local-console ceremony 可能要求启动顺序、UID 隔离或停机运维变化。

未选择的方案

  1. username 作为 User ID:可修改、可能复用,审计漂移,拒绝。
  2. JWT data 作为 User ID:每次登录随机变化且属于 credential,拒绝。
  3. 只验证 HS384 签名logout 后 token 仍可能在到期前通过,拒绝。
  4. system App 映射为 User:把机器 credential 提升成交互用户,拒绝。
  5. twoFactorActivated 推导 MFA:不能证明 token 签发时完成过 TOTP,拒绝。
  6. migration 自动创建 owner binding:身份迁移不等于授权同意,拒绝。
  7. 同 UID 在线 CLI 视为 local console:用户任务具有相同能力,拒绝。

验证要求

  • migration baseline 与 username/token/password/TOTP 完全无关,不创建 RoleBinding
  • username 变化不改变 user/usr_legacy_primary
  • HS384、精确 payload、毫秒级 active window 和 current-session membership 全部验证;
  • logout、platform drift、wrong signature/algorithm、extensible JWT/request 在 identity 副作用前拒绝;
  • legacy primary string、platform string 和 TokenInfo[] 兼容且有硬上限;
  • authentication ID 只包含 token digest,错误不泄漏 token
  • binding revoke、subject disable、损坏 storage/session source fail closed
  • assurance 始终为 single_factor
  • Node 22/24 全量测试、类型检查、schema audit 和 GitNexus reachability 通过;
  • app/loaders/api/services/shared/data 不得导入或装配本切片。