添加SSL证书管理功能,包括安装、续期、列出、撤销和申请证书的命令,同时更新依赖项和修复磁盘使用情况计算逻辑。
This commit is contained in:
187
agent-wdd/cert_manager_wdd/DNSSolver.go
Normal file
187
agent-wdd/cert_manager_wdd/DNSSolver.go
Normal file
@@ -0,0 +1,187 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user