package apply import ( "ALLinSSL/backend/internal/access" "ALLinSSL/backend/internal/cert" "ALLinSSL/backend/public" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/json" "fmt" azcorecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/providers/dns/alidns" "github.com/go-acme/lego/v4/providers/dns/azuredns" "github.com/go-acme/lego/v4/providers/dns/baiducloud" "github.com/go-acme/lego/v4/providers/dns/cloudflare" "github.com/go-acme/lego/v4/providers/dns/cloudns" "github.com/go-acme/lego/v4/providers/dns/godaddy" "github.com/go-acme/lego/v4/providers/dns/huaweicloud" "github.com/go-acme/lego/v4/providers/dns/namecheap" "github.com/go-acme/lego/v4/providers/dns/ns1" "github.com/go-acme/lego/v4/providers/dns/route53" "github.com/go-acme/lego/v4/providers/dns/tencentcloud" "github.com/go-acme/lego/v4/providers/dns/volcengine" "github.com/go-acme/lego/v4/providers/dns/westcn" "github.com/go-acme/lego/v4/registration" "net/http" "net/url" "strconv" "strings" "time" ) var AlgorithmMap = map[string]certcrypto.KeyType{ "RSA2048": certcrypto.RSA2048, "RSA3072": certcrypto.RSA3072, "RSA4096": certcrypto.RSA4096, "RSA8192": certcrypto.RSA8192, "EC256": certcrypto.EC256, "EC384": certcrypto.EC384, } var CADirURLMap = map[string]string{ "Let's Encrypt": "https://acme-v02.api.letsencrypt.org/directory", "zerossl": "https://acme.zerossl.com/v2/DV90", "google": "https://dv.acme-v02.api.pki.goog/directory", "sslcom": "https://acme.ssl.com/sslcom-dv-rsa", "sslcom-rsa": "https://acme.ssl.com/sslcom-dv-rsa", "sslcom-ecc": "https://acme.ssl.com/sslcom-dv-ecc", } func GetSqlite() (*public.Sqlite, error) { s, err := public.NewSqlite("data/accounts.db", "") if err != nil { return nil, err } s.TableName = "_accounts" return s, nil } func GetDNSProvider(providerName string, creds map[string]string, httpClient *http.Client) (challenge.Provider, error) { switch providerName { case "tencentcloud": config := tencentcloud.NewDefaultConfig() config.SecretID = creds["secret_id"] config.SecretKey = creds["secret_key"] return tencentcloud.NewDNSProviderConfig(config) case "cloudflare": config := cloudflare.NewDefaultConfig() config.AuthEmail = creds["email"] config.AuthKey = creds["api_key"] return cloudflare.NewDNSProviderConfig(config) case "aliyun": config := alidns.NewDefaultConfig() config.APIKey = creds["access_key_id"] config.SecretKey = creds["access_key_secret"] return alidns.NewDNSProviderConfig(config) case "huaweicloud": config := huaweicloud.NewDefaultConfig() config.AccessKeyID = creds["access_key"] config.SecretAccessKey = creds["secret_key"] // 不传会报错 config.Region = "cn-north-1" return huaweicloud.NewDNSProviderConfig(config) case "baidu": config := baiducloud.NewDefaultConfig() config.AccessKeyID = creds["access_key"] config.SecretAccessKey = creds["secret_key"] return baiducloud.NewDNSProviderConfig(config) case "westcn": config := westcn.NewDefaultConfig() config.Username = creds["username"] config.Password = creds["password"] return westcn.NewDNSProviderConfig(config) case "volcengine": config := volcengine.NewDefaultConfig() config.AccessKey = creds["access_key"] config.SecretKey = creds["secret_key"] return volcengine.NewDNSProviderConfig(config) case "godaddy": config := godaddy.NewDefaultConfig() config.APIKey = creds["api_key"] config.APISecret = creds["api_secret"] if httpClient != nil { config.HTTPClient = httpClient } return godaddy.NewDNSProviderConfig(config) case "namecheap": config := namecheap.NewDefaultConfig() config.APIUser = creds["api_user"] config.APIKey = creds["api_key"] return namecheap.NewDNSProviderConfig(config) case "ns1": config := ns1.NewDefaultConfig() config.APIKey = creds["api_key"] return ns1.NewDNSProviderConfig(config) case "cloudns": config := cloudns.NewDefaultConfig() config.AuthID = creds["auth_id"] config.AuthPassword = creds["auth_password"] return cloudns.NewDNSProviderConfig(config) case "aws": config := route53.NewDefaultConfig() config.AccessKeyID = creds["access_key_id"] config.SecretAccessKey = creds["secret_access_key"] return route53.NewDNSProviderConfig(config) case "azure": config := azuredns.NewDefaultConfig() config.TenantID = creds["tenant_id"] config.ClientID = creds["client_id"] config.ClientSecret = creds["client_secret"] switch strings.ToLower(creds["environment"]) { case "", "default", "public", "azurecloud": config.Environment = azcorecloud.AzurePublic case "china", "chinacloud", "azurechina", "azurechinacloud": config.Environment = azcorecloud.AzureChina case "usgovernment", "government", "azureusgovernment", "azuregovernment": config.Environment = azcorecloud.AzureGovernment default: return nil, fmt.Errorf("不支持的 Azure 环境: %s", creds["environment"]) } return azuredns.NewDNSProviderConfig(config) default: return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName) } } func GetAcmeClient(db *public.Sqlite, email, algorithm, eabId string, httpClient *http.Client, logger *public.Logger) (*lego.Client, error) { var ( ca string eabData map[string]any err error ) switch eabId { case "let", "": ca = "Let's Encrypt" default: eabData, err = access.GetEAB(eabId) if err != nil { return nil, err } if eabData == nil { return nil, fmt.Errorf("未找到EAB信息") } if eabData["Kid"] == nil { return nil, fmt.Errorf("Kid不能为空") } if eabData["HmacEncoded"] == nil { return nil, fmt.Errorf("HmacEncoded不能为空") } ca = eabData["ca"].(string) if ca == "sslcom" { switch algorithm[0] { case 'R', 'r': ca = "sslcom-rsa" case 'E', 'e': ca = "sslcom-ecc" } } } user, err := LoadUserFromDB(db, email, ca) if err != nil { logger.Debug("acme账号不存在,注册新账号") privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) user = &MyUser{ Email: email, key: privateKey, } } config := lego.NewConfig(user) config.Certificate.KeyType = AlgorithmMap[algorithm] config.CADirURL = CADirURLMap[ca] if httpClient != nil { config.HTTPClient = httpClient } client, err := lego.NewClient(config) if err != nil { return nil, err } if user.Registration == nil { logger.Debug("正在注册账号:" + email) var reg *registration.Resource if eabData != nil { Kid := eabData["Kid"].(string) HmacEncoded := eabData["HmacEncoded"].(string) reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: true, Kid: Kid, HmacEncoded: HmacEncoded, }) } else { reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) } if err != nil { return nil, err } user.Registration = reg err = SaveUserToDB(db, user, ca) if err != nil { return nil, err } logger.Debug("acme账号注册并保存成功") } return client, nil } func GetCert(runId string, domainArr []string, endDay int, logger *public.Logger) (map[string]any, error) { if runId == "" { return nil, fmt.Errorf("参数错误:_runId") } s, err := public.NewSqlite("data/data.db", "") if err != nil { return nil, err } s.TableName = "workflow_history" defer s.Close() // 查询 workflowId wh, err := s.Where("id=?", []interface{}{runId}).Select() if err != nil { return nil, err } if len(wh) <= 0 { return nil, fmt.Errorf("未获取到对应的workflowId") } s.TableName = "cert" certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select() if err != nil { return nil, err } if len(certs) <= 0 { return nil, fmt.Errorf("未获取到当前工作流下的证书") } layout := "2006-01-02 15:04:05" var maxDays float64 var maxItem map[string]any for i := range certs { if !public.ContainsAllIgnoreBRepeats(strings.Split(certs[i]["domains"].(string), ","), domainArr) { continue } endTimeStr, ok := certs[i]["end_time"].(string) if !ok { continue } endTime, err := time.Parse(layout, endTimeStr) if err != nil { continue } diff := endTime.Sub(time.Now()).Hours() / 24 if diff > maxDays { maxDays = diff maxItem = certs[i] } } if maxItem == nil { return nil, fmt.Errorf("未获取到对应的证书") } if int(maxDays) <= endDay { return nil, fmt.Errorf("证书已过期或即将过期,剩余天数:%d 小于%d天", int(maxDays), endDay) } // 证书未过期,直接返回 logger.Debug(fmt.Sprintf("上次证书申请成功,域名:%s,剩余天数:%d 大于%d天,已跳过申请复用此证书", maxItem["domains"], int(maxDays), endDay)) return map[string]any{ "cert": maxItem["cert"], "key": maxItem["key"], "issuerCert": maxItem["issuer_cert"], }, nil } func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { db, err := GetSqlite() if err != nil { return nil, err } defer db.Close() email, ok := cfg["email"].(string) if !ok { return nil, fmt.Errorf("参数错误:email") } domains, ok := cfg["domains"].(string) if !ok { return nil, fmt.Errorf("参数错误:domains") } providerStr, ok := cfg["provider"].(string) if !ok { return nil, fmt.Errorf("参数错误:provider") } endDay := 30 switch v := cfg["end_day"].(type) { case float64: endDay = int(v) case int: endDay = v case string: if v != "" { endDay, err = strconv.Atoi(v) if err != nil { return nil, fmt.Errorf("参数错误:end_day") } } case int64: endDay = int(v) } algorithm, ok := cfg["algorithm"].(string) if !ok { algorithm = "RSA2048" } var httpClient *http.Client proxy, ok := cfg["proxy"].(string) if ok && proxy != "" { // 构建代理 HTTP 客户端 proxyURL, err := url.Parse(proxy) // 替换为你的代理地址 if err != nil { return nil, fmt.Errorf("无效的代理地址: %v", err) } httpClient = &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyURL(proxyURL), }, Timeout: 30 * time.Second, } } var eabId string switch v := cfg["eabId"].(type) { case float64: eabId = strconv.Itoa(int(v)) case string: eabId = v default: eabId = "" } var providerID string switch v := cfg["provider_id"].(type) { case float64: providerID = strconv.Itoa(int(v)) case string: providerID = v default: return nil, fmt.Errorf("参数错误:provider_id") } var NameServers []string if cfg["name_server"] == nil { NameServers = []string{ "8.8.8.8:53", "1.1.1.1:53", } } else { if nameServerStr, ok := cfg["name_server"].(string); ok { NameServers = strings.Split(nameServerStr, ",") for i := range NameServers { NameServers[i] = strings.TrimSpace(NameServers[i]) } } else { return nil, fmt.Errorf("参数错误:name_server") } } var skipCheck bool if cfg["skip_check"] == nil { // 默认跳过预检查 skipCheck = true // cf 默认不跳过预检查 if providerStr == "cloudflare" { skipCheck = false } } else { switch v := cfg["skip_check"].(type) { case int: if v > 0 { skipCheck = true } else { skipCheck = false } case float64: if v > 0 { skipCheck = true } else { skipCheck = false } case string: if v == "true" || v == "1" { skipCheck = true } else { skipCheck = false } case bool: skipCheck = v default: return nil, fmt.Errorf("参数错误:skip_check") } } domainArr := strings.Split(domains, ",") for i := range domainArr { domainArr[i] = strings.TrimSpace(domainArr[i]) } // 获取上次申请的证书 runId, ok := cfg["_runId"].(string) if !ok { return nil, fmt.Errorf("参数错误:_runId") } certData, err := GetCert(runId, domainArr, endDay, logger) if err != nil { logger.Debug("未获取到符合条件的本地证书:" + err.Error()) } else { return certData, nil } logger.Debug("正在申请证书,域名: " + domains) // 创建 ACME 客户端 client, err := GetAcmeClient(db, email, algorithm, eabId, httpClient, logger) if err != nil { return nil, err } // 获取 DNS 验证提供者 providerData, err := access.GetAccess(providerID) if err != nil { return nil, err } providerConfigStr, ok := providerData["config"].(string) if !ok { return nil, fmt.Errorf("api配置错误") } // 解析 JSON 配置 var providerConfig map[string]string err = json.Unmarshal([]byte(providerConfigStr), &providerConfig) if err != nil { return nil, err } // DNS 验证 provider, err := GetDNSProvider(providerStr, providerConfig, httpClient) if err != nil { return nil, fmt.Errorf("创建 DNS provider 失败: %v", err) } if skipCheck { // 跳过预检查 err = client.Challenge.SetDNS01Provider(provider, dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { return true, nil }), dns01.AddRecursiveNameservers(NameServers), ) } else { err = client.Challenge.SetDNS01Provider(provider, dns01.AddRecursiveNameservers(NameServers), ) } if err != nil { return nil, err } // fmt.Println(strings.Split(domains, ",")) request := certificate.ObtainRequest{ Domains: domainArr, Bundle: true, } certObj, err := client.Certificate.Obtain(request) if err != nil { return nil, err } certStr := string(certObj.Certificate) keyStr := string(certObj.PrivateKey) issuerCertStr := string(certObj.IssuerCertificate) // 保存证书和私钥 data := map[string]any{ "cert": certStr, "key": keyStr, "issuerCert": issuerCertStr, } _, err = cert.SaveCert("workflow", keyStr, certStr, issuerCertStr, runId) if err != nil { return nil, err } return data, nil }