# SDD-003:域名与 HTTPS 证书管理 | 字段 | 内容 | |---|---| | 版本 / 变更级别 | 0.1 / S3(证书私钥、网络入口及生产代理配置) | | 设计状态 | Draft | | 实现状态 | 应用内 ACME、域名设置与管理员状态页已实现,本地构建/单测通过;生产端口/证书待部署验证 | | 负责人 / 日期 | 待指定 / 2026-10-21 | | 关联需求 | `test-link.appx365.com` 使用 HTTP-01 自动签发和续期;管理后台提供域名及证书页面;桌面客户端可使用 HTTP 或 HTTPS,并在设置中填写地址 | | 批准状态 | 用户要求开发与评估;生产入口切换以部署核验为准 | ## 1. 背景、目标与范围 - 当前行为:[后端路由](../../backend/internal/api/api.go)在 `:18080` 提供管理站点和 API;[部署文档](../deploy.md)仅描述外置 Nginx/Let's Encrypt 方案,没有集成证书状态页面。桌面 [URL 校验](../../client/src-tauri/src/server.rs)拒绝远端 HTTP,地址位于概览页。 - 2026-10-21 只读核对:`test-link.appx365.com` 解析到 `121.4.111.208`;服务器没有发现 Nginx、Certbot,80/443 未被监听。 - 本次目标:使用项目已有的 Go `autocert` 依赖,在现有 Gin 进程中提供公网 80/443、HTTP-01 自动签发/续签;管理 API 和页面展示域名及缓存证书状态;桌面将地址放在设置中,接受 HTTP(提示明文风险)。单域名场景无需 Nginx、Certbot 或额外的 `gin-gonic/autotls` 封装。 - 非目标:将证书私钥存到 SQLite/浏览器;允许任意命令、代理任意上游、公开认证服务内部端口;向桌面发放管理员 token;把 WireGuard 51820/UDP 交给 Nginx 代理。 - 保留行为:旧 `:18080` 管理界面与现有 VPN 专线不受证书签发流程影响;证书失败保持现有 HTTP 服务可用。 ## 2. 需求与约束 - FR-01:管理员能查看当前域名、DNS、监听地址、证书颁发者/到期日;接口不返回私钥、登录凭据或证书正文。 - FR-02:服务端仅为管理员保存的域名或 `VPN_TLS_DOMAIN` 指定的单一有效域名签发(环境变量优先),ACME 使用该域名 HostWhitelist;首次 HTTPS 握手签发,证书与账号密钥持久缓存于 `data/acme`,禁止任意域名签发。 - FR-03:续签由 `autocert.Manager` 自动管理;重启后缓存证书由首次握手加载并继续续签,到期日由管理页显示;签发或续签失败保留已有 18080 HTTP 入口。 - FR-04:HTTP 80 通过 `autocert.Manager.HTTPHandler` 放行 HTTP-01,其他当前域名请求重定向 HTTPS;443 用同一 Gin handler 提供管理站点与 API。OIDC 认证入口若另设域名需另行设计其路由,不从后台域名假设回调。 - FR-05:桌面客户端服务端地址移到“设置”,支持完整 HTTP/HTTPS 根地址并允许保存;HTTP 展示风险提示,但健康检查可以执行。不保存账号和令牌。 - NFR-01:管理 API 仅 `admin`,域名符合严格 FQDN 白名单;私钥缓存目录权限 0700,不暴露证书缓存内容或触发任意命令。 - NFR-02:部署前核对 DNS、云安全组 TCP 80/443、端口占用、现有入口与回退;续签有可观测到期信息。浏览器页面与 Tauri 环境的 URL 校验一致。 ## 3. 架构与方案 ```text Internet TCP 80 → autocert HTTPHandler(HTTP-01;其余跳转 HTTPS) Internet TCP 443 → autocert TLSConfig → 同一 Gin 管理站点 / API ACME 密钥与证书 → data/acme(重启保留) 管理页面 → admin API → 仅读 DNS、域名及证书缓存元数据 旧 TCP 18080 → 同一 Gin handler(保留兼容;后续限制公网访问) ``` 使用项目已存在的 `golang.org/x/crypto/acme/autocert`,无需额外 Gin 插件或依赖:管理员将域名保存到 `data/tls-domain`,重启后生效;`VPN_TLS_DOMAIN` 优先于文件配置,`VPN_TLS_HTTP_ADDR` / `VPN_TLS_HTTPS_ADDR` 可覆盖监听地址;未配置域名时完全保留旧启动行为。证书管理不写入业务数据库。首次公网 HTTPS 请求后才有真实缓存证书,管理页未签发前必须显示“等待首次访问”,不能声称证书已签发。HTTP 客户端输入只是用户选择;登录与设备令牌将来仍应推荐 HTTPS。 ## 4. 接口、数据与配置契约 - `GET /api/system/tls` 与 `PUT /api/system/tls/domain` 仅管理员可用,分别读取状态与保存单域名;状态返回当前/待启用域名、是否需重启、DNS、监听地址及 autocert 缓存证书的颁发者/到期日。没有手动签发按钮,首次 TLS 握手会触发签发。证书缓存位于 `data/acme`,不接收请求任意域名作为签发目标。 - 云安全组 TCP 80/443 与 DNS A 解析须对应当前服务器;`VPN_TLS_DOMAIN=test-link.appx365.com` 生效时若端口占用,应用拒绝启动并保持旧进程可回滚。 - 桌面仅在本机保存不含凭据的服务端地址;只允许 `http` / `https` 和根地址,拒绝 URL 账号、查询、片段、非根路径;实际健康检查继续调用 `/api/health`。 ## 5. 风险与验收 | 风险或失败场景 | 防护 / 恢复 | 可观察信号 | |---|---|---| | 域名指向错误或 80 不可达 | 不启用证书前先核对 DNS 和公网入站;失败保留旧 18080 | ACME 错误日志、证书未缓存 | | 80/443 端口占用 | 启动时先绑定两个端口,失败则终止新版进程并回退旧版 | 启动日志与端口监听 | | 续签失败与证书过期 | autocert 自动续签、到期日提示、缓存保留 | 证书到期信息与 ACME 日志 | | 公网 HTTP 管理凭据泄漏 | HTTPS 默认推荐;明示 HTTP 风险;服务端切换后限制 18080 公网 | HTTPS 可达、18080 仅可信入口 | | AC | 对应需求 | 前置与操作 | 预期结果 | 验证方式 | 状态 | |---|---|---|---|---|---| | AC-01 | FR-01, NFR-01 | 管理员/非管理员请求状态 | 仅管理员可查看,证书私钥不回传 | 路由权限检查;线上请求待验证 | 部分验证 | | AC-02 | FR-02, NFR-01 | 设置非法域名或端口占用 | 启动前拒绝,不向任意域名申请 | 域名/端口失败分支单测 | 已通过(本地) | | AC-03 | FR-03, FR-04, NFR-02 | 首次 HTTPS 请求与后续续签 | HTTP-01 可达、443 站点正确、缓存保留与自动续签 | DNS、ACME、TLS 实机验证 | Not Run | | AC-04 | FR-05 | 桌面保存远端 HTTP/HTTPS 并检测 | 地址保存在设置;HTTP 提示且能检测,其他协议拒绝 | Rust 单测、UI 构建;桌面实机待验证 | 部分验证 | ## 6. 发布与恢复 - 先完成本地构建和测试,备份现有二进制、确认公网 80/443 无占用及云安全组策略,再配置 `VPN_TLS_DOMAIN` 并切换新二进制。以 `https://test-link.appx365.com/api/health` 触发首次签发;确认 TLS 域名、证书到期和管理站点之后收敛 18080 公网权限。 - 若端口监听、HTTP-01 或首次握手失败,恢复原二进制/启动参数并重启旧 `:18080` 入口;`data/acme` 保留避免重复申请。不修改现有 VPN 网关或删除证书缓存。 - 私钥文件和用户数据不删除;证书吊销、DNS 变更及防火墙收敛分别决策。 ## 7. 待决问题 | 问题 | 是否阻塞 | 推荐方案 | |---|---|---| | 云安全组 TCP 80/443 是否放行 | 阻塞线上 HTTP-01 | 先核实公网访问再签发 | | `test-link.appx365.com` 是否同时承载 OIDC 认证服务 | 不阻塞管理站点 | 初版仅反代管理站点;OIDC 独立域名或另行设计路径路由 | | 证书到期告警 | 不阻塞本地实现 | 当前页面展示到期时间;后续可补主动告警 |