13 KiB
13 KiB
生产管理员 break-glass 密码重置
Goal
在生产邮件自助重置不可用、唯一活跃管理员现有仓库外密码已确认不匹配的前提下,仅使用仓库既有 reset-active-admin-password.js 恢复管理员认证能力。任务必须先证明执行脚本与已部署候选一致、目标管理员唯一,再执行一次带字面确认串的事务写入;随后完成脚本要求的首次强制改密并销毁旧 hash 有限恢复材料。
本任务只完成“临时密码 → 强制改密 → 最终密码可用”的认证恢复生命周期,不进入合同或其他业务页面,不执行 08-08-production-authenticated-readiness 所属的合同登录态验收。恢复完成后回到该任务继续只读验收。
Background and Confirmed Facts
- 前序任务已证明生产运行产品候选为
0224ad8,65 条 migration 全部 applied,连续四次rollbackWindow=OPEN / terminatedRows=0,生产基线健康。 - 生产只有 1 个活跃管理员;其 email 可唯一解析,未锁定、登录失败计数为 0,但现有仓库外管理员密码与生产 hash 不匹配。登录 attempts 仍为 0。
- 生产
EMAIL_ENABLED=false,公网 capabilities 为emailDeliveryReady=false / email_disabled;自助忘记密码不可用,且管理员 password reset 未锁定、请求计数为 0。 server/src/scripts/reset-active-admin-password.js自提交9b12451起存在,在已部署候选0224ad8与当前 HEAD 之间没有变化。执行前仍必须比较运行容器脚本与已审查候选脚本的 SHA-256,不能只依赖 Git 历史结论。- 脚本
--preflight、--verify、--verify-password-changed、--diagnose-wecom-binding均为只读;写入只在--apply ACTIVE_ADMIN_BREAK_GLASS分支发生,缺少字面确认串会返回explicit_confirmation_required。 applyReset只选择唯一 active、未 archived、具有admin/system_admin角色的账号,并在事务内再次校验状态、角色和 token version,目标变化会 fail-closed。- 所有会话、密码重置码、邮件登录码更新均限定
employee_id=target.id;不会撤销其他员工会话。 - 脚本使用应用同源
hashPassword,写入must_change_password=true。首次成功登录必定进入/change-password;只有完成该页面的密码写入后,--verify-password-changed才能 PASS。
Scope
- 对已发布来源、唯一生产目标、运行脚本 SHA、唯一管理员目标和当前认证/企微绑定状态做只读门禁。
- 在受控现场以最小权限短期保存旧 password hash,生成符合现行密码策略的一次性临时密码;二者均不回显、不进入仓库、task、普通日志或对话。
- 仅一次执行
reset-active-admin-password.js --apply ACTIVE_ADMIN_BREAK_GLASS,密码只经 stdin 输入;人工读取结构化结果并立即运行--verify。 - 将唯一管理员 email 与临时密码原子写入仓库外
live-smokesecret,使用真实浏览器仅登录并完成强制改密;随后将最终密码原子更新到同一 secret。 - 运行
--verify-password-changed,确认强制改密完成;销毁临时凭据和旧 hash 有限恢复材料,形成脱敏交接记录。
Requirements
R1 来源、目标与脚本门禁
- 执行来源必须 clean、committed、published,并通过既有生产精确身份/read-only preflight;不得使用 unpublished override。
- 生产运行/公网 build 仍须匹配产品候选
0224ad8;当前任务文档 HEAD 不作为产品 build 预期。 - 运行容器内脚本 SHA-256 必须与
git show 0224ad8:server/src/scripts/reset-active-admin-password.js一致;不一致立即PAUSED,不得从当前工作区向生产复制脚本。 - 本地先运行脚本单元测试;测试失败、脚本漂移、目标身份不确定、migration/status 异常均停止。
R2 脚本只读前置
- 先运行
--preflight,要求status=PASS、activeAdminMatches=1、exactTarget=true;不能用此前聊天或旧查询替代现场结果。 - 在写入前运行
--diagnose-wecom-binding,保存其布尔/计数摘要。exactTarget=false、目标冲突或查询失败立即停止;普通绑定存在本身不是密码重置阻塞,不修改/解绑企微。 - 另取生产认证状态只读快照:must-change、token version、活跃会话/验证码计数和窗口状态只记录布尔/计数;不输出账号、email、hash 或生产路径。
--preflight、企微诊断、脚本 SHA 与旧 hash 保存完成前,禁止生成写入请求或调用--apply。
R3 临时凭据与有限恢复材料
- 临时密码与最终密码分别由 CSPRNG 生成,满足至少 12 位、含大小写和数字且不含公司名/常见序列;两者不得相同,也不得复用旧 secret。
- 临时密码先写入本机仓库外、
0600的受控临时 secret;apply 从 stdin 读取,不放 argv、环境变量、shell history 或日志。apply PASS 且--verifyPASS 后,才把正确 email/临时密码原子更新到正式 live-smoke secret。 - 旧 hash 仅保存于生产受控现场的随机
0600文件,目录0700;本地只记录recoveryMaterialPresent=true,不得记录路径或 hash。 - 旧 hash 不是完整或部分状态回滚:写回也不能恢复被撤销会话、清理的锁定/计数、token version、失效验证码或审计。它唯一用途是新凭据异常时提供一个已知格式正确的 hash。
- 旧 hash 最晚在
--verify-password-changedPASS 后立即销毁;若流程 24 小时内未完成,也必须销毁并记录recoveryMaterialDestroyed=true,不得无限保留。
R4 唯一写入与失败即停
- 唯一 break-glass 写入命令为
node src/scripts/reset-active-admin-password.js --apply ACTIVE_ADMIN_BREAK_GLASS,执行次数严格等于 1。 - 必须接受并记录脚本的认证副作用:password hash 更新、
must_change_password=true、三个认证失败/锁定域清零、password-reset request 状态清零、token version +1、仅目标账号活跃会话撤销、重置码/邮件码失效、写入SYSTEM_ADMIN_BREAK_GLASS_PASSWORD_RESET审计。 --apply非 PASS、SSH/容器通道中断、输出缺失/不可解析或目标变化时立即停止;不得盲重试第二次。先只读运行--verify定性实际状态,再回来取得新决策。- 不手写 password hash UPDATE,不修改角色、启用/归档状态、email、手机号或其他账号。
R5 写后验证与强制改密
- apply 后立即运行
--verify,要求:唯一目标、mustChangePassword=true、locksCleared、activeSessions=0、activePasswordResetCodes=0、activeEmailLoginCodes=0、auditPresent=true。 --verifyPASS 后,使用一次性 Playwright 浏览器 runner、单浏览器、零 retry 完成一次密码登录。runner 从 stdin/受控文件描述符读取凭据,不把密码放入 CLI argv、环境变量或持久 test spec;登录只允许/login→/change-password,必须观察mustChangePassword跳转,不得进入/portal后的业务页面。- 强制改密使用临时密码作为旧密码、独立生成的最终密码作为新密码;只提交一次。失败立即停止,不重复猜测。
- 改密成功后运行
--verify-password-changed,要求mustChangePassword=false、locksCleared、activeSessions=1、passwordChangeAuditPresent=true、breakGlassAuditPresent=true。 --diagnose-wecom-binding可在写前和最终验证后各运行一次,只记录绑定/同步/近期登录计数;不得尝试企微绑定、解绑或真机登录。
R6 Secret、清理与任务交接
- 最终密码经
--verify-password-changedPASS 后原子更新仓库外 live-smoke secret;文件必须为0600,不得留下临时副本或 shell history。 - 清理临时密码文件、浏览器 storage/cookie、旧 hash 恢复材料;只记录布尔结论,不记录实际位置或内容。
- 本任务不执行合同详情、终止按钮、员工权限负向或其他业务验收。重置任务完成后,恢复
08-08-production-authenticated-readiness,使用已确认最终凭据继续其只读 AC。 - 任一敏感值出现在 stdout、artifact、task 或对话即
FAIL;先移除/轮换暴露材料,再继续。
Acceptance Criteria
- AC1:执行来源 clean/published,生产身份、65 migration、产品 SHA 和运行脚本 SHA 门禁全部 PASS;未复制或替换生产脚本。
- AC2:
--preflight为 PASS/1/true,写前--diagnose-wecom-binding与认证状态快照完成;目标唯一且无绑定冲突后才进入写入。 - AC3:旧 hash 以
0700/0600受控短期材料保存,临时/最终密码由 CSPRNG 独立生成并满足策略;任何敏感值均未回显或进入仓库/task/chat。 - AC4:
--apply ACTIVE_ADMIN_BREAK_GLASS恰好执行一次且返回结构化 PASS;未执行手写 SQL 写入,未修改角色、启用状态、email 或其他账号。 - AC5:紧随 apply 的
--verifyPASS,脚本声明的 must-change、锁、会话、验证码、token/audit 副作用与实际计数自洽。 - AC6:浏览器仅完成一次管理员登录和一次强制改密提交,按预期进入
/change-password;到/portal后立即停止,业务 API 实际发送数为 0,失败自动 retry 为 0。 - AC7:
--verify-password-changedPASS,最终仓库外 secret 原子更新且0600;临时凭据、浏览器状态和旧 hash 均已销毁。 - AC8:生产账号写入仅限唯一管理员认证生命周期;其他员工会话/账号、合同、权限、schema 与业务数据写入计数为 0。
- AC9:只读 reviewer 已复核目标唯一性、apply 次数、副作用、secret 清理和证据脱敏;其核对对象为任务脱敏执行记录、源码与单测,不是独立生产重跑。task/process/governance/sensitive/diff 门禁全部 PASS。
- AC10:任务交接明确说明只恢复管理员认证,不代表合同登录态验收通过;
08-08-production-authenticated-readiness继续保持 PAUSED,直至其自身 AC 完成。
Out of Scope
- 合同台账/详情/终止按钮、员工权限负向、archived/terminated 样本或其他产品验收。
- 员工账号选择或密码恢复;邮件通道启用、SMTP 配置、生产重启、忘记密码验证码流程。
- 手写 SQL/Prisma 写入、角色/状态/email/手机号修改、创建新管理员、企微绑定/解绑或真机验证。
- 部署应用、修改产品代码、脚本、schema、migration、生产
.env或 feature flag。 - 把旧 hash 当作完整回滚,删除审计,或为恢复历史会话/计数执行额外写入。
Accepted Risk and Stop Conditions
- 生产只有一个活跃管理员。脚本写入后到强制改密完成前,管理员访问依赖临时密码;任何异常都必须停止,不能重复 apply。
- apply 与 change-password 都会递增 token version、撤销会话和失效认证挑战;这是设计内副作用,不得用旧 hash 假装恢复这些状态。
- 企微身份绑定记录不由脚本修改,但旧会话失效可能改变现有访问体验;只读诊断用于交接,不授权绑定写入。
- 若
--verifyBLOCKED、临时密码丢失或一次登录/改密失败,保留现场和有限恢复材料,在 24 小时上限内回来取得明确决策;禁止自行写回旧 hash。 - 合同终止回滚窗口可能被真实用户并发关闭;本任务只读记录,不通过停用功能或配置变更阻止使用。
Open Questions
- 无。强制改密被归类为重置生命周期,不是合同验收;合同与员工负向验收归还原 readiness task。
Test Plan
规划/本地
cd server && npm test -- tests/unit/scripts/reset-active-admin-password.test.jsnode --check server/src/scripts/reset-active-admin-password.jspython3 ./.trellis/scripts/task.py validate 08-09-production-admin-break-glass-resetnode scripts/qa/check-process-contract.mjs --baseline --jsonnode scripts/qa/check-trellis-governance.mjs --jsonnode scripts/qa/check-sensitive-content.mjsgit diff --check
获批生产窗口
- clean/published provenance、生产 read-only preflight、产品/脚本 SHA。
--preflight、写前--diagnose-wecom-binding、有限恢复材料存在性。- 单次
--apply ACTIVE_ADMIN_BREAK_GLASS,随后--verify。 - 仓库外 secret 更新;一次性 Playwright runner 通过 stdin 单次登录/强制改密,不把凭据放入 argv/env。
--verify-password-changed、写后企微诊断、窗口复查、secret/hash/browser 清理。
Evidence Rules
- 允许记录:commit/脚本 SHA 是否匹配、PASS/BLOCKED、目标计数、布尔、副作用计数、apply/login/change-password 次数和清理布尔。
- 禁止记录:管理员 identifier/email、临时/最终密码、旧/新 hash、cookie/token、生产路径、连接串、数据库/SSH 身份、原始 payload。
- 原始受控输出若包含敏感值不得进入 task;报告只保存稳定字段和相对 evidence 引用。