status: review created: 2026-07-26 complexity: 🔴复杂
当前 Admin 与 Report 的默认配置将同一根密钥明文提交到 Git,配置中心又允许从数据库、管理 API 和前端表单读取或修改该密钥。进一步排查确认,该密钥不仅是 API 动态密钥不可用时的降级密钥,还间接用于加密持久化数据:数据连接密码和低代码动态业务字段。
因此不能直接替换旧密钥。直接替换会导致 ai_report_data_connection.password_cipher 及已经启用 ai_crud_config.encrypt_config 的业务字段无法解密。本变更按兼容发布、启用新写入、存量迁移与退役三个里程碑完成以下可验证结果:
keyId;活动密钥负责新写入,历史密钥只负责解密。SessionKeyStore 和前端会话密钥状态。本变更不执行生产数据库迁移、不提交真实密钥、不重写 Git 历史。新安装可由启动引导器首次生成并持久化运行密钥;已有环境的 legacy key、生产 Secret Manager 注入和迁移命令执行仍由部署人员在代码验证通过后完成。
forge-server/forge-admin-server/src/main/resources/application.yml#forge.crypto.secretKeyforge-server/forge-report-server/src/main/resources/application.yml#forge.crypto.secretKeyforge-starter-core/.../CryptoProperties 将根密钥绑定到 secretKey。forge-starter-config/.../DbPropertySourcePostProcessor#postProcessEnvironment 通过 addFirst 注册数据库配置源,优先级高于环境和 YAML。forge-starter-config/.../ConfigConverter#convertCryptoConfig 将 secretKey、RSA 公私钥写入 forge.crypto.*。ConfigManageController#getCryptoConfig/updateCryptoConfigSysConfigGroupController 的 /api/config/group/** 通用 CRUDSysConfigController 的 /system/config/** 散配置 CRUDforge-admin-ui/src/views/system/config-center.vue 的加密配置表单EncryptorFactory 注册 SM4Encryptor 与 AESEncryptor;无参 encrypt/decrypt 从 CryptoProperties.secretKey 取默认密钥。EncryptResponseBodyAdvice、DecryptRequestBodyAdvice 优先使用浏览器会话密钥,无会话密钥时回退默认根密钥。DataConnectionController#convertToEntity 用默认根密钥加密并写入 ai_report_data_connection.password_cipher。JdbcDataSourceProvider#decryptPassword 用默认根密钥解密后创建 Hikari 数据源。DynamicCrudService#applyEncrypt/applyDecrypt 按 ai_crud_config.encrypt_config 中的算法,用默认根密钥处理动态业务表字段。EncryptTypeHandler 内含固定密钥;唯一引用是 SysClient 中已注释的 @TableField。sys_config_group 初始化数据包含 secretKey、rsaPrivateKey 等字段;通用配置分组接口会把原始 config_value 返回前端并允许整体覆盖。/system/config 已对敏感键查询结果做脱敏,但 SysConfigServiceImpl#insertConfig/updateConfig 仍允许新增或修改 forge.crypto.* 部署级密钥,DbConfigLoader#loadSysConfig 会把 config_type='Y' 的值加载为高优先级配置。ConfigManagerService#getCryptoConfig 直接反序列化为 CryptoProperties,因此管理 API 会序列化运行密钥字段。CryptoProperties 没有启动期校验。错误密钥通常直到某次加解密才报错,故障被推迟到运行期。JdbcDataSourceProvider#decryptPassword 捕获解密异常后返回原始密文,随后把密文作为 JDBC 密码使用,掩盖真实的密钥故障。DynamicCrudService#applyEncrypt/applyDecrypt 捕获顶层异常后只记 WARN;写入可能继续保存明文,读取可能继续返回密文/明文,无法形成可靠安全边界。encrypt_config.algorithm 才能确定旧密文算法。encrypt_config 大多为空,但不能据此推断生产库不存在低代码加密字段。VersionedKekCapabilityPayloadCrypto,证明项目接受“活动 keyId + 历史 key map + 缺钥失败关闭”的模型;该实现服务于审批载荷,不直接复用其数据格式。sys_config/sys_config_group 仍可让旧密钥继续生效。${FORGE_CRYPTO_SECRET_KEY:},Docker 示例只声明变量名,不提供默认密钥。ConfigConverter 不再从数据库配置分组生成任何根密钥、RSA 私钥或持久化密钥环属性。/api/config/group/** 对 crypto 分组统一做敏感字段清洗;任何写入敏感字段的请求明确拒绝,不静默保存。/system/config/** 禁止新增、修改或改名为部署级 crypto 密钥键,避免通过散配置表重新建立覆盖。V1.0.52 从 sys_config 和 sys_config_group 清除历史敏感配置字段;安全清理使用物理删除并记录原因。EncryptTypeHandler 及 SysClient 中未使用的 import/注释引用。CryptoProperties 增加持久化密钥配置:启用状态、版本化写入开关、活动 keyId/密钥、历史解密密钥和旧无版本密钥兼容开关。PersistentCryptoService,格式固定为 FPC1:<algorithm>:<keyId>:<payload>;keyId 只允许安全字符,格式异常和未知 keyId 失败关闭。writeVersioned=false 时保持旧格式写入,供兼容版本首次滚动发布;true 时只用活动密钥写 FPC1,读取同时支持 FPC1 和旧格式。PersistentCryptoService;JdbcDataSourceProvider 解密失败抛业务异常,不把密文当 JDBC 密码。applyEncrypt/applyDecrypt 改用 PersistentCryptoService;配置或密文错误中止当前写/读操作,不再吞异常继续。EncryptorFactory,不改传输协议。LEGACY、ACTIVE、HISTORICAL、UNKNOWN、BLOCKED 数量,只返回计数和定位元数据。id + 原密文。configKey 显式选择,解析 encrypt_config、运行数据源、表、主键和字段;先校验标识符、写权限、列容量和单主键,再允许分批迁移。expectedActiveKeyId 与运行配置一致;默认 dryRun=true,执行模式必须由平台管理员明确提交。sys_config、sys_config_group、接口响应、浏览器状态或日志。[A-Za-z0-9_-]{1,32};活动密钥和历史密钥必须为 Base64 编码且解码后满足算法要求。为同时兼容 SM4/AES,本阶段持久化 keyring 统一使用 16 字节密钥。legacyAlgorithm 只用于无版本旧密文。legacy-read-enabled=true 且旧密钥存在时才允许读取,否则失败关闭。write-versioned=false 仅用于首次兼容发布,不是长期运行模式;生产启用新 keyring 后必须切换为 true。configKey,只处理当前租户;只读运行数据源、无单主键、字段不存在或列容量不足时只报告阻塞,不执行写入。encrypt_config 的盘点中,LEGACY + UNKNOWN + BLOCKED + FAILED = 0。FPC1 写入并关闭 legacy read;已有历史密文的升级环境必须显式提供原 legacy key 和兼容开关。| 操作 | 表名 | 字段/索引 | 说明 |
|---|---|---|---|
| 清理 | sys_config |
config_key |
物理删除根密钥、RSA 私钥和持久化 keyring 对应散配置;这是敏感信息清除例外,回滚时从外部 Secret 重新注入,禁止恢复入库 |
| 清理 | sys_config_group |
config_value JSON |
对 group_code='crypto' 使用 JSON_REMOVE 删除 secretKey、rsaPrivateKey、persistence 等敏感节点,保留非敏感功能开关 |
| 更新 | 初始化 SQL | crypto 分组 JSON | 新装库不再包含敏感字段占位,避免通用接口继续暴露密钥模型 |
| 数据迁移 | ai_report_data_connection |
password_cipher |
运行期受控迁移,不由 Flyway 解密;字段 varchar(500) 保持不变 |
| 数据迁移 | 动态业务表 | encrypt_config 指定字段 |
运行期按配置、数据源和租户迁移;不自动执行 DDL,容量不足时阻塞并报告 |
Flyway 版本:forge-server/db/migration/V1.0.52__remove_database_crypto_secrets.sql。脚本不得包含真实密钥,必须使用表存在检查/JSON 有效性判断,且可重复执行核心清理语句。
| 操作 | 接口 | 方法 | 变更内容 |
|---|---|---|---|
| 收紧 | /api/config/manage/crypto |
GET | 返回不含 secretKey、rsaPrivateKey、持久化 keyring 的管理 DTO |
| 收紧 | /api/config/manage/crypto |
PUT | 只接受非敏感功能开关;请求包含部署级密钥字段时拒绝 |
| 收紧 | /api/config/group/** |
GET/POST/PUT | crypto 分组响应清洗敏感 JSON;写入敏感字段拒绝 |
| 收紧 | /system/config/** |
POST | 新增/修改散配置时拒绝部署级 crypto 密钥键;现有普通敏感配置继续沿用脱敏编辑语义 |
| 新增 | /api/config/manage/crypto/migration/inventory |
POST | 平台管理员按当前租户和可选 configKeys 执行只读盘点 |
| 新增 | /api/config/manage/crypto/migration/execute |
POST | 平台管理员显式执行迁移,默认 dry-run,校验 expectedActiveKeyId |
| 内部兼容 | 数据连接 CRUD/连接测试 | 既有 | 协议不变;密码密文改为版本化持久化服务处理 |
| 内部兼容 | 低代码动态 CRUD | 既有 | 协议不变;加密字段支持新旧格式,错误改为失败关闭 |
迁移响应只包含租户、来源、configKey、表/字段、格式分类、计数、阻塞原因和批次结果;不得返回明文、密文、密钥或可逆摘要。
forge-starter-core:扩展 CryptoProperties 持久化密钥配置,增加共享的部署级 crypto 密钥键策略。forge-starter-crypto:启动校验、版本化持久化密文服务、格式解析和单元测试;删除死 TypeHandler。forge-starter-config:安全管理 DTO、配置转换、通用配置分组清洗/拒绝、迁移编排入口及测试。forge-plugin-data:数据连接持久化加密、失败关闭、盘点/迁移贡献器及 Mapper XML。forge-plugin-generator:动态 CRUD 持久化加解密、低代码盘点/迁移贡献器和动态数据源预检。forge-plugin-system:封堵 sys_config 散配置写入旁路,删除 SysClient 的死 import/注释。forge-admin-server、forge-report-server:环境变量配置和装配验证。forge-admin-ui:配置中心删除部署级密钥编辑项。forge-server/db/migration、两份初始化 SQL、docker、forge-docs:历史敏感配置清理与部署 Runbook。不涉及前端浏览器 CryptoRuntimeConfig 白名单、RSA 会话交换路径、Flow 服务协议、文件存储 AK/SK、生成器数据源 GenDatasourcePasswordCodec 和高风险审批独立 KEK。
git filter-repo。旧值仍可从历史读取,因此必须以“密钥已轮换并退役”作为最终安全边界。test-spec.md。write-versioned=true 且关闭 legacy read;已有密文的升级仍使用保守的双读和分阶段写入流程。迁移接口 dryRun=true,低代码容量不足不自动扩列,生产真实迁移不在本地执行。EncryptorFactory 继续服务 API/会话兼容;PersistentCryptoService 专门服务数据库密文,避免以后轮换传输密钥再次影响存量数据。FPC1:<algorithm>:<keyId>:<payload> 复用现有 SM4/AES 实现,新增最小元数据支持轮换;本阶段不顺带升级加密模式,避免把算法重构与密钥迁移耦合。encrypt_config.algorithm;版本化格式以密文内算法为准。EnvironmentPostProcessor 在配置绑定前将显式配置或持久化密钥文件加入 Spring Environment;Java 进程不尝试修改父 Shell 环境。| Task | 状态 | 实际改动文件 | 备注 |
|---|---|---|---|
| Proposal | 完成 | spec.md、tasks.md、test-spec.md、execution-log.md |
用户明确要求继续完成剩余任务,HARD-GATE 已通过 |
| Task 1-5 | 完成 | Core/Crypto/Config/System、YAML、Docker、UI、初始化 SQL、Flyway | 清除配置来源与管理暴露面;补充方括号 Map 键旁路防护和非法 JSON 失败关闭 |
| Task 6-8 | 完成 | Persistent Crypto、Data、Generator | FPC1 双读/分阶段写入接入数据连接和低代码字段;读取失败关闭 |
| Task 9-11 | 完成 | Data/Generator migration、Admin migration API | 当前租户盘点、默认 dry-run、全 scope 预检、批次原子事务和原值比较更新 |
| Task 12 | 完成 | Runbook、测试、构建、静态扫描、两阶段审查 | 代码就绪;生产迁移、全租户归零和旧钥退役仍为部署门禁 |
| Task 13 | 完成 | Starter Crypto 启动引导器/注册/测试、Admin/Report YAML、Docker 持久化卷、Runbook | 新安装无需手工 export;历史 legacy key 仍需从既有 Secret 来源提供 |
SessionKeyStore 协议保留,默认仍为兼容写入,未执行生产迁移或密钥退役。test-compile 和 Admin/Report 聚合 package 通过;隔离矩阵 59 条测试通过;Node 20.19.0 前端 8725 模块生产构建通过;敏感值、Flyway、死代码和协议保留静态检查通过。target/classes 隔离运行目标测试。低代码运行时当前只注册 MySQL、PostgreSQL、Oracle 方言,本变更验证这三种现有方言,未扩展 SQL Server 整体运行时支持。docker compose config --quiet 通过。