支持同项目内域名与服务器双向复用,局部修改和清空不影响其他域名;在绑定保存、服务器编辑和部署前拒绝已知共享路径冲突。 新增仓储、接口、工作流与页面回归源码,同步任务状态和第三版接口交接。按要求先开发后统一测试,本批未运行测试、构建或部署,保留在途任务与主机别名等待办。
181 lines
15 KiB
Markdown
181 lines
15 KiB
Markdown
# 发布加固与实机验收记录
|
||
|
||
## 当前开发边界(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 版本,另做相应环境验收。
|