83 lines
10 KiB
Markdown
83 lines
10 KiB
Markdown
# 无停机续期整改记录
|
||
|
||
## 2026 年 9 月 18 日 HTTP 备用方案复核
|
||
|
||
02:25 使用全新测试 CA 账号完成五域名真实 HTTP-01 挑战尝试,全部被 CA 判为 403,未取得证书。Nginx 同时段访问日志中五个对应请求均记录为 200,不能将失败直接归因于本站文件权限,也不能仅凭本站日志批准公网挑战可用。两个站点 138 次连续 HTTPS 探测全部成功,线上四个服务进程、正式证书、私钥、应用和旧续期任务均未变化。
|
||
|
||
候选新增显式 `SMAIL_ACME_CHALLENGE=http`,仅使用现有 Nginx Webroot,不监听新端口、不停止服务、不自动从 DNS 降级。41 项远程隔离回归通过,包括 HTTP 子进程掩码与 root 独占账号目录检查。HTTP 模式尚未启用,DNS 专用授权仍缺失,正式换证和外部告警仍未验收。详情见《HTTP无停机续期验收记录》;下方保留此前 DNS 整改阶段的证据。
|
||
|
||
## 范围与当前结论
|
||
|
||
远程主机时间:2026-09-18 00:00 至 00:35,时区为 Asia/Shanghai。测试仅在 `47.108.78.12` 执行,本机仅编辑和传输。本文中的日期以远程命令及证据时间为准。
|
||
|
||
- [✓] 明确原停机原因:线上旧脚本使用 TLS-ALPN-01 独占公网 443,并显式停止整个 Nginx 服务。
|
||
- [✓] Nginx 已从签发目录迁移到 `/opt/smail/ssl/custom.crt`、`custom.key`,与 Smail 共用已发布证书;8001/8002、上游 TLS 校验及其他站点配置未改动。
|
||
- [✓] 28 项远程隔离回归通过,包括真实独立 Nginx 换证、平滑重载和失败回退后的实际 TLS 指纹验证,以及严格 umask 下保留配置权限的回归。
|
||
- [✓] 线上配置迁移期间两站点 58 次 HTTPS 请求全部成功,一条迁移前已建立的下载连接完整返回 312,377 字节;主服务进程未重启。
|
||
- [✓] 候选脚本在真实未到期状态完成系统信任、域名、密钥、权限和线上证书一致性检查;七个 TLS/STARTTLS 入口再次通过。
|
||
- [!] DNS-01 候选未替换线上续期脚本和 systemd 定时任务。尚无 Smail 专用 DNS 凭据配置,不得借用 Scert 凭据,也不能以模拟结果代替真实挑战。
|
||
- [!] 全新 DNS 挑战、正式新证书签发发布、外部失败和到期告警未验收;整体生产放行仍不通过。
|
||
|
||
本轮没有清库、发送业务邮件、重置账号、重编译应用或停止共享服务。正式证书、程序及现有续期脚本均未替换,不构成最新应用首次安装或业务浏览器验收。
|
||
|
||
## 代码变更
|
||
|
||
`deploy/nginx/renew_certificate.py` 使用 `SMAIL_ACME_DNS_PROVIDER` 指定 DNS 服务商,不再调用 `stop/start nginx`,不允许配置缺失时退回停机验证。依据线上实际使用证书判断是否临近到期,而不是依据签发目录中的候选证书。ACME 正式服务地址显式指定,命令超时为 600 秒,保留密钥复用及互斥控制。
|
||
|
||
`deploy/nginx/deploy_certificate.py` 与续期入口共用同一进程锁。候选、旧证书和旧私钥首先保存到 root 专属快照,再对同一份快照进行系统信任链、五个域名、服务用途、剩余至少七天有效期以及私钥匹配验证。普通续期只允许复用已有密钥,密钥轮换另行验收。
|
||
|
||
发布后执行 `nginx -t`、平滑重载,并实际检查 443 和 8002 的系统信任及证书指纹。失败时恢复旧文件、重新重载并检查旧指纹。文件已经一致但线上仍使用旧证书时,会补做重载,避免上次任务在写文件后被中断导致永久不同步。校验快照和回退副本保留在 `/var/backups/smail-certificate-*`,不会自动删除。
|
||
|
||
候选 service 移除了 `ExecStopPost` 启动 Nginx 的旧钩子,支持 `/etc/smail/acme-dns.env`,总超时 720 秒。原有 96/128 MiB、禁用交换空间、CPU 和任务数限额保留。`acme-dns.env.example` 只包含空白凭据示例,不是可直接启用的正式授权。
|
||
|
||
历史维护窗口工具 `test/deployment/renewal_drill.py` 增加旧续期脚本摘要限定,拒绝将原先针对停机实现的演练用于新的 DNS 实现。
|
||
|
||
修正 `test/tools/remote_review.py`:项目审查上传目录使用 `0700`,不再把已保护的目录改回 `0755`。已通过真实上传后的远程权限检查确认。
|
||
|
||
## 隔离测试证据
|
||
|
||
证据目录:`/var/tmp/smail-review-cert-safe-20260918`。
|
||
|
||
最终日志:`tests-final-3.txt`。单元 `smail-cert-safe-tests-20260918-0030` 返回成功,28 项通过,零跳过;测试执行 3.125 秒,单元运行约 3.344 秒,内存峰值约 24.6 MiB,交换空间为零。这不是生产容量结论。此前 27 项通过日志 `tests-final-2.txt` 继续保留。
|
||
|
||
测试使用独立网络空间和只读系统目录,禁止访问线上应用、正式 ACME 状态、管理 socket 及 Nginx 配置。测试证书由隔离 CA 签发,只有隔离验证上下文信任,不导入系统信任库。真实 Nginx 实例监听隔离空间的 18443/18002,测试完成后仅终止该测试实例。
|
||
|
||
覆盖申请失败及超时不停止业务、不发布候选;未配置或非法服务商拒绝;不可信链、错误域名、临近到期、私钥不匹配及密钥轮换拒绝;并发、锁权限、证书权限、符号链接拒绝;重载失败和线上指纹失败回退;中断后的重载补偿;候选文件在快照后变化不能影响发布内容。
|
||
|
||
第一轮 23 项模拟及文件系统回归通过。新增真实 Nginx 测试首次失败,是该实例试图使用被只读保护的主机 `/var/lib/nginx/body`,并非生产服务故障;随后将其全部临时路径指向隔离测试目录。失败日志 `tests-final.txt` 与最终通过日志均保留,未放宽隔离边界。
|
||
|
||
`candidate-live-check.txt` 为候选脚本的真实未到期检查,成功返回;运行约 450 毫秒,峰值约 11.8 MiB。应用和 ACME 目录只读挂载;本次未申请证书、未改正式文件,也没有替换线上定时任务。其受保护快照为 `/var/backups/smail-certificate-20260917T162339-e3f3d284`,名称时间为 UTC。
|
||
|
||
## 线上迁移证据
|
||
|
||
最终目录:`/var/tmp/smail-review-cert-safe-20260918-cutover-final`。
|
||
|
||
- `cutover-result.json`:完整前后摘要、服务状态、连续探测和下载证据。
|
||
- `smail.conf.before`:原 Smail 站点配置,迁移工具只接受已核对的原配置摘要和仅变更两条证书路径的候选配置。
|
||
- `cutover-output.txt`:单元 `smail-cert-cutover-20260918-4` 执行输出。
|
||
- `transport-result.json`:七个 TLS/STARTTLS 入口、Host/SNI 和 HTTP 跳转复测。
|
||
|
||
迁移时间为本地 00:22:31 至 00:22:38,仅执行一次平滑重载。Nginx 主进程 `578671`、Smail 主进程 `574584` 及各自 InvocationID 前后一致。两站点探测使用 `mail.example.com:443` 和 `web.apmmk.cn:443`,没有关闭系统信任或域名校验。
|
||
|
||
下载使用线上实际资源 `/assets/main-app-96760af3.js`,重载前建立连接并读取前 1024 字节,重载后读取余下内容,与独立请求返回的完整内容比较相同。此项证明本次已有下载连接完成,不扩大为任意长连接或长期可用性保证。
|
||
|
||
Smail 站点配置 SHA-256 从 `2c6e42ce1c996947a13b35bddd9840888a4bd9e7e2de7480954daa3fafbb18a4` 变为 `bf6816c38f9c639ab17fd7344fff4b06afc5a2388f74318e18c60b8b3311ebd8`。其他站点配置、正式证书、私钥、应用双程序和线上续期脚本摘要未变。
|
||
|
||
迁移前有三次拒绝或失败,均未修改线上配置:第一次权限检查发现上传目录变为 0755;第二次发现入口 JS 仅为小型引导脚本,不能充当持续下载样本;第三次按旧记录探测 Scert 8004 被拒绝。实时核查确认 Scert 已迁移至按域名共享 443,工具随后按当前实际入口执行,未恢复或修改 Scert 配置。对应日志及失败结果保留在主证据目录和 `-cutover` 目录。
|
||
|
||
迁移工具首次原子写入受单元 `UMask=0077` 影响,导致 Smail 配置权限由原来的 0644 收紧为 0600。工具已增加显式 `fchmod` 并通过第 28 项回归;未重新执行整次线上迁移。00:34 持证书锁并核对类型、属主和配置摘要后,仅将 `/etc/nginx/conf.d/smail.conf` 恢复为原来的 0644,内容摘要保持不变,未触发重载或重启。
|
||
|
||
收尾证据保存在主证据目录的 `closeout-0035.txt`、`protected-closeout-0036.txt`、`transport-closeout-0036.json`,文件后缀为批次标识,实际执行时间以内容为准。本地六个候选代码及 service 文件与远程测试文件的 SHA-256 一致;远程 Python 编译检查、候选 `systemd-analyze verify` 和 `nginx -t` 成功。systemd 检查另提示宿主机现有单元的 `CPUAccounting` 已被移除并被忽略,不属于候选续期单元错误,本轮未修改这些宿主机配置。
|
||
|
||
收尾再次逐项核对八个保护文件,应用双程序、正式证书及私钥、旧续期双脚本和 service、Scert 配置均未变化。Smail 与 Scert 的 HTTPS 页面可用,七个 TLS/STARTTLS 入口系统信任及主机名校验通过,未知 Host/SNI 拒绝;Nginx 和 Smail 主进程及 InvocationID 不变。旧续期 timer 仍为 active,专用 DNS 配置仍不存在,不把本次收尾描述为新续期任务已启用。
|
||
|
||
## 启用 DNS 续期前的门槛
|
||
|
||
1. 确认 `example.com` 实际 DNS 服务商及专用最小权限授权。凭据只保存在远程受保护文件,不在聊天、仓库、普通文档或日志中传递;示例采用阿里云不代表已获得该账号授权。
|
||
2. 使用全新、独立的 ACME 测试账号完成五个域名的真实 DNS-01 挑战,验证 TXT 创建、传播及清理,不以已有授权缓存代替挑战。
|
||
3. 验证 DNS 拒绝、超时与清理失败期间两个站点及邮件服务仍可用,正式证书和私钥保持不变。
|
||
4. 在受保护备份和持续探测下同步安装新的续期脚本、部署脚本和 service,再完成正式签发及七个入口的新指纹检查。不能只替换其中一个脚本:旧续期脚本在持锁期间通过子进程调用部署脚本,与新版本共用锁的执行方式不同,混用会导致锁冲突。
|
||
5. 接入真实外部续期失败、证书临近到期告警,完成一次失败告警送达及恢复验证,再将无停机续期列为验收通过。
|
||
|
||
截至本轮结束,线上仍使用旧续期定时任务,证书有效期至 2026-12-16。保留旧任务不代表批准未来停机维护窗口;必须在进入到期续期窗口前完成 DNS 迁移。发布验收清单中的其他业务、安全、容量和恢复门槛继续有效。
|