test-spec.md 13 KB

单测 Spec — 框架加密密钥生命周期加固

status: done created: 2026-07-26

0. 测试原则

  • Red/Green TDD:每个 Task 的行为测试先在旧实现上失败,再实施最小修复。
  • First Run the Tests/apply 前先运行 Starter Crypto 现有测试和相关模块编译,记录真实基线。
  • 增量复用:复用阶段一已验证的 JDK 17、Node 20.19.0、Admin 聚合 package 与前端 build 命令。
  • 安全证据:除单测外必须扫描历史根密钥、部署级密钥字段、日志输出和 Flyway 占位符。
  • 不碰生产数据:自动验证不连接生产库、不执行真实迁移、不生成可提交密钥。
  • 失败关闭优先:错误密钥、未知 keyId、损坏密文、非法配置和容量不足必须有反向测试,避免“只测成功路径”。

1. 测试框架

项目
JUnit 版本 JUnit Jupiter 5,由 Spring Boot 3.5.13 BOM 管理
Mock 框架 Mockito + AssertJ/JUnit Assertions
现有测试基线 Starter Crypto 1 个测试类/3 条;Starter Config 无测试目录;Generator 有既有专项测试,Data 暂无加密专项测试
数据访问验证 Mockito/测试 JdbcTemplate + Mapper XML/SQL 合同测试;有隔离测试库时再做可选 E2E
前端验证 Node v20.19.0 + pnpm build,本次 UI 仅删除字段,不做视觉改版

2. 覆盖范围

P0 — 密钥配置与持久化协议(必须覆盖)

类名:CryptoConfigurationValidator

场景 配置 预期结果
传输加密启用且根密钥空 enabled=true 启动校验失败并指出 FORGE_CRYPTO_SECRET_KEY
根密钥非法 Base64/错误长度 任一启用路径 启动校验失败,不回显配置值
兼容写入 writeVersioned=false 要求旧格式写入密钥,不要求活动 key
版本化写入 writeVersioned=true 要求合法 activeKeyId 和活动 key
历史 keyring 合法/非法 keyId、重复/空 key 合法通过,非法失败
全部能力关闭 不配置密钥 不因无关密钥失败

类名:CryptoSecretEnvironmentPostProcessor

场景 输入 预期结果
首次新安装 无显式根密钥、密钥文件不存在 原子生成两个独立 16 字节 Base64 密钥,注入 Spring Environment
重启 密钥文件已存在 复用全部原值,文件不重写
显式配置 环境/JVM 提供非空根密钥 跳过自动生成,不创建文件
损坏文件 缺少必需字段、非法 Base64 或错误长度 启动失败,不重生成
权限 POSIX 文件系统 目录 0700、密钥文件 0600
空白 Compose 变量 容器传入空字符串 按未配置处理,由密钥文件值覆盖空白值

类名:VersionedPersistentCryptoService

场景 输入 预期结果
兼容写 writeVersioned=false 输出旧无版本 Base64,可被旧/新服务读取
活动 key 写 writeVersioned=true 输出 FPC1:算法:keyId:payload,不含明文
活动 key 读 当前 keyId 正确解密
历史 key 读 轮换前 keyId 正确解密,新写仍使用活动 keyId
旧密文读 legacy key + legacy algorithm 正确解密
未知 keyId 合法格式但 key 不存在 失败关闭
头/算法/payload 损坏 多种非法输入 失败关闭,不返回原值
缺旧钥 无版本密文 失败关闭
null/空值 null/空字符串 按接口合同原样处理,不误分类
重加密 旧/历史/活动格式 旧和历史转活动;活动格式保持幂等

P1 — 配置暴露面

类名:CryptoConfigExposureTest / CryptoConfigSanitizerTest

场景 输入 预期结果
管理 GET crypto 配置 JSON 不含根密钥、RSA 私钥、activeKey、keys、legacyKey
管理 PUT 非敏感开关 正常保存并同步
管理 PUT 敏感字段 明确拒绝,不写数据库
ConfigConverter 含历史敏感字段 JSON 属性 map 不产生部署级密钥键
通用 group list/page/detail crypto 原始 JSON 返回前完成清洗
通用 group add/edit 含敏感字段 明确拒绝
散配置 add/edit/改名 部署级 crypto key 明确拒绝,不写数据库
普通敏感散配置 文件存储等既有配置 继续使用既有掩码保留语义,不扩大禁用范围
非 crypto 分组 普通 JSON 不受敏感清洗误伤

