Files
WddSuperAgent/agent-wdd/cert_manager_wdd/DNSSolver.go

188 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}