Scert/doc/发布加固与实机验收记录.md
xuans e2200534d3 完善域名服务器多对多关联与共享路径预检,补齐回归源码及进度文档
支持同项目内域名与服务器双向复用,局部修改和清空不影响其他域名;在绑定保存、服务器编辑和部署前拒绝已知共享路径冲突。

新增仓储、接口、工作流与页面回归源码,同步任务状态和第三版接口交接。按要求先开发后统一测试,本批未运行测试、构建或部署,保留在途任务与主机别名等待办。
2026-09-28 21:51:05 +08:00

181 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 发布加固与实机验收记录
## 当前开发边界(2026-09-28)
- [x] 生命周期、资源读取隔离及域名服务器多对多已有新增源码,关联保存、服务器编辑和部署预检增加共享路径冲突保护,等待统一测试。
- [!] 本阶段未构建、未重新部署、未使用 SSH 或浏览器,不清理业务数据。正式运行包不能据此标记为已升级,历史包 `5f9af32` 不代表当前源码。
- [ ] 开发完成后按正式产物身份重新进行数据库、接口、桌面页面与实机推送验收;需验证两个域名共享服务器、每域名多服务器、失败保留及路径隔离,不使用辅助脚本作为验收条件。
- [ ] 同主机别名、配置并发变化与在途任务、真实远端路径占用及全资源权限继续保留待办,不以本批预检或旧通过记录关闭生产门禁。详见支持矩阵第二十六节。
## 当前发布验证补充(2026-09-28 20:15)
- [x] 干净提交 `5f9af32` 的 Windows 正式发布包构建成功,发布清单中 10 个产物、压缩包内 9 个交付文件及运行副本中的主程序与 4 个 Web 文件核对一致;主程序与正式 Web 已在独立目录实际启动。
- [x] 临时 HTTPS 指纹和主机名已严格核对;浏览器仅验证首次初始化的未认证入口及无效令牌拒绝,不是公信 TLS 或初始化完成验收。初始化后端专项 128 个叶子通过、4 条平台条件跳过,退出 0。
- [!] 浏览器文件工具的工作区范围不包含 Scert,尚未传入本机一次性地址并兑换会话;没有绕过限制或放开产品认证。
- [ ] Windows Service 安装与重启、初始化完成、成员页面、Linux 原生及生产门禁仍未通过。本批没有接触远端、真实 DNS 或既有业务库。
证据与产物哈希见《构建版本与数据库支持矩阵》第二十五节;总清单与执行清单已同步。以下 9 月 15 日候选验收记录仅保留为历史,不代表当前发布候选或生产放行。
> 2026-09-27 历史边界:本文描述下列日期对应的验收批次,不是当前候选的最新事实。当前完整整改索引见《Scert整改任务执行清单》,12 层生产门禁及逐项证据要求见《整改任务规格与验收约定》和《基础整改任务规格》P0-11;未复验的历史主机、包及结果不得直接沿用为通过。
日期: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 或桌面端验收。
升级安全依赖后以下验证均通过:
```text
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`。
本地包:
```text
build/release-verified-20260915-2040/Scert-20260915-V0.1.0-linux-amd64.tar.gz
```
| 产物 | SHA256 |
| --- | --- |
| Linux 归档 | `6e53af49f6fa8db62f8cdbe3ca99d064030bbaa54da0110e84f0f1b45be3dad0` |
| 核心程序 `scert` | `9cf21bde232c0d05d61b2044af5be9c9e739e3aa880a38492315a37d1733a786` |
| 隔离部署测试程序 | `0024836bf883e00ec484bdae66b0bf16f757e64204d6a7f2f158cf55b114df81` |
| 发布清单 `manifest.json` | `e6f8702c94f8e9d139ad72418b0edd7cb9f5ba0fa24a3554895b1dbac8496514` |
上述归档、测试程序和清单已传输至管理机,全部回读 SHA256 一致。远程最终候选入口:
```text
/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 字段,也没有清理原工作区。此前所有候选包保留但不作为最终候选入口。
## 五、远程部署遗留锁与恢复流程
1. 停止针对这些目标文件的新任务,核实所有部署进程和管理员操作都已停止。
2. 记录目标路径、错误消息、事务 `BackupID`、临时路径和备份路径。不要复制私钥内容到工单或日志。
3. 确认原证书、完整链和私钥属于同一版本,检查内容、域名、有效期、属主及权限;私钥和私钥备份至少保持 `0600`,并确认业务服务的实际读取用户。
4. 回滚失败或状态不明确时,先从本事务备份恢复并重新校验。不要对整个证书根目录执行递归删除或递归改属主。
5. 文件恢复不等于运行态恢复。使用目标业务自身的配置验证命令;共享进程的 reload 必须获得对应维护授权。
6. 只有确认没有在途事务、文件已经一致且运行态处理完成后,管理员才能逐个移除已核实归属的空锁目录。禁止自动清理所有 `.scert-lock`,禁止根据锁目录年龄直接抢锁。
7. 无法确认创建成功响应的临时文件可能保留,即使本次业务文件已回滚也需要人工核查;不能按名称前缀批量删除。
## 六、正式升级与回滚门槛
1. 核对发布提交、`source_dirty=false` 和可信通道取得的归档 SHA256;解压到全新目录,再执行 `sha256sum -c SHA256SUMS`。
2. 确认域名在用户允许范围内,并分别验证当前 RAM 的读取、新增、删除能力。读取成功不代表可写,历史成功不是永久授权。
3. 确认管理机能以正常 TLS 校验连接正式 ACME 目录;不得借助关闭验证、随意更换 DNS 或修改未授权网络设施绕过。
4. 维护窗口内停止 Scert,按 `生产部署与运维说明.md` 创建包含配置、数据库、凭据和证书的完整备份,保留旧程序、Web 资源及服务配置。
5. 使用同一发布包更新核心程序、Web 资源与服务模板,先检查配置和本地健康,再检查 `/readyz`;不能为获取 200 手工伪造 ready 字段。
6. 初始化、实际 TLS、登录和授权链路通过后才接入真实业务推送。更新共享服务前单独获得维护授权。
7. 启动或验收失败时停止新版本,恢复经核验的旧程序、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 版本,另做相应环境验收。