188 lines
4.6 KiB
Go
188 lines
4.6 KiB
Go
package cert_manager_wdd
|
||
|
||
import (
|
||
"agent-wdd/cloudflare"
|
||
"agent-wdd/log"
|
||
"crypto/ecdsa"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// DNSSolver DNS验证器
|
||
type DNSSolver struct {
|
||
CloudflareAPI *cloudflare.CloudflareClient // Cloudflare API客户端
|
||
}
|
||
|
||
// SetTXTRecord 设置DNS TXT记录
|
||
//
|
||
// 参数:
|
||
// - domain: 域名
|
||
// - token: 验证token
|
||
// - keyAuth: 验证密钥
|
||
//
|
||
// 返回:
|
||
// - error: 错误信息
|
||
func (ds *DNSSolver) SetTXTRecord(domain, token, keyAuth string) error {
|
||
log.Debug("设置DNS TXT记录: %s", domain)
|
||
|
||
// 生成记录名和内容
|
||
recordName := fmt.Sprintf("_acme-challenge.%s", domain)
|
||
if strings.HasPrefix(domain, "*.") {
|
||
// 通配符域名处理
|
||
recordName = fmt.Sprintf("_acme-challenge.%s", domain[2:])
|
||
}
|
||
|
||
// 计算记录值
|
||
// 对于DNS-01验证,值是密钥授权的SHA-256哈希的Base64编码
|
||
recordValue := keyAuth
|
||
|
||
// 获取域名的zone信息
|
||
zones, err := ds.CloudflareAPI.ListZones(nil)
|
||
if err != nil {
|
||
return fmt.Errorf("获取Cloudflare zones失败: %w", err)
|
||
}
|
||
|
||
var zoneID string
|
||
zoneName := ""
|
||
for _, zone := range zones {
|
||
// 对于常规域名,检查域名是否以zone名称结尾
|
||
// 对于通配符域名,检查去掉*. 前缀的域名是否以zone名称结尾
|
||
domainToCheck := domain
|
||
if strings.HasPrefix(domain, "*.") {
|
||
domainToCheck = domain[2:]
|
||
}
|
||
|
||
if strings.HasSuffix(domainToCheck, zone.Name) && (zoneName == "" || len(zone.Name) > len(zoneName)) {
|
||
zoneID = zone.ID
|
||
zoneName = zone.Name
|
||
}
|
||
}
|
||
|
||
if zoneID == "" {
|
||
return fmt.Errorf("未找到域名 %s 的Cloudflare Zone", domain)
|
||
}
|
||
|
||
// 创建TXT记录
|
||
record := cloudflare.DNSRecord{
|
||
Type: "TXT",
|
||
Name: recordName,
|
||
Content: recordValue,
|
||
TTL: 120,
|
||
}
|
||
|
||
_, err = ds.CloudflareAPI.CreateDNSRecord(zoneID, record)
|
||
if err != nil {
|
||
return fmt.Errorf("创建DNS TXT记录失败: %w", err)
|
||
}
|
||
|
||
log.Info("DNS TXT记录已创建: %s = %s", recordName, recordValue)
|
||
|
||
// 等待DNS传播
|
||
log.Info("等待DNS记录传播...")
|
||
time.Sleep(30 * time.Second)
|
||
|
||
return nil
|
||
}
|
||
|
||
// CleanupTXTRecord 清理DNS TXT记录
|
||
//
|
||
// 参数:
|
||
// - domain: 域名
|
||
// - token: 验证token
|
||
//
|
||
// 返回:
|
||
// - error: 错误信息
|
||
func (ds *DNSSolver) CleanupTXTRecord(domain, token string) error {
|
||
log.Debug("清理DNS TXT记录: %s", domain)
|
||
|
||
// 生成记录名
|
||
recordName := fmt.Sprintf("_acme-challenge.%s", domain)
|
||
if strings.HasPrefix(domain, "*.") {
|
||
// 通配符域名处理
|
||
recordName = fmt.Sprintf("_acme-challenge.%s", domain[2:])
|
||
}
|
||
|
||
// 获取域名的zone信息
|
||
zones, err := ds.CloudflareAPI.ListZones(nil)
|
||
if err != nil {
|
||
return fmt.Errorf("获取Cloudflare zones失败: %w", err)
|
||
}
|
||
|
||
var zoneID string
|
||
zoneName := ""
|
||
for _, zone := range zones {
|
||
domainToCheck := domain
|
||
if strings.HasPrefix(domain, "*.") {
|
||
domainToCheck = domain[2:]
|
||
}
|
||
|
||
if strings.HasSuffix(domainToCheck, zone.Name) && (zoneName == "" || len(zone.Name) > len(zoneName)) {
|
||
zoneID = zone.ID
|
||
zoneName = zone.Name
|
||
}
|
||
}
|
||
|
||
if zoneID == "" {
|
||
return fmt.Errorf("未找到域名 %s 的Cloudflare Zone", domain)
|
||
}
|
||
|
||
// 查找TXT记录
|
||
filter := &cloudflare.DNSRecordFilter{
|
||
Type: "TXT",
|
||
Name: recordName,
|
||
}
|
||
|
||
records, err := ds.CloudflareAPI.ListDNSRecords(zoneID, filter)
|
||
if err != nil {
|
||
return fmt.Errorf("查找DNS TXT记录失败: %w", err)
|
||
}
|
||
|
||
// 删除匹配的记录
|
||
for _, record := range records {
|
||
if record.Name == recordName && record.Type == "TXT" {
|
||
_, err = ds.CloudflareAPI.DeleteDNSRecord(zoneID, record.ID)
|
||
if err != nil {
|
||
return fmt.Errorf("删除DNS TXT记录失败: %w", err)
|
||
}
|
||
log.Info("DNS TXT记录已删除: %s", recordName)
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// IssueCertificate 颁发证书
|
||
//
|
||
// 参数:
|
||
// - domain: 域名
|
||
// - email: 注册邮箱
|
||
// - directoryURL: ACME目录URL
|
||
// - privateKey: 私钥
|
||
// - certDir: 证书保存目录
|
||
// - dnsSolver: DNS验证器
|
||
//
|
||
// 返回:
|
||
// - *CertInfo: 证书信息
|
||
// - error: 错误信息
|
||
func IssueCertificate(domain, email, directoryURL string, privateKey *ecdsa.PrivateKey, certDir string, dnsSolver *DNSSolver) (*CertInfo, error) {
|
||
log.Info("开始为域名 %s 颁发证书", domain)
|
||
|
||
// 创建ACME客户端
|
||
acmeClient, err := NewACMEClient(directoryURL, email, dnsSolver.CloudflareAPI)
|
||
if err != nil {
|
||
log.Error("创建ACME客户端失败: %v", err)
|
||
return nil, fmt.Errorf("创建ACME客户端失败: %w", err)
|
||
}
|
||
|
||
// 使用ACME客户端获取证书
|
||
certInfo, err := acmeClient.ObtainCertificate(domain, privateKey, certDir)
|
||
if err != nil {
|
||
log.Error("获取证书失败: %v", err)
|
||
return nil, fmt.Errorf("获取证书失败: %w", err)
|
||
}
|
||
|
||
log.Info("证书颁发成功: %s", domain)
|
||
return certInfo, nil
|
||
}
|