Flyway 合同

  • 脚本版本为 V1.0.52 且仓库无重复版本。
  • sys_config 删除目标覆盖根密钥、RSA 私钥和 persistence keyring。
  • sys_config_group 只清理 group_code='crypto' 的敏感 JSON 路径。
  • 脚本不包含历史密钥值、真实密钥或 ${...} Flyway 占位符。
  • 安全物理删除原因和回滚方式已写入 Spec/Runbook。

P1 — 数据连接加密与迁移

类名:DataConnectionCryptoLifecycleTest

场景 输入/Mock 预期结果
新增密码 版本化写开启 保存 FPC1,不保存明文
修改空密码 已有密文 保留原密文
读取旧密码 legacy 密文 JDBC 获得明文密码
读取新密码 active/historical JDBC 获得明文密码
错误 key/损坏密文 解密抛异常 连接创建失败,不把密文当密码

类名:DataConnectionCryptoMigrationServiceTest

场景 预期结果
inventory 仅计数分类,不返回敏感值
dry-run 不执行 UPDATE
execute 旧格式变活动格式,更新条件含 id + 原密文
并发修改 条件更新 0 行,计冲突,不覆盖新值
重复执行 活动格式跳过,结果幂等
单行失败 批次回滚/失败计数,其他批次按合同继续

P1 — 低代码字段加密与迁移

类名:DynamicCrudCryptoLifecycleTest

场景 输入 预期结果
SM4/AES 写入 camelCase 配置 + snake_case 数据 指定列写版本化密文
旧/活动/历史读取 多字段混合 全部正确解密
非字符串/空字段 null/数字/空串 按既有合同跳过
非法算法/未知 keyId/损坏值 任一字段 当前操作失败,不返回或落库原值
encrypt_config 非法 非对象/字段规则非法 明确业务错误

类名:LowcodeCryptoMigrationServiceTest

场景 预期结果
未显式 configKey 拒绝执行
主库/外部库 使用对应 RuntimeJdbcTemplate 与方言
数据源只读 BLOCKED,不执行写入
表/字段不存在 BLOCKED
非单主键/非法标识符 BLOCKED
列容量不足 BLOCKED,不自动 DDL
dry-run 返回分类和容量结果,不 UPDATE
execute 参数化批次更新,按当前租户,原值比较
重跑/并发冲突 幂等并正确计数

P2 — 入口、装配与静态证据

  • ConfigManageController 只允许平台管理员调用 migration API。
  • migration execute 默认 dryRun=trueexpectedActiveKeyId 不匹配时拒绝。
  • Admin/Report 均能装配 PersistentCryptoService 和 Data 插件依赖。
  • 管理端构建通过,crypto 表单不再读写部署级密钥字段。
  • YAML 显式 Secret 仍为空默认且不包含历史密钥;启动引导开关和外部文件路径可通过环境变量覆盖。
  • EncryptTypeHandlerSysClient 死引用清零。
  • 浏览器 CryptoRuntimeConfig/SessionKeyStore 仍存在,API 会话协议未被误删。

不测试(明确原因)

  • 不执行生产库真实迁移:需要生产备份、真实 keyring、租户清单和维护窗口。
  • 不调用外部低代码数据源:仓库没有可提交的凭据;使用 Mock/测试数据源验证路由和 SQL 合同。
  • 不重写 Git 历史:历史清理是仓库治理操作,轮换后旧钥失效即可形成运行安全边界。
  • 不升级 SM4/AES 加密模式:本变更解决密钥生命周期;算法模式升级需独立密码学评审和新协议版本。
  • 不新增 UI E2E:配置中心只删除字段,生产 build 和静态选择器扫描足以覆盖本轮 UI 差异。

3. 执行计划

  • Step 1:使用 JDK 17 运行 Starter Crypto 现有测试,记录实施前基线。
  • Step 2:Task 1/3/4/6 配置与协议测试 Red → Green。
  • Step 3:Task 7 数据连接测试 Red → Green。
  • Step 4:Task 8/10 低代码测试 Red → Green。
  • Step 5:Task 9/11 迁移服务与入口测试 Red → Green。
  • Step 6:执行相关 Reactor 测试、Admin/Report package 和 UI build。
  • Step 7:执行 YAML/JSON/SQL/敏感值/死代码/Flyway 静态扫描和 git diff --check
  • Step 8:完成 Spec Compliance → Code Quality 两阶段审查并增量修复。
  • Step 9:实现 Task 13 启动自动引导,执行定向 Reactor 测试、最小 SpringApplication 合同和 Compose 解析。

