Files
railwaysystem/docs/iot/ROUND5S_SAFETY_HARDENING.md
T

122 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Round 5S — 真实 IoT 安全收口与验证边界
基线:`373921447fa4727c20aed7b28c5d1519fe26fa78`。仅本地修复;未部署、推送、修改共享数据库、连接真实 MQTT 或操作真实人脸服务。
## 1. 持久化契约
复用 `iotlocks.lock_auth` 与 `iotlocks.lock_command`。新增来源模式、稳定环境 ID、意图 UUID、递增业务版本、意图方向/时间、执行状态。命令另存明确设备 SN 与发送尝试开始时间。元数据由服务端生成,JSON 请求不能注入。
`status/sync_status` 保留原界面兼容语义,但不能单独证明设备已撤权。期望状态在授权意图中,发送状态在命令中,设备确认在双回执字段中。
| 执行状态 | 意义 | 恢复规则 |
| --- | --- | --- |
| READY | 当前来源、当前环境、当前版本,尚未尝试发送 | 经身份、协议及写入门禁后可以恢复;PENDING 或连接失败导致的 FAIL 均不丢失意图 |
| DISPATCHING | 数据库已提交发送占用,传输可能尚未执行或结果未知 | 禁止盲重发;超时进入人工核对 |
| AWAITING_ACK | 传输调用返回,但设备结果未确认 | 禁止自动或人工命令重发;等待严格关联回执,超时人工核对 |
| CONFIRMED | 当前版本的门锁成功回执与网关回执齐备 | 授权确认成功或撤权确认清除;重复回执不改写 |
| REJECTED | 设备明确返回失败 | 意图进入人工核对,不能把 FAIL 当成重发许可 |
| MANUAL_REVIEW | 无法证明物理结果、旧操作仍可能影响设备 | 不宣称成功、不取消物理结果、不生成后继授权绕过问题 |
| QUARANTINED | LEGACY、Mock/真实来源不符、环境不符、非法身份或载荷 | 不重放、不自动升级为 REAL |
| CANCELED | 操作者取消了尚未尝试发送的命令 | 记录保留;未完成意图进入核对,不能伪称设备撤权 |
服务器启动时间不再作为可信来源判断依据。兼容属性 `replayProtectionEnabled` 即使关闭,也不能关闭来源、环境、版本和发送占用校验。
相同环境 ID 必须跨重启保持稳定;不能把进程启动时间或随机进程 ID 当作环境 ID。切换为另一环境时,旧意图保持隔离。
## 2. 事务与恢复
1. 入住/退宿业务事务内持久化授权或撤权意图;退宿失败不能吞掉意图写入异常后仍声称成功。重复退宿仍执行撤权对账。
2. 同一意图 UUID 最多创建一条命令,数据库唯一索引作为最终保护。
3. 命令只落入 outbox;外发必须在事务提交之后。
4. 独立事务按“授权行 → 命令行”顺序锁定并校验当前版本,把发送占用及尝试时间提交到数据库。
5. 传输边界重新核验持久化占用、设备映射、载荷及权限;只执行一次发送尝试。
6. 不确定异常、占用后崩溃、回执超时均进入核对。无协议幂等或查询证明时,不自动重发。
7. 回执校验 Topic、seq、op、action、设备 SN、结果码及当前意图版本。命令确认和授权投影在同一数据库事务中完成。
READY 意图没有命令时可补建;已有 READY 命令由命令恢复器处理。失败/未知命令不再无限阻止恢复,也不会被补偿器绕过后重复生成。恢复扫描优先撤权,不因固定前 100 条策略阻塞后续合法意图。
旧版本回执无法更新新版本授权。未知旧撤权不能被本地“取消”后立即重新授予权限。
人工核对入口为现有授权/命令查询 API 返回的 `executionState`、`intentAction`、版本和静态原因。此次不增加能够把未知结果强行改成成功的 API。操作人员应保存设备侧证据,经批准后的下一阶段处理;不得直接批量修改数据库状态或来源标记。
## 3. 身份与协议
真实白名单为空、SN 为空或未知时一律拒绝。身份取明确设备 SN,再验证“设备 → 网关 → 分组”的数据库映射及授权快照。分组只负责路由,不是白名单身份。
本轮支持已有协议证据中的人脸/密码授权结构:
| 业务命令 | 帧 op | params |
| --- | --- | --- |
| 0011 人脸授权 | facefeature | add、8 位人员编号、特征、人员编号、起始时间、结束时间、设备 SN、设备密码 |
| 0011 密码授权 | password | add、8 位人员编号、密码、起始时间、结束时间、设备 SN、设备密码 |
| 0012 撤权 | 对应授权类型的 op | delete、8 位人员编号、设备 SN、设备密码 |
Topic 为已有 `ydlink/{groupCode}/thing/data/batch`。seq 与命令流水一致,保持既有信封顺序;空载荷、错人员/目标/路由/类型、缺凭据或非法时间窗口在发布前拒绝。
不新增设备协议字段,不声称本地 UUID 被硬件理解,也不假定硬件支持幂等。既有 `chksum=xx`、固件和时区等仍需老师原始协议/报文独立确认;`iot.mqtt.protocol-verified` 默认 false,在获得证据之前真实传输被拒绝。未确认的指纹、IC 卡、网关配置和其他真实控制操作在此分支拒绝,不能扩大设备白名单来绕过。
## 4. 外发约束
- 原始 MQTT publish 在真实模式永久拒绝。运维 Controller 仅在 `iot.mqtt.mock=true` 且 QA API 显式开启时加载,并要求专门权限 `iot:mqtt:manage`。该权限未自动授予任何外部账号。
- 真实命令只能使用 command-aware 传输,并提供已提交的发送占用。所有授权、撤权、补偿、人工重试均走同一门禁。
- MQTT 默认不连接,手动 connect/reconnect 也检查模式与连接开关。所有遗嘱消息被拒绝,避免绕过 outbox。控制报文禁止 retained。
- 控制发送使用 QoS 0(at-most-once),禁用 Paho 自动重连、强制 clean session、拒绝文件报文持久化,防止客户端/会话队列绕过 outbox 自行重发。连接健康检查只创建无历史队列的新会话。丢失或送达不确定进入人工核对;该传输策略还需隔离 Broker 与老师端兼容验证,不能以提高 QoS 的方式绕过未知结果保护。
- OpenAPI 默认关闭;无密钥、短密钥和错误密钥拒绝。过滤器锚定真实 Controller 路径并处理 context path,配置路径不能绕过鉴权。
- 人脸 Mock 默认 true,优先于数据库 endpoint,直接返回合成结果。真实 HTTP 另需显式开关、环境一致、目标设备授权和精确端点白名单;禁止重定向。数据库 endpoint 无法越过白名单。
- 人脸图片只接受上传目录内的真实路径,拒绝外部 URL、目录穿越、符号链接越界及超大文件。Mock 提取不下载任何图片。
- 旧 DMS 不具备此分支的设备来源契约,真实网络被禁止,仅保留显式启用且端点为 loopback 的 Mock 回归通道,禁止重定向。
- 定时补偿默认关闭。开启后也不能发送旧 Mock、未知来源、发送结果未知或旧版本命令。
## 5. 安全迁移方案(未在共享环境执行)
脚本:`RuoYi-Vue/sql/iot/005_round5s_execution_safety.sql`。
这是增量字段与索引迁移,不删除/重建表,不创建账号,不修改权限。历史行默认 `LEGACY / version 0 / QUARANTINED`;禁止按日期或设备编号自动回填 REAL。空旧意图键保留为空,现有记录不被重新发送。
管理员后续须先备份并批准目标库、停止旧写入进程,在隔离 Kingbase 数据库验证脚本、Mapper、生成键、锁顺序及事务语义,再考虑批准应用到共享库。不能使用全量 qa-bootstrap 重新初始化已有库。回退时保留新增字段和证据,不自动删除列;回退旧应用也不得开启真实连接。
已在隔离 H2 PostgreSQL 模式测试脚本重复执行、默认隔离和唯一键。H2 不能替代 Kingbase 的方言及运行环境验收。
## 6. 认证值与历史资料
JWT 仅接受运行环境注入的 Base64 HS512 密钥(解码后至少 64 字节)。缺失/非法时登录签发安全失败,没有固定弱值回退。OpenAPI 使用注入密钥。Druid 控制台默认关闭,显式开启而无合格注入凭据时启动校验失败。
新 QA 种子不保留可直接登录的共享密码哈希,测试身份默认禁用。旧确定性临时密码降级被拒绝,即使旧开关被误开启也不会生成可预测密码。开发 Swagger 用户测试 API 默认禁用,示例中不保存密码或手机号。
管理员待轮换/复核清单(本轮未操作外部账号):
| 范围 | 所需处置 |
| --- | --- |
| 历史 application.yml JWT 值 | 更换密钥,确认旧 Token/会话失效策略 |
| 历史 OpenAPI 测试密钥 | 废止已部署固定值,仅在批准环境启用新密钥 |
| 历史 Druid 控制台凭据 | 禁用或轮换并核验实际访问控制 |
| 实验工程 DB/MQTT/启动脚本凭据 | 管理员核实仍有效的账号后轮换;本轮未复制或推送 |
| 既有 QA/共享库预置账号 | 审核、禁用或轮换;修改种子不会改变已经存在的账号 |
| 实验历史 iotlocks.dmp、人脸上传文件 | 限制传播并核对留存权限;不改写旧 Git 历史 |
| 继承依赖测试私钥、pay.png/profile.jpg 等素材 | 区分公开测试夹具与实际秘密,审核素材来源;旧提交和静态资源未自动清理 |
锁屏默认头像改用已有通用角色素材。源仓库历史、原工程和实验工程均保持不变,不 Force Push。
## 7. 自动化证据与未验证范围
`Round5sExecutionIntegrationTest` 使用真实 `LockAuthServiceImpl`、`LockCommandServiceImpl`、生产 MyBatis XML、DataSourceTransactionManager 和新建本地临时文件 H2 库。每次连接关闭后由文件重新打开,测试数据库不连接服务器。只有设备映射查询、MQTT 传输及回执是可控替身。
覆盖空/合法/非法白名单、分组冒充、人脸/密码及撤权载荷、Mock 切换隔离、旧请求时间恢复、意图提交后命令创建前中断、提交发送占用后中断、未知回执、明确设备拒绝、旧回执保护、错 Topic/设备、写入/连接关闭、协议未批准、回滚、并发发送占用、增量迁移及原始 MQTT/遗嘱旁路。
另有 YAML 人脸绑定/端点隔离、OpenAPI context path/密钥、JSON 元数据注入、JWT/Druid 认证、DMS 边界以及原业务桥接定向回归测试。桥接替身测试不作为真实恢复机制证明,旧“取消 SENT 后重新授权”和可预测密码测试已改为拒绝的安全语义。
重现命令(只运行选定测试,不运行数百项全量测试):
```sh
mvn -o -f RuoYi-Vue/pom.xml -pl ruoyi-admin -am \
'-Dtest=Round5s*,RealIotSecurityGateTest,FaceExtractClientMockTest,CommandRespHandlerMockTest,DmsHttpClientTest,DmsTokenManagerTest,TrainIotLockAuthBridgeMockTest' \
-Dsurefire.failIfNoSpecifiedTests=false test
mvn -o -f RuoYi-Vue/pom.xml -pl ruoyi-admin -am -DskipTests package
```
本轮自动化结果:81 项定向测试通过,失败/错误/跳过均为 0;Java 21 下九模块后端打包成功。授权、命令和锁屏三个 Vue 模板经隔离安装的 Vue 2.6.12 编译器检查无错误,脚本及状态标签工具通过语法检查;未完成前端全量 bundle 构建或浏览器验收。处理状态在授权/命令页面显示,未知结果不能通过“重发/取消”按钮操作,提示不再把请求受理写成设备已执行。
本轮未证明:Kingbase 实际升级与整套住宿事务、新配置下完整 Spring Boot 启动、跨进程/多实例故障注入、老师原始协议 checksum/固件/设备幂等、真实 Broker ACL/TLS、现场设备在线状态、实际门锁授权撤权以及管理员凭据轮换。
结论:软件安全逻辑可继续隔离验证,不能把本轮测试写成真实门锁联调成功;真实写入、网络连接、人脸 HTTP、OpenAPI 和自动补偿的默认开关保持关闭。