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 }