From 6f3d4eb81f1404c8a817e210fa1665a8cb66e14b Mon Sep 17 00:00:00 2001 From: zhangchenhao Date: Thu, 22 May 2025 18:47:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=94=B3=E8=AF=B7=E8=AF=81?= =?UTF-8?q?=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/access.go | 26 +++- backend/internal/access/eab.go | 8 +- backend/internal/cert/apply/apply.go | 156 ++++++++----------- backend/internal/cert/deploy/deploy.go | 8 + backend/internal/cert/deploy/tencentcloud.go | 30 ++-- backend/migrations/init.go | 3 +- 6 files changed, 120 insertions(+), 111 deletions(-) diff --git a/backend/app/api/access.go b/backend/app/api/access.go index ccbc80a..2017d25 100644 --- a/backend/app/api/access.go +++ b/backend/app/api/access.go @@ -180,6 +180,7 @@ func AddEAB(c *gin.Context) { Kid string `form:"Kid"` HmacEncoded string `form:"HmacEncoded"` CA string `form:"ca"` + Mail string `form:"mail"` } err := c.Bind(&form) if err != nil { @@ -190,6 +191,7 @@ func AddEAB(c *gin.Context) { form.Kid = strings.TrimSpace(form.Kid) form.HmacEncoded = strings.TrimSpace(form.HmacEncoded) form.CA = strings.TrimSpace(form.CA) + form.Mail = strings.TrimSpace(form.Mail) if form.Name == "" { public.FailMsg(c, "名称不能为空") return @@ -206,9 +208,14 @@ func AddEAB(c *gin.Context) { public.FailMsg(c, "CA不能为空") return } - err = access.AddEAB(form.Name, form.Kid, form.HmacEncoded, form.CA) + if form.Mail == "" { + public.FailMsg(c, "Email不能为空") + return + } + err = access.AddEAB(form.Name, form.Kid, form.HmacEncoded, form.CA, form.Mail) if err != nil { public.FailMsg(c, err.Error()) + return } public.SuccessMsg(c, "添加成功") return @@ -221,6 +228,7 @@ func UpdEAB(c *gin.Context) { Kid string `form:"Kid"` HmacEncoded string `form:"HmacEncoded"` CA string `form:"ca"` + Mail string `form:"mail"` } err := c.Bind(&form) if err != nil { @@ -231,6 +239,7 @@ func UpdEAB(c *gin.Context) { form.Kid = strings.TrimSpace(form.Kid) form.HmacEncoded = strings.TrimSpace(form.HmacEncoded) form.CA = strings.TrimSpace(form.CA) + form.Mail = strings.TrimSpace(form.Mail) if form.Name == "" { public.FailMsg(c, "名称不能为空") return @@ -247,7 +256,11 @@ func UpdEAB(c *gin.Context) { public.FailMsg(c, "CA不能为空") return } - err = access.UpdEAB(form.ID, form.Name, form.Kid, form.HmacEncoded, form.CA) + if form.Mail == "" { + public.FailMsg(c, "mail不能为空") + return + } + err = access.UpdEAB(form.ID, form.Name, form.Kid, form.HmacEncoded, form.CA, form.Mail) if err != nil { public.FailMsg(c, err.Error()) } @@ -313,6 +326,7 @@ func TestAccess(c *gin.Context) { result = deploy.QiniuAPITest(form.ID) default: public.FailMsg(c, "不支持测试的提供商") + return } if result != nil { @@ -337,7 +351,7 @@ func GetSiteList(c *gin.Context) { public.FailMsg(c, err.Error()) return } - + var siteList []any switch form.Type { case "btpanel": @@ -345,11 +359,11 @@ func GetSiteList(c *gin.Context) { default: public.FailMsg(c, "不支持的提供商") } - + if err != nil { public.FailMsg(c, fmt.Sprintf("获取网站列表失败%v", err)) return } - + public.SuccessData(c, siteList, len(siteList)) -} \ No newline at end of file +} diff --git a/backend/internal/access/eab.go b/backend/internal/access/eab.go index b721854..0fc9707 100644 --- a/backend/internal/access/eab.go +++ b/backend/internal/access/eab.go @@ -62,7 +62,7 @@ func GetEABList(search string, p, limit int64) ([]map[string]any, int, error) { return data, int(count), nil } -func AddEAB(name, Kid, HmacEncoded, ca string) error { +func AddEAB(name, Kid, HmacEncoded, ca, mail string) error { s, err := GetSqliteEAB() if err != nil { return err @@ -76,11 +76,12 @@ func AddEAB(name, Kid, HmacEncoded, ca string) error { "ca": ca, "update_time": now, "create_time": now, + "mail": mail, }) return err } -func UpdEAB(id, name, Kid, HmacEncoded, ca string) error { +func UpdEAB(id, name, Kid, HmacEncoded, ca, mail string) error { s, err := GetSqliteEAB() if err != nil { return err @@ -93,6 +94,7 @@ func UpdEAB(id, name, Kid, HmacEncoded, ca string) error { "HmacEncoded": HmacEncoded, "ca": ca, "update_time": now, + "mail": mail, }) return err } @@ -113,7 +115,7 @@ func GetEAB(id string) (map[string]interface{}, error) { if err != nil { return nil, err } - s.Close() + defer s.Close() data, err := s.Where("id = ?", []interface{}{id}).Find() if err != nil { return nil, err diff --git a/backend/internal/cert/apply/apply.go b/backend/internal/cert/apply/apply.go index 2692edc..3d3ba48 100644 --- a/backend/internal/cert/apply/apply.go +++ b/backend/internal/cert/apply/apply.go @@ -92,13 +92,38 @@ func GetDNSProvider(providerName string, creds map[string]string) (challenge.Pro config.AccessKey = creds["access_key"] config.SecretKey = creds["secret_key"] return volcengine.NewDNSProviderConfig(config) - + default: return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName) } } -func GetAcmeClient(db *public.Sqlite, email, algorithm, ca, proxy, eabId string, logger *public.Logger) (*lego.Client, error) { +func GetAcmeClient(db *public.Sqlite, email, algorithm, proxy, eabId string, 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) + } + user, err := LoadUserFromDB(db, email, ca) if err != nil { logger.Debug("acme账号不存在,注册新账号") @@ -107,101 +132,54 @@ func GetAcmeClient(db *public.Sqlite, email, algorithm, ca, proxy, eabId string, Email: email, key: privateKey, } - - config := lego.NewConfig(user) - config.Certificate.KeyType = AlgorithmMap[algorithm] - config.CADirURL = CADirURLMap[ca] - if 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, - } - config.HTTPClient = httpClient - } - client, err := lego.NewClient(config) + } + config := lego.NewConfig(user) + config.Certificate.KeyType = AlgorithmMap[algorithm] + config.CADirURL = CADirURLMap[ca] + if proxy != "" { + // 构建代理 HTTP 客户端 + proxyURL, err := url.Parse(proxy) // 替换为你的代理地址 if err != nil { - return nil, err + return nil, fmt.Errorf("无效的代理地址: %v", err) } + httpClient := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + }, + Timeout: 30 * time.Second, + } + 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 - switch ca { - case "Let's Encrypt": - reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - case "zerossl", "google": - // 获取EAB参数 - var eabData map[string]any - if eabId == "" { - data, err := access.GetAllEAB(ca) - if err != nil { - return nil, err - } - if len(data) <= 0 { - return nil, fmt.Errorf("未找到EAB信息") - } - eabData = data[0] - } else { - eabData, err = access.GetEAB(eabId) - if err != nil { - return nil, err - } - if eabData == nil { - return nil, fmt.Errorf("未找到EAB信息") - } - } - Kid := eabData["kid"].(string) + if eabData != nil { + Kid := eabData["Kid"].(string) HmacEncoded := eabData["HmacEncoded"].(string) reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: true, Kid: Kid, HmacEncoded: HmacEncoded, }) - default: + } 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 - } else { - config := lego.NewConfig(user) - config.Certificate.KeyType = AlgorithmMap[algorithm] - config.CADirURL = CADirURLMap[ca] - if 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, - } - config.HTTPClient = httpClient - } - - // 初始化 ACME 客户端 - client, err := lego.NewClient(config) - if err != nil { - return nil, err - } - return client, nil } + return client, nil } func GetCert(runId string, domainArr []string, endDay int, logger *public.Logger) (map[string]any, error) { @@ -272,7 +250,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { return nil, err } defer db.Close() - + email, ok := cfg["email"].(string) if !ok { return nil, fmt.Errorf("参数错误:email") @@ -305,10 +283,6 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if !ok { algorithm = "RSA2048" } - ca, ok := cfg["ca"].(string) - if !ok { - ca = "Let's Encrypt" - } proxy, ok := cfg["proxy"].(string) if !ok { proxy = "" @@ -322,7 +296,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { default: eabId = "" } - + var providerID string switch v := cfg["provider_id"].(type) { case float64: @@ -348,7 +322,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { return nil, fmt.Errorf("参数错误:name_server") } } - + var skipCheck bool if cfg["skip_check"] == nil { // 默认跳过预检查 @@ -383,12 +357,12 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { 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 { @@ -402,7 +376,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { } logger.Debug("正在申请证书,域名: " + domains) // 创建 ACME 客户端 - client, err := GetAcmeClient(db, email, algorithm, ca, proxy, eabId, logger) + client, err := GetAcmeClient(db, email, algorithm, proxy, eabId, logger) if err != nil { return nil, err } @@ -421,13 +395,13 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if err != nil { return nil, err } - + // DNS 验证 provider, err := GetDNSProvider(providerStr, providerConfig) if err != nil { return nil, fmt.Errorf("创建 DNS provider 失败: %v", err) } - + if skipCheck { // 跳过预检查 err = client.Challenge.SetDNS01Provider(provider, @@ -444,7 +418,7 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { if err != nil { return nil, err } - + // fmt.Println(strings.Split(domains, ",")) request := certificate.ObtainRequest{ Domains: domainArr, @@ -454,18 +428,18 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { 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 diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index abf1077..ada8ace 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -31,6 +31,14 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { cfg["resource_type"] = "cos" logger.Debug("部署到腾讯云COS...") return DeployToTX(cfg) + case "tencentcloud-waf": + cfg["resource_type"] = "waf" + logger.Debug("部署到腾讯云WAF...") + return DeployToTX(cfg) + case "tencentcloud-teo": + cfg["resource_type"] = "teo" + logger.Debug("部署到腾讯云EdgeOne...") + return DeployToTX(cfg) case "1panel": logger.Debug("部署到1Panel...") return Deploy1panel(cfg) diff --git a/backend/internal/cert/deploy/tencentcloud.go b/backend/internal/cert/deploy/tencentcloud.go index 696b4f5..4478076 100644 --- a/backend/internal/cert/deploy/tencentcloud.go +++ b/backend/internal/cert/deploy/tencentcloud.go @@ -29,6 +29,7 @@ func UploadToTX(client *ssl.Client, key, cert string) (string, error) { request := ssl.NewUploadCertificateRequest() request.CertificatePublicKey = common.StringPtr(cert) request.CertificatePrivateKey = common.StringPtr(key) + request.Repeatable = common.BoolPtr(false) // 返回的resp是一个UploadCertificateResponse的实例,与请求对象对应 response, err := client.UploadCertificate(request) if _, ok := err.(*errors.TencentCloudSDKError); ok { @@ -53,7 +54,7 @@ func DeployToTX(cfg map[string]any) error { if !ok { return fmt.Errorf("证书错误:cert") } - + var providerID string switch v := cfg["provider_id"].(type) { case float64: @@ -83,26 +84,35 @@ func DeployToTX(cfg map[string]any) error { region = r } client := ClientTencentcloud(providerConfig["secret_id"], providerConfig["secret_key"], region) - + // 上传证书 certificateId, err := UploadToTX(client, strings.TrimSpace(keyPem), strings.TrimSpace(certPem)) if err != nil { return err } // fmt.Println(certificateId) - + request := ssl.NewDeployCertificateInstanceRequest() - + request.CertificateId = common.StringPtr(certificateId) - if cfg["resource_type"] == "cdn" { + resourceType := cfg["resource_type"].(string) + switch resourceType { + case "cdn", "waf", "teo": domain, ok := cfg["domain"].(string) if !ok { return fmt.Errorf("参数错误:domain") } - request.InstanceIdList = common.StringPtrs([]string{domain}) - request.ResourceType = common.StringPtr("cdn") - } - if cfg["resource_type"] == "cos" { + domain = strings.TrimSpace(domain) + domainArray := strings.Split(domain, ",") + if len(domainArray) == 0 { + return fmt.Errorf("参数错误:domain") + } + for i, d := range domainArray { + domainArray[i] = strings.TrimSpace(d) + } + request.InstanceIdList = common.StringPtrs(domainArray) + request.ResourceType = common.StringPtr(resourceType) + case "cos": domain, ok := cfg["domain"].(string) if !ok { return fmt.Errorf("参数错误:domain") @@ -118,7 +128,7 @@ func DeployToTX(cfg map[string]any) error { request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", region, bucket, domain)}) request.ResourceType = common.StringPtr("cos") } - + // 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应 response, err := client.DeployCertificateInstance(request) if _, ok := err.(*errors.TencentCloudSDKError); ok { diff --git a/backend/migrations/init.go b/backend/migrations/init.go index 18d72f0..45a1f5e 100644 --- a/backend/migrations/init.go +++ b/backend/migrations/init.go @@ -187,7 +187,8 @@ func init() { HmacEncoded TEXT not null, ca TEXT not null, create_time TEXT, - update_time TEXT + update_time TEXT, + mail TEXT not null ); `)