146 行
8.6 KiB
Markdown
146 行
8.6 KiB
Markdown
# 生产员工 Smoke Break-glass 重置:设计说明
|
||
|
||
## 1. 边界与分阶段授权
|
||
|
||
本任务修复专用 employee smoke 的认证能力,不改变员工角色或权限,也不执行合同负向验收。整个任务分三层:本地工具实现、正常候选发布、生产账号 reset。`task.py start` 只进入第一层;发布和账号写入各有独立审批停点。
|
||
|
||
新脚本必须通过正常发布进入镜像。带外复制虽然改动面看似更小,却绕过 provenance、运行脚本 SHA 和可复现发布合同,不能用于唯一一次生产认证写入。
|
||
|
||
## 2. 现有 helper 的真实副作用
|
||
|
||
`repair-oa-only-smoke-accounts.js` 的 dry-run 和 apply 共用 `planForTarget`:
|
||
|
||
```text
|
||
restore_active_employee -> status/employment_status/archived_at
|
||
clear_login_lock -> login_failed_* / login_locked_until
|
||
add_missing_roles -> INSERT employee_roles, never delete extras
|
||
every applied target -> token_version +1 + lifecycle event
|
||
password_hash -> never touched
|
||
session/challenge -> never revoked/invalidated
|
||
```
|
||
|
||
因此它既不能恢复密码,也不能建立精确角色不变量。本任务仅运行 dry-run 作为“现场仍无需生命周期/角色修复”的独立证明;任何 planned action 都阻塞新脚本 apply,helper apply 永久禁止。
|
||
|
||
## 3. 新脚本目标模型
|
||
|
||
新增 `reset-employee-smoke-password.js`,复用现有 helper 导出的 smoke 纯判据,但不调用其 `execute/applyTarget`。目标由数据库状态自行发现,不传 identifier:
|
||
|
||
```text
|
||
status == active && employment_status == active && archived_at == null
|
||
&& email/phone identifier ready
|
||
&& current login lock is clear
|
||
&& must_change_password == false
|
||
&& smoke(row.employee_no | row.email | row.name)
|
||
&& sorted(roleCodes) == ["employee"]
|
||
&& resolvePermissionsForRoles(roleCodes)
|
||
excludes ["*", "project.manage", "project.view_all", "system.settings"]
|
||
```
|
||
|
||
查询最多保留两个匹配用于判断唯一性,稳定输出只包含 count/boolean。角色精确断言同时保证没有管理员、经理、财务、人事等额外角色;权限断言独立存在,防止未来 `ROLE_PERMISSIONS.employee` 漂移后角色仍正确但权限已扩大。
|
||
|
||
## 4. 状态机
|
||
|
||
```text
|
||
PLANNING
|
||
-> local implementation/tests/read-only review
|
||
-> STOP: deployment approval
|
||
-> commit/push/remoteVerified -> normal deploy -> runtime script SHA match
|
||
-> production read-only identity/migration/window
|
||
-> repair helper dry-run: OK/no actions, apply=0
|
||
-> employee script --preflight: PASS/1/exact role/no manage permissions
|
||
-> old-hash limited material + temp/final secrets ready
|
||
-> STOP: production account-write approval
|
||
-> --apply EMPLOYEE_SMOKE_BREAK_GLASS exactly once
|
||
-> --verify + independent role/permission snapshot
|
||
-> only if post-write negative premise PASS: one login
|
||
-> /change-password once -> stop at /portal
|
||
-> --verify-password-changed + final role/permission snapshot
|
||
-> secret update + sensitive cleanup + read-only reviewer
|
||
-> RESET_COMPLETE -> handoff 08-08 readiness
|
||
```
|
||
|
||
任一箭头失败即停止。发布失败不会触及账号;apply 结果不确定时仅 `--verify`,不得重试;写后权限不变量失败时不得登录。
|
||
|
||
## 5. 命令接口与稳定输出
|
||
|
||
脚本接口:
|
||
|
||
- `--preflight`:目标计数、唯一性、smoke/identifier/status/role/permission 布尔。
|
||
- `--verify`:上述不变量 + must-change、locks、active session/code、audit 状态。
|
||
- `--verify-password-changed`:上述不变量 + must-change=false、一个新 active session、两类 audit。
|
||
- `--diagnose-wecom-binding`(可选只读):仅记录绑定/同步/近期认证计数,不输出 external user id。
|
||
- `--apply EMPLOYEE_SMOKE_BREAK_GLASS`:唯一写入口,密码从 stdin。
|
||
|
||
错误只输出 allowlist category,例如 `employee_smoke_target_not_unique`、`employee_smoke_target_changed`、`explicit_confirmation_required`、密码输入/策略错误;未知异常只输出安全类别,不回显数据库消息或目标字段。
|
||
|
||
## 6. 事务写入合同
|
||
|
||
apply 在事务外发现唯一目标并取得 token version,生成同源 bcrypt hash;事务内再次读取并核对所有目标/权限不变量。通过后仅执行:
|
||
|
||
1. 更新目标 `password_hash`、`must_change_password=true`;清三类认证失败/锁定字段与 password-reset request 字段;token version +1。
|
||
2. 以 `employee_id=target.id` 撤销 active auth sessions、失效 password reset codes、消耗 email login codes。
|
||
3. 写 `SYSTEM_EMPLOYEE_SMOKE_BREAK_GLASS_PASSWORD_RESET` 审计,actor/target 均为目标,extra 只记录 `credentialRecorded=false`。
|
||
|
||
事务不包含 `employee_roles`、status/employment/archive、identifier、企微或业务表。单测必须对这些缺席路径做断言,而不只验证预期 update 存在。
|
||
|
||
## 7. 严格一次与不确定结果
|
||
|
||
执行前记录专用 attempt 哨兵和本次审计基线;一旦发出 apply,`applyExecutionCount` 固定为 1。结果分支:
|
||
|
||
- 完整 `PASS`:立即 `--verify`。
|
||
- 明确 `BLOCKED`/非零:停止,不重试。
|
||
- SSH 中断、输出缺失、JSON 不可解析:标记 `APPLY_UNCERTAIN`,只运行 `--verify`。
|
||
|
||
verify 若显示 must-change、audit/session/code 等状态已落地,则按成功写入处理并禁止第二次 apply;审计计数相对写前恰好 +1 作为独立交叉证明。
|
||
|
||
## 8. 写后权限再确认
|
||
|
||
写前诊断不是写后证据。apply 后先由脚本 `--verify` 重算目标、角色和权限,再以独立 `BEGIN READ ONLY` 查询重算同一组不变量:
|
||
|
||
```text
|
||
targetMatches=1
|
||
rolesExact=true
|
||
hasWildcard=false
|
||
hasProjectManage=false
|
||
hasProjectViewAll=false
|
||
hasSystemSettings=false
|
||
```
|
||
|
||
任何一个值不符都停止在无登录状态。不得通过删除角色、改权限或改验收断言补救;这类生产写入需要新任务。
|
||
|
||
## 9. 凭据、强制改密与恢复材料
|
||
|
||
临时/最终密码由受控 CSPRNG 分别生成,均满足应用策略且不相同。临时密码通过 stdin 进入 apply;正式仓库外 employee secret 仅在 verify PASS 后原子更新为正确 identifier + 临时密码,强制改密完成后再替换为最终密码。
|
||
|
||
旧 hash 只在生产现场 `0700/0600` 暂存,最晚 apply 后 24 小时销毁。它不是完整或部分回滚;即使另行获批写回,也不能恢复 token、会话、challenge、计数和审计。
|
||
|
||
浏览器使用单 context、workers=1、retries=0:一次登录必须到 `/change-password`,一次提交允许到 `/portal` 后立即关闭;不得主动进入业务功能或发送非认证业务 mutation。`/portal` 自然落点的被动只读 shell 请求单独计数,不与主动业务访问混为一谈。随后 `--verify-password-changed` 和最终独立权限快照都 PASS 才清理材料。
|
||
|
||
## 10. 发布与脚本身份
|
||
|
||
本地实现和只读 reviewer 通过后形成 clean/published 候选。生产发布沿用现有 identity bootstrap、preflight、migration/window 和 `deploy-existing.sh`,不手工同步单文件。
|
||
|
||
发布后以 `git show <candidate>:server/src/scripts/reset-employee-smoke-password.js` 为基线,对运行容器同路径做 SHA-256 比较;只记录 `scriptShaMatch=true`。脚本不存在或不一致即停止账号阶段。候选没有 migration 时 pending 必须为空;任何意外 migration 或生产门禁 finding 都停止。
|
||
|
||
## 11. 失败矩阵
|
||
|
||
| 阶段 | 失败 | 动作 |
|
||
| --- | --- | --- |
|
||
| local tests/review | 非 PASS | 修复本地代码,不发布 |
|
||
| deploy approval/provenance | 缺失或不一致 | 停止,零生产变更 |
|
||
| deployment | 失败或运行 SHA 不符 | 停止,零账号写入 |
|
||
| helper dry-run | planned action / unsafe target | PAUSED,helper apply=0 |
|
||
| employee preflight | 非唯一/角色或权限不符 | 停止,不选择目标 |
|
||
| recovery/secret | 无法安全准备 | 停止,不 apply |
|
||
| apply 明确失败 | BLOCKED | 停止,不重试 |
|
||
| apply 通道不确定 | 无完整结果 | 仅 `--verify` 定性 |
|
||
| post-write role/permission | 任一不变量失败 | 禁止登录,另行决策 |
|
||
| login/change-password | 首次失败 | 停止,零 retry,不再 apply |
|
||
| cleanup | 失败 | 标记敏感清理未完成,不归档 |
|
||
|
||
## 12. Reviewer 与交接
|
||
|
||
本地实现后必须启动只读 reviewer,重点检查目标发现、角色/权限精确断言、事务写集、失败类别和测试反例;reviewer 不改代码。生产执行后再由只读 reviewer 核对候选/脚本 SHA、apply 次数、审计增量、目标限定副作用、写后权限证据和材料清理。
|
||
|
||
最终只向原 readiness task 交接稳定布尔与计数,不交接凭据。该 task 才负责登录后的合同负向验收和最终 readiness 口径。
|