6.5 KiB
生产管理员 break-glass 密码重置:设计说明
1. 设计边界
本任务恢复唯一活跃管理员的认证能力,不修复产品、不启用邮件、不做合同验收。写入面由两个产品原生事务构成:break-glass 脚本写入临时密码,以及登录后的 /api/auth/change-password 写入最终密码。两者共同构成重置生命周期;除此之外没有生产账号或业务写入。
合同只读验收属于仍在 LOGIN PAUSED 的 08-08-production-authenticated-readiness,只有本任务完成、最终 secret 可用后才恢复。
2. 状态机与门禁
PLANNING
-> source/target/runtime-script SHA(只读)
-> --preflight PASS/1/true(只读)
-> --diagnose-wecom-binding(只读)
-> 旧 hash 有限恢复材料 + 临时/最终 secret 就绪
-> --apply ACTIVE_ADMIN_BREAK_GLASS(唯一一次脚本写入)
-> --verify PASS(只读)
-> 正式 secret 写入临时凭据
-> /login 一次 -> must-change redirect
-> /change-password 一次(最终密码写入)
-> --verify-password-changed PASS(只读)
-> 正式 secret 写入最终凭据
-> 写后诊断 / 清理旧 hash、临时 secret、浏览器状态
-> RESET_COMPLETE / handoff readiness task
任何读门禁失败都停在写入前。apply 通道中断或非 PASS 时不得再 apply;先以 --verify 判断是否已经提交。一次登录/改密失败同样停止,不能消耗第二次尝试。
3. 被执行脚本的身份
本地当前脚本自已部署候选以来未变,但生产执行仍以三方事实确认:
- 公网/运行 build 为产品候选;
git show <产品候选>:server/src/scripts/reset-active-admin-password.js生成审查基线;- 运行容器同路径 SHA-256 与基线精确一致。
只记录 scriptShaMatch=true。不把当前 HEAD 的任务文档提交当产品 build,也不向生产同步脚本。
4. 唯一目标与并发保护
--preflight 使用 findActiveAdmins(... take:2),只有 1 个 active、未 archived、具 admin/system_admin 的账号才 PASS。apply 前已取得目标 id/token version;事务内再读 status、archived、角色、token version。任何变化都抛 active_admin_target_changed,事务不写。
会话和 challenge 的 updateMany 均带 employee_id=target.id。审核时必须将返回计数理解为“唯一目标的副作用”,不能外推为全表操作。
5. 凭据生成、保管与流转
临时密码和最终密码由本机受控 Node/CSPRNG 进程生成,强制满足密码策略且不相同。值不进入环境变量或 argv:
- 临时密码写到新建
0600临时 secret,通过 stdin 喂给 apply; - 正确 email 只在受控进程中从唯一管理员记录取得;
- apply/verify PASS 后,原子更新正式 live-smoke secret 为 email + 临时密码;
- 强制改密成功后,再原子替换为最终密码;
- 更新过程保留其他角色键,不生成明文备份副本。
若主 secret 更新失败,临时 secret 保留到恢复决策,避免 apply 已成功但密码丢失。所有清理只输出布尔。
6. 旧 hash 有限恢复材料
写前使用 read-only 查询读取唯一目标当前 hash,并由远端进程直接写入 0700 受控目录下随机 0600 文件。hash 不经过普通 stdout,不离开生产现场。
它不是完整或部分状态回滚。即使另行批准写回,也不能恢复 token version、会话、challenge、锁定计数或审计。默认禁止写回;仅当新凭据异常且获得新的明确写入批准时,才可把它当作一个格式已知的 hash 候选。
保留期限取以下较早者:
--verify-password-changedPASS;- apply 后 24 小时。
到期删除并验证 recoveryMaterialDestroyed=true。若 hash 未销毁,任务不得宣称完全收口。
7. 写入事务与验证
7.1 Break-glass apply
输入仅走 stdin,命令必须包含精确确认串。预期输出:唯一目标、passwordUpdated、mustChangePassword、tokenVersionIncremented、locksCleared、仅目标会话/challenge 计数和 auditCreated 均满足合同。
非零退出、缺失 JSON、status!=PASS 或 SSH 中断都进入 APPLY_UNCERTAIN。此时只允许 --verify;若 verify 显示写入已落地,禁止重试。
7.2 首次登录与强制改密
一次性 Playwright Node runner 使用新会话、单 browser、零 retry,并只从 stdin/受控文件描述符读取临时与最终密码;runner 源码不落仓库或 test spec,凭据不进入 argv/env:
/login填正确 email 和临时密码,提交一次;- 必须跳到
/change-password;任何/portal或业务路由出现都视为 contract mismatch; - 填临时密码、最终密码、确认密码,提交一次;
- 允许应用成功跳到
/portal,但立即停止,不点击任何业务入口; - 清除浏览器 storage/cookie 并关闭 runner。
随后运行 --verify-password-changed。该验证要求唯一目标、must-change=false、locks cleared、activeSessions=1,以及改密和 break-glass 两类审计都存在。
8. 企微绑定影响
--diagnose-wecom-binding 写前和最终验证后均只读运行。它记录绑定存在、目录同步、冲突、近期密码/企微登录计数,不读取或输出 external userid。脚本不修改绑定;如出现冲突或目标不唯一,停止并另行处理,不在本任务绑定/解绑。
9. 失败矩阵
| 阶段 | 失败 | 动作 |
|---|---|---|
| provenance/target/SHA | 不一致 | 停止,零写入 |
| preflight | 非 PASS/1/true | 停止,不选择目标 |
| recovery material | 无法安全保存 | 停止,不 apply |
| apply 明确失败 | status=BLOCKED |
停止,不重试 |
| apply 通道不确定 | 无完整结果 | 只跑 --verify 定性 |
| verify | BLOCKED | 保留现场,禁止登录 |
| secret 更新 | 失败 | 保留受控临时 secret,停止 |
| login/change-password | 首次失败 | 停止,零 retry,不 apply 第二次 |
| verify-password-changed | BLOCKED | 保留现场,另行决策 |
| hash 清理 | 失败 | 标记敏感清理未完成,不归档 |
10. Reviewer 与交接
本任务涉及唯一生产管理员、认证状态机和真实账号写入,执行后必须由只读 reviewer 核对:脚本身份、目标唯一、apply 次数、事务副作用、两次密码写入、secret/hash 清理和零业务写入。reviewer 不修改代码、账号或现场材料。
最终只交接“管理员最终凭据已在仓库外 secret 就绪”和稳定计数;不交接凭据值。合同功能是否可用仍由原 readiness task 独立判定。