13 KiB
发布加固与实机验收记录
日期:2026 年 9 月 15 日,时间均为北京时间。
一、结论与授权边界
当前是 Linux amd64 发布候选验收,不是正式生产放行。 本记录是本轮最新状态入口;其他历史验收记录中的日期、主机、缓存和未完成项,不应直接视为当前状态。
| 主机 | 用途 | 本轮边界 |
|---|---|---|
47.108.78.12 |
Scert 管理机 | 独立候选包和验收程序,不覆盖在用配置、数据库或主程序 |
8.137.23.95 |
证书推送目标 | 仅使用新建隔离证书目录,不部署 Scert 管理服务 |
pcux.cn及其子域的 DNS、证书、私钥、权限、属主和站点配置禁止修改。- 不清空业务数据库、不修改 RAM 策略、不执行全局域名枚举。
- 不重载或重启共享 Nginx,不将模拟命令成功当作真实业务重载验收。
- 本地既有补丁、二进制和构建目录保留,不混入源码提交。
二、需求与任务清单
- [✓] 保留多用户功能,阿里云权限错误失败关闭,撤权后重新在线验证。
- [✓] 初始化必须在签发、校验证书和私钥、配置提交成功后标记就绪。
- [✓] CLI 的已有凭据引用和
--dns-verified不得绕过在线权限检查。 - [✓] 初始化失败使用独立上下文回滚证书,保护用户原有凭据。
- [✓] ACME 测试根与正式系统信任池隔离,无独立 issuer 字段也必须验证链。
- [✓] 远程部署按文件路径加协作锁,锁冲突拒绝,不自动抢锁。
- [✓] 远程上传独占创建、先收紧权限再写私钥,所有上传完成才替换。
- [✓] 重命名响应丢失后重连恢复;回滚失败保留本事务锁并报告人工处置。
- [✓] 发布脚本受版本控制,使用独立目录,不覆盖旧前端产物或旧归档。
- [✓] SSH 安全依赖升级、全量回归和漏洞复扫。
- [✓] 最新 Linux 候选包构建、传输校验和隔离实机复验。
- [✓] 同一候选源码的部署包单元与协议测试在 Ubuntu 管理机通过。
- [!] 正式 ACME、初始化完成、TLS/SNI、实际业务重载和浏览器完整流程。
三、代码复审与安全加固
初始化与证书信任
setup.Apply 仅接受配置输入,不再直接标记 ready。HTTP 与 CLI 各自完成证书事务后才能提交就绪状态。CLI 初始化使用进程锁,取得锁后再次检查配置是否已存在。
新增 CLI 参数 --acme-environment staging|production,默认 staging。测试证书不被普通浏览器信任,不能用于生产。四个测试根来自 Let's Encrypt 官方仓库,仅用于测试签发结果校验;来源见 backend/internal/certificate/trust/来源说明.md。没有向操作系统、浏览器或 ACME HTTP 客户端添加测试根,也没有关闭正式证书链校验。
cert.pem、fullchain.pem 的叶子及私钥必须一致,不能只验证 fullchain 而忽略错误的单独证书文件。
远程部署事务
为每个规范化目标路径创建 <path>.scert-lock 目录,按路径排序获取。远程原子建目录可以让遵守该协议的不同任务和进程互斥;不能防止管理员或旧版本直接改文件。
无法确认临时上传文件所有权时,不自动删除该路径。备份目标已存在时拒绝覆盖。失败后用独立的 30 秒上下文重连、重新校验 SSH 指纹并回滚。文件回滚和服务运行态恢复是两件事:reload 已尝试后即使文件回滚成功,仍提示运行态需要核查。
安全依赖
本轮 govulncheck 检出 golang.org/x/crypto v0.55.0 的 GO-2026-6354、GO-2026-6355,调用路径涉及 ssh.NewClientConn。两项均为 SSH 通道死锁拒绝服务问题,按扫描建议升级至 v0.56.0。升级后必须重新全量测试、竞态测试并复扫,不能沿用升级前结果。
升级后 govulncheck ./... 与详细复扫均退出零:可达漏洞为零,已导入包的其他漏洞为零。模块层仍有 GO-2026-5932,属于未被本项目导入的弃用 golang.org/x/crypto/openpgp 包,无修复版本。本轮没有为消除该提示而删除仍需使用的 SSH 模块,不把“无可达漏洞”扩大为依赖树完全没有提示。
四、构建与验收证据
本轮后端实际构建工具链为 Go 1.26.6;仓库外默认 go version 可能显示不同版本,以模块内工具链及发布清单为准。
构建说明见 build/README.md。发布清单包含源码提交、源码脏状态、实际工具链、目标平台和 SHA256。仅本轮 Linux 服务包,不包含新的 Windows 或桌面端验收。
升级安全依赖后以下验证均通过:
go test -p 1 -count=1 -timeout=3m ./...
go test -race -p 1 -count=1 -timeout=3m ./...
go vet -p=1 ./...
go mod verify
govulncheck ./...
govulncheck -show verbose ./...
npm ci
npm audit --omit=dev --audit-level=high
全量后端验证在 Windows 开发机运行。前端全新依赖安装及生产构建通过,依赖审计零漏洞。发布脚本语法检查通过;已有发布目录的拒绝覆盖测试通过,原 frontend/dist/index.html 哈希保持不变。
20:42 收取 Ubuntu 管理机部署包测试结果:PASS,退出码 0。测试程序来自下述同一候选源码,执行前取消 SCERT_ACCEPTANCE_CONFIRM、SCERT_ACCEPTANCE_CONFIG 和 SCERT_ACCEPTANCE_TARGET,运行参数为 -test.v -test.timeout 3m。本次覆盖部署包的路径校验、证书一致性、上传权限、SSH 指纹与算法选择、锁冲突、超时取消、重连回滚、重命名响应丢失恢复等测试;隔离实机测试因未启用环境变量按预期跳过,其单独实测结果见后文。这是 Ubuntu 上的部署包测试,不是整个后端的 Linux 全量测试。
最终候选来源为独立 Git 克隆,提交 b40fff703115df33f065ddf1e0ec970b04818760。发布清单 source_dirty=false,程序内嵌 vcs.revision 与提交一致、vcs.modified=false,GOOS=linux、GOARCH=amd64、CGO_ENABLED=0,SSH 依赖为 v0.56.0。
本地包:
build/release-verified-20260915-2040/Scert-20260915-V0.1.0-linux-amd64.tar.gz
| 产物 | SHA256 |
|---|---|
| Linux 归档 | 6e53af49f6fa8db62f8cdbe3ca99d064030bbaa54da0110e84f0f1b45be3dad0 |
核心程序 scert |
9cf21bde232c0d05d61b2044af5be9c9e739e3aa880a38492315a37d1733a786 |
| 隔离部署测试程序 | 0024836bf883e00ec484bdae66b0bf16f757e64204d6a7f2f158cf55b114df81 |
发布清单 manifest.json |
e6f8702c94f8e9d139ad72418b0edd7cb9f5ba0fa24a3554895b1dbac8496514 |
上述归档、测试程序和清单已传输至管理机,全部回读 SHA256 一致。远程最终候选入口:
/scert/Scert-20260915-V0.1.0-linux-amd64-verified-2040.tar.gz
/scert/release-verified-20260915-2040/
/scert/scert-release-verified-20260915-2040-manifest.json
/scert/scert-deployment-verified-20260915-2040.test
20:37 在管理机检查归档成员无越界路径或特殊文件后解压到新目录,sha256sum -c SHA256SUMS 全部通过,安装脚本 bash -n 通过,scert、scert-admin 和安装器可执行权限均为 0755。实际运行候选 scert version 返回 Scert 0.1.0,DNS 帮助中文正常。未执行安装器,未替换正在运行的 /scert/scert。
最新 TestRemoteDeploymentIsolatedAcceptance 从管理机发起,向 8.137.23.95 新目录 /etc/ssl/scert-acceptance-20260915-123741.890429938 推送,PASS,9.66 秒:
| 场景 | 实机结果 |
|---|---|
| 首次推送 | 成功 |
| 第二次替换、保留旧备份 | 成功 |
| 第二个 SSH 连接遇到已持有的文件锁 | 拒绝部署,他人锁和当前证书不变 |
| validation 返回非零 | 失败并恢复原文件 |
| reload 返回非零 | 失败并恢复原文件,明确提示运行态未确认 |
| validation 超时 | 实际触发 deadline exceeded,重连回滚成功 |
全部场景复核 cert/fullchain 为 0644、key 及 key 备份为 0600,无遗留 .scert-tmp-* 或 .scert-lock。隔离目录保留 6 个证书及备份文件。
20:38 复核目标机 Nginx active,nginx -t 通过;受保护的 /ssl/pcux.cn/fullchain.pem 和 /ssl/pcux.cn/privkey.pem 哈希、权限、UID/GID 与 20:17 基线完全一致。
本次使用自签测试证书和 /usr/bin/true、false、sleep,没有真实 DNS/ACME 签发、没有配置业务 Nginx 站点、没有 reload 或 restart 共享服务。此结果证明 SSH/SFTP 事务与回滚,不证明正式证书、真实业务 reload 或 HTTPS/SNI 已验收。
构建过程中发现嵌套 Git worktree 的 Go VCS 识别会读取外层工作区状态,外置 linked worktree 的产物则没有内嵌提交。最终改用独立克隆构建,未伪造 VCS 字段,也没有清理原工作区。此前所有候选包保留但不作为最终候选入口。
五、远程部署遗留锁与恢复流程
- 停止针对这些目标文件的新任务,核实所有部署进程和管理员操作都已停止。
- 记录目标路径、错误消息、事务
BackupID、临时路径和备份路径。不要复制私钥内容到工单或日志。 - 确认原证书、完整链和私钥属于同一版本,检查内容、域名、有效期、属主及权限;私钥和私钥备份至少保持
0600,并确认业务服务的实际读取用户。 - 回滚失败或状态不明确时,先从本事务备份恢复并重新校验。不要对整个证书根目录执行递归删除或递归改属主。
- 文件恢复不等于运行态恢复。使用目标业务自身的配置验证命令;共享进程的 reload 必须获得对应维护授权。
- 只有确认没有在途事务、文件已经一致且运行态处理完成后,管理员才能逐个移除已核实归属的空锁目录。禁止自动清理所有
.scert-lock,禁止根据锁目录年龄直接抢锁。 - 无法确认创建成功响应的临时文件可能保留,即使本次业务文件已回滚也需要人工核查;不能按名称前缀批量删除。
六、正式升级与回滚门槛
- 核对发布提交、
source_dirty=false和可信通道取得的归档 SHA256;解压到全新目录,再执行sha256sum -c SHA256SUMS。 - 确认域名在用户允许范围内,并分别验证当前 RAM 的读取、新增、删除能力。读取成功不代表可写,历史成功不是永久授权。
- 确认管理机能以正常 TLS 校验连接正式 ACME 目录;不得借助关闭验证、随意更换 DNS 或修改未授权网络设施绕过。
- 维护窗口内停止 Scert,按
生产部署与运维说明.md创建包含配置、数据库、凭据和证书的完整备份,保留旧程序、Web 资源及服务配置。 - 使用同一发布包更新核心程序、Web 资源与服务模板,先检查配置和本地健康,再检查
/readyz;不能为获取 200 手工伪造 ready 字段。 - 初始化、实际 TLS、登录和授权链路通过后才接入真实业务推送。更新共享服务前单独获得维护授权。
- 启动或验收失败时停止新版本,恢复经核验的旧程序、Web 资源及对应配置;如涉及数据变化,只使用已验证的完整恢复流程,不能只换二进制赌数据兼容。
七、当前外部阻塞与待验收项
20:19 附近管理机 Production 与 Staging ACME 均能解析,但 TCP 连接 5 秒超时,尚未进入 TLS。实际 Scert 进程的大小写 HTTP/HTTPS/ALL/NO_PROXY 环境变量均未设置。阿里云 API 连接正常不代表 ACME 路径正常;本轮证据不足以定位具体云防火墙或上游网络根因。
20:24 只读复核:管理服务 active,配置仍 setup_ready=false,监听 127.0.0.1:9004,时钟同步正常,根分区约 13% 使用。既有后台域名 scert-accept-20260914.epwn.uno 不在本轮允许范围,未对其签发。
20:38 再次复核管理服务:/healthz=200,/readyz=503 setup_incomplete,运行中的主程序仍为 /scert/scert,新候选尚未替换在用服务。这是明确的生产准入阻塞,不能用独立候选验收通过覆盖。
20:44 收尾只读复核:管理服务仍 active,/healthz=200、/readyz=503 setup_incomplete。管理机的最终归档、清单和部署测试程序 SHA256 再次与本地一致;本地发布清单的全部 14 项产物哈希也复核通过。正式 ACME 目录连接仍在 5 秒后超时,HTTP 为 000、TCP 和 TLS 建连计时均为零,不能宣布出站网络问题已经解决。
正式生产验收还需:
- [!] 确定并配置允许范围内的管理后台域名,完成正式 ACME 签发和初始化。
- [!]
/readyz真正就绪,公网 HTTPS、SNI、证书链和管理后台浏览器流程通过。 - [!] 真实业务目标的证书替换、服务读取权限、配置校验和受控 reload。
- [!] 在允许测试写入但 RAM 实际拒绝的候选上验证拒绝路径;不通过写入
pcux.cn代替此项。 - [!] 当前版本的受控升级、备份恢复和故障回退演练。
- [!] 如发布 Windows、桌面端或 MySQL 版本,另做相应环境验收。