# 根域名证书校验补充验收 ## 结论与范围 北京时间 2026 年 9 月 21 日,在已提交并推送的 `37bb937` 基线上补充验收。发现并修复自动 DNS 证书的校验不一致:申请时要求根域名和泛域名,但运行时共用的域名列表遗漏根域名。正式程序没有替换,其他项目没有停止,用户原有云端测试报告没有修改。 **这是一个已实机复现并修复的证书覆盖缺口,不代表真实 DNS 续期或整体验收通过。** ## 需求与任务清单 - [✓] 自动 DNS 证书在申请、配置复验、加载和导出阶段均要求根域名与泛域名。 - [✓] 在云服务器保留整改前失败反例,再用新编译测试程序验证修复。 - [✓] 无效新证书不替换有效导出文件;热加载失败时保留尚有效的内存证书。 - [✓] 后续完整证书仍可热加载,外部证书管理模式的既有兼容测试通过。 - [✓] 云端配置证书专项 21 个顶层测试、SSL 初始化包 26 个顶层测试,均零跳过通过。 - [✓] 本地运行时校验与热加载专项竞态回归通过。 - [✓] 最新前端、插件和 Linux 程序已编译,三库空数据实机回归通过。 - [✓] 配置包静态安全复扫完成,原有七条告警未增加;扫描退出 1,不计为安全门禁通过。 - [!] 公网 DNS 挑战、自动续期、真实 CA 换证和全协议热加载仍需单独实机验收。 ## 整改前反例 测试通过托管存储准备有效期内、密钥配对正确但仅包含 `*.example.com` 的测试证书,故意不在准备阶段校验域名,模拟已有文件的加载场景。没有使用正式私钥或修改正式证书。 云端目录:`/var/tmp/smail-review-ssl-apex-20260921/20260921-042413-before/`。 - 配置引用复验错误接受缺少根域名的证书。 - 当前及旧自动 DNS 配置的运行时加载均错误接受。 - 导出错误接受该证书,并替换已导出的有效证书包。 - 根域名加泛域名的正向用例通过;只有根域名的反例已能正确拒绝。 本轮两个顶层测试失败,零跳过,云端测试退出 1。日志完整保留在 `test-log/production-remediation-20260921/ssl-apex-before/`。这是一个共享校验列表缺口的多路径表现,不按断言数宣传漏洞数量。 ## 修复和回归 `backend/config/ssl_custom.go` 的 `validationCertificateDomains` 在自动 DNS 项目托管模式明确加入根域名,继续要求泛域名及已配置的服务域名。保留既有文件安全、证书密钥匹配、有效期和强度校验,不改变手动证书入口、管理责任或 DNS 记录。 新增 `backend/config/ssl_apex_test.go`,覆盖配置复验、当前及旧配置加载、导出文件内容和身份保留,以及内存证书的拒绝与后续恢复。热加载测试调用实际运行时证书回调,但不是外部客户端对全部邮件协议的换证演练。 | 云端轮次 | 目录后缀 | 结果 | | --- | --- | --- | | 最终配置证书专项 | `20260921-042721-final` | 21 个顶层测试通过,零跳过,退出零 | | SSL 初始化完整包 | `20260921-042935-setup-all` | 26 个顶层测试通过,零跳过,退出零 | 测试使用非特权 `smail`、独立临时目录及私有网络,正式目录不可访问;内存上限 256 MiB、CPU 配额 50%、禁用交换空间、运行时间上限 300 秒。最终配置证书专项执行输出记录内存峰值 8.1 MiB,不据此推导生产容量。 本地命令为 `node test/run.mjs backend -race -count=1 -p 1 -run '^TestProjectDNS(Runtime|Reload)' ./config`,通过。日志目录 `test-log/run-2026-09-20T20-28-01-801Z-38632/` 使用 UTC 文件名,不是北京时间日期。 ## 候选产物 前端、Telegram 界面、三个 Linux 插件、Linux 主程序与管理员工具重新编译。版本为 `review-apex-20260921`,内嵌源码标识为 `37bb937c4040ae35a9393c418b9d7679f7bd436d-dirty`,不冒充已发布版本。 | 产物 | SHA-256 | | --- | --- | | 主程序 | `bb451caf67f6421253c5b4edddc6fce9961ac04613c4fe3166db36a465291939` | | 管理员工具 | `e5b540a23f598f892f23b17f59e2f97c7560f8c3dca317ba6b6cdf2d67485821` | | 最终配置测试程序 | `6d2cda0c4f3483bb05de86b8067588e27689f80043bd17b3e501e38cbbedacb4` | | SSL 初始化测试程序 | `263126767560b5e09eccbc415eff5de4192c9c5babc6b16d90417f904a81db4d` | 构建日志和摘要位于 `test-log/production-remediation-20260921/build-apex.log`、`apex-build-manifest.json`;配置目录 94 个 Go 文件的摘要记录在 `apex-config-source-manifest.json`。 ## 最新候选三库实机回归 同一主程序上传至 `/var/tmp/smail-review-apex-20260921/`,每轮创建新的空数据隔离实例,以 192 MiB 内存限额验证直接 TLS 拓扑。 | 数据库 | 实例目录后缀 | 结果 | | --- | --- | --- | | SQLite | `smail-review-runtime-20260921-043114` | 通过;只有 `smail.db`,完整性检查正常 | | MySQL | `smail-review-runtime-20260921-043229` | 通过;配置历史与业务同库,SQLite 文件零个 | | PostgreSQL | `smail-review-runtime-20260921-043411` | 通过;配置历史与业务同库,SQLite 文件零个 | 覆盖空库初始化、中文邮件和附件、IMAP APPEND/EXPUNGE、HTTP 旧会话撤销、协议码及客户端码撤销后的旧连接拒绝、过载恢复。三轮均无 OOM、资源采集错误或清理错误;SQL 测试账号已锁定,桥接已停止。 证据为 `apex-runtime-results.json`、`apex-runtime-evidence/` 和 `apex-evidence-index.json`,共十二份下载文件的摘要已经逐一复核。`apex-live-final.json` 再次确认六个保护服务和正式程序摘要未变,没有运行中的本轮隔离服务。该回归使用隔离域名和手动测试证书,不冒充公网域名验收、真实自动续期或新候选的 Nginx 正向复验。 ## 静态扫描边界 Windows 主机设置 Linux/amd64、禁用 CGO,对 `./config` 运行原有 `gosec v2.22.10`。55 个文件、11,378 行,包加载错误零,原有 `nosec` 为 4;七条告警均为中等级,G304 五条、G302 两条,退出码 1。逐条按规则、文件、行号和代码片段与上一轮全项目报告中的配置包比较,集合完全一致。 原始日志为 `gosec-apex-config.log`,完整提取的 JSON 为 `gosec-apex-config.json`,SHA-256 为 `7c0eed5bd0cb48ab5fd926c431a8155671c5f8418aaeaa9830e49512c2ca7db9`。扫描后配置目录九十四个源码文件与构建时清单一致。这里只复扫变更包,不把它说成又一次全项目扫描;上一候选全项目三十八条仍是未通过记录,其他待办没有关闭。 ## 保留的工具失败与证据边界 首次修复后,两个测试已经退出零,但验收辅助工具无条件再次停止已被 systemd 回收的短时单元,收到退出 5,将该轮判为失败;原始记录保留在 `ssl-apex-after/`,没有改成通过。 后续辅助工具额外读取 `LoadState`、`ActiveState`、`MainPID`,仅在进程为零、状态不活动且明确 `not-found` 时接受该停止返回值;其他停止错误仍拒绝。新建隔离轮次重新执行后通过。失败记录没有重置,正式服务进程标识和正式主程序摘要均保持不变。 原有浏览器、凭据轮换、外部邮件最终收件、迁移进程重启、容量和异机灾备等门禁仍保留,详见 `生产补充验收记录.md`。