4. 历史验证基线

时间 范围 命令 结果 备注
2026-07-26 阶段一 Admin 聚合 JDK 17 Admin 43 模块 package 通过 复用 framework-captcha-security-hardening/execution-log.md
2026-07-26 阶段一前端 Node 20.19.0 pnpm build 通过 8725 模块,存在既有 Vite 警告
2026-07-26 18:54 CST 本 Proposal 文档结构、行尾/EOF、敏感值扫描 通过 HARD-GATE 前不运行代码测试

5. 本轮增量验证

时间 变更范围 必跑项 实际命令 结果 跳过/警告
2026-07-26 18:54 CST Proposal 文档 行尾/EOF、章节/状态一致性、历史密钥精确扫描 rg + tail/od + 章节断言 通过 四份文件均以换行结束,无尾随空白,不含两处已知硬编码密钥值;仅文档,不运行代码测试
2026-07-26 21:22-21:53 CST 配置、协议、数据连接、低代码和迁移入口 Red/Green 与批次事务回归 Reactor test-compile + /tmp/ForgeJUnitRunner 隔离矩阵 通过 Red 捕获 2 条批次原子性失败和 1 条非法 JSON 失败;修复后批次事务 12 条及各目标组通过
2026-07-26 22:02 CST 最终目标测试 Config、Crypto、Data、Generator/System/Admin Reactor 当前 target/classes 隔离执行 通过 Config 8、Crypto 22、Data 7、Generator/System/Admin 22,共 59 条,0 失败
2026-07-26 22:02 CST Admin/Report 测试编译 44 模块 -Penable-tests -DskipTests test-compile JDK 17 Maven Reactor 通过 44/44 SUCCESS;测试源码全部编译
2026-07-26 22:07 CST Admin UI Node 20.19.0 pnpm build Vite 生产构建 通过 8725 模块,1 分 33 秒;保留既有组件命名、CSS 注释和分块警告
2026-07-26 22:08 CST Admin/Report 聚合包 mvn -pl forge-admin-server,forge-report-server -am package -DskipTests JDK 17 Maven Reactor 通过 44/44 SUCCESS,38.178 秒
2026-07-26 22:09 CST 最终静态安全证据 敏感值、Flyway、初始化 SQL、死代码、协议保留、diff rg/find/git diff --check 通过 受管范围历史根密钥和旧种子密文均 0;Flyway 无重复/占位符;SessionKeyStore 链路保留
2026-07-27 Task 13 启动引导 首启/重启/显式优先/空变量/损坏/并发/权限/SpringApplication 自动注册 Starter Crypto Reactor 目标测试 通过 自动引导 8/8;与原配置校验合计 15/15;不启动真实服务
2026-07-27 Docker 密钥持久化 Compose 环境变量与 crypto_secrets 命名卷 docker compose config --quiet 通过 未构建镜像、未启动容器

基础设施警告

  • 直接插件测试会解析本地仓库中的陈旧 forge-starter-crypto jar,目标测试编译失败;最终证据使用已成功编译的 Reactor target/classes 置于 classpath 首位执行。
  • Generator 常规 Reactor test 仍受仓库既有 Surefire groups/excludedGroups 与测试引擎配置影响;44 模块 test-compile 和隔离 JUnit 结果已分别覆盖编译与行为。
  • 一次聚合 package 未显式固定 JDK,落到系统 Java 8 后以“无效的目标发行版: 17”失败;同一命令固定 JDK 17 后通过,不属于代码失败。
  • 未启动后端、前端开发服务器、数据库或 Redis;未产生需要停止的服务 PID。

6. 执行证据

  • execution-log.md:逐 Task 追加命令、关键输出、失败根因和跳过项。
  • 关键接口:/api/config/manage/crypto/api/config/group/**、crypto migration inventory/execute、数据连接和低代码既有 CRUD。
  • 关键数据库检查:sys_config/sys_config_group 敏感字段清理,password_cipher 和动态字段格式分类;真实结果仅在隔离测试库/生产变更单留档。
  • 服务启动与停止:默认不启动服务;如需装配验证,只停止本轮明确启动的 PID。