Files
Havline/internal/acme/ca.go
T
mrlee dda0eb876e feat(cert,scripts,docs): Linux 原生安装包与证书申请三处修复(DNS 解析器 / Buypass 下线 / ZeroSSL 报错与密钥脱敏)(v1.2.13)
- Linux 安装包:scripts/build-linux.ps1 交叉编译出 fonu-<版本>-linux-{amd64,arm64}.tar.gz(二进制 + migrations + systemd 安装脚本 + 运行时资源 + sha256),scripts/fonu-install.sh 负责装机(/opt/fonu、/var/lib/fonu、/etc/fonu/fonu.env 首次生成随机会话密钥)并新增 --reset-admin;装完直接打印初始管理员口令

- 证书 DNS-01:lego 默认读系统 resolv.conf(容器里是 Docker 的 127.0.0.11,不返回权威 NS)导致 could not determine authoritative nameservers;现显式传 AddRecursiveNameservers,默认 223.5.5.5/1.1.1.1/8.8.8.8,设置项 acme_dns_resolvers 可覆盖(system = 回到系统解析器)

- Buypass 已停止签发(2025-10-15)并终止服务(2026-04-15):从可选 CA 移除,历史记录仍可展示,直接提交会被拦下并给出中文原因

- ZeroSSL:422 时带出 error.type/error.code(非 JSON 退回截断正文);同时修掉网络错误把含 access_key 的完整 URL 写进任务日志与 app.log 的密钥泄露

- 测试:解析器列表解析 3 例 + ZeroSSL EAB 4 例(含不泄露密钥断言);gofmt / go build / go vet / go test 全绿
2026-09-27 10:10:15 +08:00

101 lines
2.4 KiB
Go

package acme
import (
"fmt"
"strings"
"github.com/go-acme/lego/v4/lego"
)
const (
CALetsEncrypt = "letsencrypt"
CALetsEncryptStaging = "letsencrypt-staging"
CAZeroSSL = "zerossl"
CABuypass = "buypass"
)
// caRetired 记录已停止签发的 CA:仍然认识这些值(历史证书记录里可能存着,用于展示与清理),
// 但不允许再用来申请——否则用户只会看到一个难懂的 ACME 报错而不是「这家 CA 不干了」。
// 否则用户只会看到一个难懂的 ACME 报错。
var caRetired = map[string]string{
CABuypass: "Buypass 已于 2025-10-15 停止签发证书(ACME 服务 2026-04-15 终止),请改用 Let's Encrypt 或 ZeroSSL",
}
// CAUnavailableReason 返回该 CA 已停用的原因;可用的 CA 返回空字符串。
func CAUnavailableReason(ca string) string {
return caRetired[NormalizeCA(ca)]
}
const (
zeroSSLDirectoryURL = "https://acme.zerossl.com/v2/DV90"
buypassDirectoryURL = "https://api.buypass.com/acme/directory"
)
func NormalizeCA(ca string) string {
switch ca {
case "", CALetsEncrypt, "production":
return CALetsEncrypt
case CALetsEncryptStaging, "staging", "letsencrypt-test":
return CALetsEncryptStaging
case CAZeroSSL:
return CAZeroSSL
case CABuypass, "buypass-go":
return CABuypass
default:
return ca
}
}
func DirectoryURL(ca string) (string, error) {
switch NormalizeCA(ca) {
case CALetsEncrypt:
return lego.LEDirectoryProduction, nil
case CALetsEncryptStaging:
return lego.LEDirectoryStaging, nil
case CAZeroSSL:
return zeroSSLDirectoryURL, nil
case CABuypass:
return buypassDirectoryURL, nil
default:
return "", fmt.Errorf("不支持的颁发机构:%s", ca)
}
}
func CALabel(ca string) string {
switch NormalizeCA(ca) {
case CALetsEncrypt:
return "Let's Encrypt"
case CALetsEncryptStaging:
return "Let's Encrypt 测试"
case CAZeroSSL:
return "ZeroSSL"
case CABuypass:
return "Buypass"
default:
if ca == "" {
return "手动导入"
}
return ca
}
}
func ValidateCA(ca string) error {
_, err := DirectoryURL(ca)
return err
}
func ValidateCADomains(ca string, domains []string) error {
switch NormalizeCA(ca) {
case CABuypass:
if len(domains) > 5 {
return fmt.Errorf("Buypass 单张证书最多支持 5 个域名")
}
for _, domain := range domains {
if strings.HasPrefix(domain, "*.") {
return fmt.Errorf("Buypass 不支持通配符证书")
}
}
}
return nil
}