Guard HTTP to HTTPS IP certificate staging

This commit is contained in:
Codex committed 2026-10-26 15:37:11 +08:00
1 parent d3699abb18
commit 3c76d2d118
3 files changed
+30 -2

No files matched your search

+18
View File
@@ -172,6 +172,24 @@ func (s *Server) setTLSDomain(c *gin.Context) {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()}) c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
return return
} }
// A plain HTTP site can stage HTTPS on the same host before a certificate
// exists. The explicit activation action sets up HTTP-01, issues the
// certificate and only then replaces the live site.
if actual.Scheme == "http" && scheme == "https" && actual.Domain == strings.ToLower(strings.TrimSpace(req.Domain)) && !nginxgateway.CertificateReady(actual.Domain) {
if !tlsservice.ValidDomain(actual.Domain) {
if err := nginxgateway.CheckIPCertificateSupport(actual.Domain); err != nil {
c.JSON(http.StatusPreconditionFailed, gin.H{"error": err.Error()})
return
}
}
if err := tlsservice.ConfigurePublicEntry(service.BaseDir(), scheme, req.Domain, port); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
store.Audit(c.GetString("username"), "tls_domain_staged", newBase, "待签发证书并启用 HTTPS")
c.JSON(http.StatusOK, gin.H{"status": tlsservice.Status(), "site_activation_required": true})
return
}
authPort, err := authGatewayPort(newBase) authPort, err := authGatewayPort(newBase)
if err != nil { if err != nil {
c.JSON(http.StatusConflict, gin.H{"error": err.Error()}) c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
+10
View File
@@ -348,6 +348,16 @@ func certificateExists(domain string) bool {
!time.Now().Before(leaf.NotBefore) && time.Now().Before(leaf.NotAfter) !time.Now().Before(leaf.NotBefore) && time.Now().Before(leaf.NotAfter)
} }
// CertificateReady checks that the installed leaf is valid for the target host.
func CertificateReady(host string) bool { return certificateExists(host) }
// CheckIPCertificateSupport fails before changing a live site when the
// Certbot service cannot issue and renew a short-lived IP certificate.
func CheckIPCertificateSupport(host string) error {
_, err := ipCertificateArgs(host)
return err
}
func ipCertificateArgs(domain string) ([]string, error) { func ipCertificateArgs(domain string) ([]string, error) {
// The scheduled systemd service on supported hosts must use the same Certbot // The scheduled systemd service on supported hosts must use the same Certbot
// binary as interactive issuance, or six-day certificates may expire. // binary as interactive issuance, or six-day certificates may expire.
+2 -2
View File
@@ -30,7 +30,7 @@
## 4. 接口、数据与配置契约 ## 4. 接口、数据与配置契约
- `GET /api/system/tls` 增加可选 `renewal_events` 最近成功记录(时间、证书标识);旧字段兼容。 - `GET /api/system/tls` 增加可选 `renewal_events` 最近成功记录(时间、证书标识);旧字段兼容。
- `PUT /api/system/tls/domain` 接受 HTTPS 公网 IPv4;生产活跃 OIDC 回调仍可返回 409,现有站点没有目标证书时不能直接热切换。 - `PUT /api/system/tls/domain` 接受 HTTPS 公网 IPv4;生产活跃 OIDC 回调仍可返回 409。已有同一 Host 的 HTTP 站点在目标证书缺失时只保存 HTTPS 草稿,显式执行站点启用步骤后才签发并切换;在 Certbot 能力不足时保存前返回 412,保留原站点。
- 固定续签记录:`/var/log/opsx-vpn/cert-renewals.log`,仅成功执行钩子时追加;首次签发和上传应由现有审计记录区分,不误标续签。 - 固定续签记录:`/var/log/opsx-vpn/cert-renewals.log`,仅成功执行钩子时追加;首次签发和上传应由现有审计记录区分,不误标续签。
## 5. 风险与验收 ## 5. 风险与验收
@@ -45,7 +45,7 @@
| AC | 对应需求 | 操作 | 预期结果 | 验证 | 结果 | | AC | 对应需求 | 操作 | 预期结果 | 验证 | 结果 |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| AC-01 | FR-01 | HTTP IP、HTTPS 域名无证书 | 不显示伪证书或 0001 日期 | 前端构建/代码检查 | 构建和代码检查通过;未做交互测试 | | AC-01 | FR-01 | HTTP IP、HTTPS 域名无证书 | 不显示伪证书或 0001 日期 | 前端构建/代码检查 | 构建和代码检查通过;未做交互测试 |
| AC-02 | FR-02 | 验证公网 IPv4、私网、非法 IP 和 Certbot 参数 | 公网仅 HTTPS 可签,低版本明确拒绝,域名原流程不变 | Go 单测/本机渲染 | Go 单测通过;未在兼容 Certbot 上签发真实 IP 证书 | | AC-02 | FR-02 | 验证公网 IPv4、私网、非法 IP 和 Certbot 参数 | 公网 IP HTTPS 可签,低版本明确拒绝,域名原流程不变 | Go 单测/本机渲染 | Go 单测通过;未在兼容 Certbot 上签发真实 IP 证书 |
| AC-03 | FR-03 | 模拟 deploy hook 记录 | 成功重载后追加事件;不记录失败/未续签 | Go 测试/只读检查 | Go 事件解析与 shell 语法通过;真实续签未发生 | | AC-03 | FR-03 | 模拟 deploy hook 记录 | 成功重载后追加事件;不记录失败/未续签 | Go 测试/只读检查 | Go 事件解析与 shell 语法通过;真实续签未发生 |
| AC-04 | NFR-01 | 远端部署检查 | 生产域名登录、OIDC、证书/定时器正常,未改公网模式 | 远端只读探测 | Not Run | | AC-04 | NFR-01 | 远端部署检查 | 生产域名登录、OIDC、证书/定时器正常,未改公网模式 | 远端只读探测 | Not Run |