91 lines
5.5 KiB
Markdown
91 lines
5.5 KiB
Markdown
# RemoteDesk 在线升级
|
||
|
||
## 1. 安全模型
|
||
|
||
控制端只接受管理员配置的 HTTPS 更新清单和 Ed25519 公钥。CDN 传输不能替代发布签名:客户端先验证清单签名,再验证其中的产品、目标平台和版本,下载 MSI 后还会验证签名清单内的精确文件大小和 SHA-256。任何一步失败都不会启动安装程序。
|
||
|
||
更新清单使用以下信封格式,`payload` 是原始 UTF-8 JSON 字节的 Base64,避免 JSON 字段顺序或空白变化破坏签名语义:
|
||
|
||
```json
|
||
{
|
||
"schema": 1,
|
||
"payload": "BASE64_SIGNED_JSON",
|
||
"signature": "BASE64_ED25519_SIGNATURE"
|
||
}
|
||
```
|
||
|
||
签名载荷包含固定产品名 `remotedesk`、发布通道、三段式版本、发布时间、目标平台、MSI HTTPS URL、文件大小、SHA-256 和可选发行说明。Windows x64 当前使用目标值 `windows-x64`。
|
||
|
||
## 2. 发布密钥
|
||
|
||
在隔离的发布环境中生成 Ed25519 PKCS#8 私钥,例如:
|
||
|
||
```powershell
|
||
openssl genpkey -algorithm Ed25519 -out remotedesk-update-private.pem
|
||
```
|
||
|
||
私钥不得提交到仓库、写入安装包或上传 CDN。生产环境应把它保存在受审计的 CI 密钥库或离线签名设备中,并限制发布工作流读取权限。
|
||
|
||
## 3. 生成清单
|
||
|
||
先完成 MSI 构建和 Authenticode 签名,再生成更新清单。清单中的哈希必须对应最终上传的字节:
|
||
|
||
```powershell
|
||
cargo run --locked -p remotedesk-update-manifest -- `
|
||
--installer .\artifacts\RemoteDesk-M0-0.3.0-windows-x64.msi `
|
||
--installer-url https://updates.example.com/stable/RemoteDesk-M0-0.3.0-windows-x64.msi `
|
||
--private-key C:\secure\remotedesk-update-private.pem `
|
||
--version 0.3.0 `
|
||
--notes-file .\release-notes.txt `
|
||
--output .\artifacts\stable.json `
|
||
--public-key-output .\artifacts\update-public-key.txt
|
||
```
|
||
|
||
生成器拒绝覆盖已有清单,避免意外替换已发布元数据。将 MSI 和 `stable.json` 上传到只提供 HTTPS 的静态发布源;清单和安装包 URL 不允许凭据、query 或 fragment。
|
||
|
||
Drone 构建配置只生成未签名开发包,不读取生产签名材料。正式发布应在受保护的 Drone 发布流水线或离线签名环境中执行,并把版本标签精确绑定到 Cargo workspace 版本,例如 `v0.3.0`。发布环境必须配置并限制以下 secrets:
|
||
|
||
- `WINDOWS_SIGNING_PFX_BASE64`:生产 Authenticode PFX 的 Base64 字节。
|
||
- `WINDOWS_SIGNING_PFX_PASSWORD`:PFX 密码。
|
||
- `UPDATE_ED25519_PRIVATE_KEY_PEM`:Ed25519 PKCS#8 私钥全文。
|
||
- `UPDATE_ED25519_PUBLIC_KEY_BASE64`:预先登记的 32 字节公钥 Base64,用来阻止误用另一把私钥发布。
|
||
|
||
发布流水线临时导入 PFX,验证私钥、有效期和 Code Signing EKU,以 SHA-256 和 RFC 3161 时间戳签署控制端内部 EXE、PowerShell helper、控制端 MSI、Windows Host PowerShell 模块和 Host MSI,并在生成任何发布哈希前逐个验签。随后生成 Ed25519 更新清单和 SPDX JSON SBOM,先上传不可变的版本资产,最后才替换稳定通道中的 `stable.json`。固定清单地址为:
|
||
|
||
```text
|
||
https://updates.example.com/stable/stable.json
|
||
```
|
||
|
||
发布对象存储必须只提供 HTTPS;客户端只跟随 HTTPS 且最多三次。Drone 发布环境应配置审批、保护版本标签的创建权限,并在缺少任何 secret、标签与源码版本不一致、证书或时间戳无效、最终 MSI 验签失败或公钥不匹配时,在更新发布通道前终止。
|
||
|
||
## 4. 控制端配置
|
||
|
||
在“全局设置 -> 在线升级”中填写清单 HTTPS URL,以及生成器输出的 Base64 公钥并保存。启用“自动检查更新”后,控制端每次启动会检查一次;也可以手动点击“检查更新”。
|
||
|
||
发现更高版本后,用户确认“下载并安装”,控制服务会:
|
||
|
||
1. 再次下载并验证签名清单,防止检查和安装之间被替换。
|
||
2. 下载 MSI 到 `%LOCALAPPDATA%\RemoteDesk\updates`,限制最大 1 GiB。
|
||
3. 验证字节数和 SHA-256,启动独立升级 helper。
|
||
4. 返回接受状态后退出控制服务;helper 再次校验哈希并运行 `msiexec /passive /norestart`。
|
||
5. 安装成功后重新启动 RemoteDesk;MSI 日志保存在更新目录。
|
||
|
||
当前 MSI 的固定 `UpgradeCode` 和 `MajorUpgrade` 规则负责原位升级并拒绝降级。便携预览版也可以检查并安装 MSI,但不会覆盖原便携目录。
|
||
|
||
## 5. 本地接口
|
||
|
||
控制端页面通过回环控制服务调用以下接口:
|
||
|
||
| 接口 | 成功状态 | 用途 |
|
||
|---|---:|---|
|
||
| `GET /api/v1/settings` | 200 | 读取更新 URL、公钥和自动检查开关 |
|
||
| `PUT /api/v1/settings` | 200 | 保存配置;URL 与公钥必须同时提供或同时清空 |
|
||
| `POST /api/v1/update/check` | 200 | 请求体为 `{}`;下载、验签并比较版本 |
|
||
| `POST /api/v1/update/install` | 202 | 请求体为 `{"version":"x.y.z"}`;重新验签、下载并启动退出后安装流程 |
|
||
|
||
请求 JSON 或字段不合法返回 400;未配置更新源、重复发起安装返回 409;检查阶段的网络、HTTP、清单或签名错误返回 502;安装准备或 helper 启动失败返回 500。所有错误都返回 `{"error":"..."}`,不会在失败后静默安装。
|
||
|
||
## 6. 正式发布边界
|
||
|
||
仓库已实现客户端升级机制、签名清单生成器和 Drone 构建入口,但不会内置生产私钥、PFX 或把信任公钥静默写入客户端。普通 Drone CI 和本地打包明确生成未签名开发包;正式签名资产必须由受保护的发布环境生成。首次生产发布仍需审计 Drone 权限、证书链、时间戳服务、对象存储下载和真实 MSI 原位升级。
|