diff --git a/api/waf_sslorder.go b/api/waf_sslorder.go index f81e4e5..7d28a9b 100644 --- a/api/waf_sslorder.go +++ b/api/waf_sslorder.go @@ -39,6 +39,9 @@ func (w *WafSslOrderApi) AddApi(c *gin.Context) { var req request.WafSslorderaddReq err := c.ShouldBindJSON(&req) if err == nil { + if req.ApplyMethod != "dns01" { + req.SkipDNSVerify = 0 + } hostBean := wafHostService.GetDetailByCodeApi(req.HostCode) if hostBean.Id == "" { response.FailWithMessage("查找主机未找到", c) @@ -178,6 +181,9 @@ func (w *WafSslOrderApi) ModifyApi(c *gin.Context) { var req request.WafSslordereditReq err := c.ShouldBindJSON(&req) if err == nil { + if req.ApplyMethod != "dns01" { + req.SkipDNSVerify = 0 + } existingOrder := wafSslOrderService.GetDetailById(req.Id) if existingOrder.Id == "" { response.FailWithMessage("SSL订单不存在", c) diff --git a/model/request/waf_sslorder_req.go b/model/request/waf_sslorder_req.go index 41a0c41..3f9bca4 100644 --- a/model/request/waf_sslorder_req.go +++ b/model/request/waf_sslorder_req.go @@ -6,6 +6,7 @@ type WafSslorderaddReq struct { HostCode string `json:"host_code"` //网站唯一码(主要键) ApplyPlatform string `json:"apply_platform"` //申请平台 ApplyMethod string `json:"apply_method"` //申请方式http,dns + SkipDNSVerify int64 `json:"skip_dns_verify"` //dns01是否跳过本地DNS传播校验 0否 1是 ApplyDns string `json:"apply_dns"` //申请dns服务商 ApplyEmail string `json:"apply_email"` //申请邮箱 ApplyDomain string `json:"apply_domain"` //申请域名 @@ -16,6 +17,7 @@ type WafSslordereditReq struct { HostCode string `json:"host_code"` //网站唯一码(主要键) ApplyPlatform string `json:"apply_platform"` //申请平台 ApplyMethod string `json:"apply_method"` //申请方式http01,dns + SkipDNSVerify int64 `json:"skip_dns_verify"` //dns01是否跳过本地DNS传播校验 0否 1是 ApplyDns string `json:"apply_dns"` //申请dns服务商 ApplyEmail string `json:"apply_email"` //申请邮箱 ApplyDomain string `json:"apply_domain"` //申请域名 diff --git a/model/sslorder.go b/model/sslorder.go index 19396e8..e9f942d 100644 --- a/model/sslorder.go +++ b/model/sslorder.go @@ -13,6 +13,7 @@ type SslOrder struct { HostCode string `json:"host_code"` //网站唯一码(主要键) ApplyPlatform string `json:"apply_platform"` //申请平台 ApplyMethod string `json:"apply_method"` //申请方式http01,dns + SkipDNSVerify int64 `json:"skip_dns_verify"` //dns01是否跳过本地DNS传播校验 0否 1是 ApplyDns string `json:"apply_dns"` //申请dns服务商 ApplyEmail string `json:"apply_email"` //申请邮箱 ApplyKey string `json:"apply_key"` //申请私钥 diff --git a/service/waf_service/waf_sslorder.go b/service/waf_service/waf_sslorder.go index c0c4445..131711a 100644 --- a/service/waf_service/waf_sslorder.go +++ b/service/waf_service/waf_sslorder.go @@ -26,6 +26,7 @@ func (receiver *WafSSLOrderService) AddApi(req request.WafSslorderaddReq) (model HostCode: req.HostCode, ApplyPlatform: req.ApplyPlatform, ApplyMethod: req.ApplyMethod, + SkipDNSVerify: req.SkipDNSVerify, ApplyDns: req.ApplyDns, ApplyDomain: req.ApplyDomain, ApplyEmail: req.ApplyEmail, @@ -43,6 +44,7 @@ func (receiver *WafSSLOrderService) ModifyApi(req request.WafSslordereditReq) er "HostCode": req.HostCode, "ApplyPlatform": req.ApplyPlatform, "ApplyMethod": req.ApplyMethod, + "SkipDNSVerify": req.SkipDNSVerify, "ApplyDns": req.ApplyDns, "ApplyDomain": req.ApplyDomain, "ApplyEmail": req.ApplyEmail, @@ -108,6 +110,7 @@ func (receiver *WafSSLOrderService) ModifyById(sslOrder model.SslOrder) error { "HostCode": sslOrder.HostCode, "ApplyPlatform": sslOrder.ApplyPlatform, "ApplyMethod": sslOrder.ApplyMethod, + "SkipDNSVerify": sslOrder.SkipDNSVerify, "ApplyDns": sslOrder.ApplyDns, "ApplyDomain": sslOrder.ApplyDomain, "ApplyEmail": sslOrder.ApplyEmail, @@ -145,6 +148,7 @@ func (receiver *WafSSLOrderService) RenewAdd(orderId string) (model.SslOrder, er HostCode: order.HostCode, ApplyPlatform: order.ApplyPlatform, ApplyMethod: order.ApplyMethod, + SkipDNSVerify: order.SkipDNSVerify, ApplyDns: order.ApplyDns, ApplyDomain: order.ApplyDomain, ApplyEmail: order.ApplyEmail, diff --git a/utils/ssl/ssl.go b/utils/ssl/ssl.go index 60d4710..42e45b4 100644 --- a/utils/ssl/ssl.go +++ b/utils/ssl/ssl.go @@ -1,6 +1,7 @@ package ssl import ( + "SamWaf/common/zlog" "SamWaf/model" "SamWaf/utils" "crypto" @@ -14,10 +15,12 @@ import ( "fmt" "net" "strings" + "time" "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/baiducloud" @@ -91,23 +94,25 @@ func RegistrationSSL(order model.SslOrder, savePath string, caServerAddress stri // because we aren't running as root and can't bind a listener to port 80 and 443 // (used later when we attempt to pass challenges). Keep in mind that you still // need to proxy challenge traffic to port 5002 and 5001. - if order.ApplyMethod == "http01" { + switch order.ApplyMethod { + case "http01": provider, err := webroot.NewHTTPProvider(savePath) if err != nil { return order, err } err = client.Challenge.SetHTTP01Provider(provider) - } else if order.ApplyMethod == "dns01" { + case "dns01": dnsProvider, err := GetDnsProvider(order.ApplyDns) if err != nil { + zlog.Error(fmt.Sprintf("Failed to initialize DNS provider `%s`: %v", order.ApplyDns, err)) return order, err } - err = client.Challenge.SetDNS01Provider(dnsProvider) + err = setDNS01ProviderWithRetry(client, dnsProvider, order.SkipDNSVerify) + if err != nil { + zlog.Error(fmt.Sprintf("Failed to set DNS-01 provider: %v", err)) + } } - if err != nil { - return order, err - } // New users will need to register var reg *registration.Resource if applyPlatform == "zerossl" { @@ -185,7 +190,6 @@ func RegistrationSSL(order model.SslOrder, savePath string, caServerAddress stri } // Each certificate comes back with the cert bytes, the bytes of the client's // private key, and a certificate URL. SAVE THESE TO DISK. - fmt.Printf("%#v\n", certificates) order.ResultPrivateKey = certificates.PrivateKey order.ResultCertificate = certificates.Certificate @@ -268,13 +272,13 @@ func ReNewSSL(order model.SslOrder, savePath string, caServerAddress string, app } else if order.ApplyMethod == "dns01" { dnsProvider, err := GetDnsProvider(order.ApplyDns) if err != nil { + zlog.Error(fmt.Sprintf("Failed to initialize DNS provider `%s`: %v", order.ApplyDns, err)) return order, err } - err = client.Challenge.SetDNS01Provider(dnsProvider) - } - - if err != nil { - return order, err + err = setDNS01ProviderWithRetry(client, dnsProvider, order.SkipDNSVerify) + if err != nil { + zlog.Error(fmt.Sprintf("Failed to set DNS-01 provider: %v", err)) + } } // New users will need to register var reg *registration.Resource @@ -363,7 +367,6 @@ func ReNewSSL(order model.SslOrder, savePath string, caServerAddress string, app // Each certificate comes back with the cert bytes, the bytes of the client's // private key, and a certificate URL. SAVE THESE TO DISK. - fmt.Printf("%#v\n", certificates) order.ResultPrivateKey = certificates.PrivateKey order.ResultCertificate = certificates.Certificate @@ -429,15 +432,48 @@ func GetDnsProvider(dnsName string) (challenge.Provider, error) { case "huaweicloud": return huaweicloud.NewDNSProvider() case "tencentcloud": - return tencentcloud.NewDNSProvider() case "cloudflare": - return cloudflare.NewDNSProvider() case "baiducloud": - return baiducloud.NewDNSProvider() default: return nil, fmt.Errorf("unrecognized DNS provider: %s", dnsName) } } + +func setDNS01ProviderWithRetry(client *lego.Client, dnsProvider challenge.Provider, skipDNSVerify int64) error { + // 默认重试10次,每次间隔30秒 + dnsPrecheckRetry := 10 + dnsPrecheckRetryInterval := 30 * time.Second + + // DNS 传播等待时间:300秒(3分钟) + propagationTimeout := 180 * time.Second + + if skipDNSVerify == 1 { + // 跳过本地DNS传播校验:固定等待后直接通知CA进行验证 + return client.Challenge.SetDNS01Provider(dnsProvider, dns01.PropagationWait(propagationTimeout, true)) + } + + // 默认执行本地DNS传播校验,并增加重试机制 + return client.Challenge.SetDNS01Provider(dnsProvider, + dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { + var lastErr error + for i := 1; i <= dnsPrecheckRetry; i++ { + ok, err := check(fqdn, value) + if ok && err == nil { + return true, nil + } + lastErr = err + if i < dnsPrecheckRetry { + time.Sleep(dnsPrecheckRetryInterval) + } + } + + if lastErr != nil { + return false, lastErr + } + return false, fmt.Errorf("dns propagation precheck failed after %d retries", dnsPrecheckRetry) + }), + ) +} diff --git a/wafdb/migrations_core.go b/wafdb/migrations_core.go index dc31cf4..5ff1db6 100644 --- a/wafdb/migrations_core.go +++ b/wafdb/migrations_core.go @@ -718,6 +718,42 @@ func RunCoreDBMigrations(db *gorm.DB) error { return nil }, }, + // 迁移20: 为 ssl_orders 表添加 skip_dns_verify 字段(DNS校验跳过开关) + { + ID: "202603250001_add_ssl_orders_skip_dns_verify", + Migrate: func(tx *gorm.DB) error { + zlog.Info("迁移 202603250001: 为 ssl_orders 表添加 skip_dns_verify 字段") + + // 检查字段是否已存在 + if tx.Migrator().HasColumn(&model.SslOrder{}, "skip_dns_verify") { + zlog.Info("skip_dns_verify 字段已存在,执行默认值回填") + if err := tx.Exec("UPDATE ssl_orders SET skip_dns_verify = 0 WHERE skip_dns_verify IS NULL").Error; err != nil { + zlog.Warn("回填 skip_dns_verify 默认值失败", "error", err.Error()) + } + return nil + } + + // 添加字段 + if err := tx.Migrator().AddColumn(&model.SslOrder{}, "skip_dns_verify"); err != nil { + return fmt.Errorf("添加 skip_dns_verify 字段失败: %w", err) + } + + // 为历史数据回填默认值 0(不跳过DNS校验) + if err := tx.Exec("UPDATE ssl_orders SET skip_dns_verify = 0 WHERE skip_dns_verify IS NULL").Error; err != nil { + zlog.Warn("设置 skip_dns_verify 默认值失败", "error", err.Error()) + } + + zlog.Info("skip_dns_verify 字段添加成功") + return nil + }, + Rollback: func(tx *gorm.DB) error { + zlog.Info("回滚 202603250001: 删除 ssl_orders 表的 skip_dns_verify 字段") + if tx.Migrator().HasColumn(&model.SslOrder{}, "skip_dns_verify") { + return tx.Migrator().DropColumn(&model.SslOrder{}, "skip_dns_verify") + } + return nil + }, + }, }) // 执行迁移 diff --git a/wafenginecore/sslorder.go b/wafenginecore/sslorder.go index 951f812..73d033f 100644 --- a/wafenginecore/sslorder.go +++ b/wafenginecore/sslorder.go @@ -31,6 +31,7 @@ func (waf *WafEngine) ApplySSLOrder(chanType int, bean model.SslOrder) { //加载环境变量 privateGroupName := bean.PrivateGroupName privateGroupBelongCloud := bean.ApplyDns + zlog.Debug(fmt.Sprintf("Loading DNS provider env info - Group: `%s`, Cloud: `%s` for domain: %s", privateGroupName, privateGroupBelongCloud, bean.ApplyDomain)) //查询环境变量信息 wafssl.LoadDnsProviderEnvInfo(privateGroupName, privateGroupBelongCloud) } diff --git a/wafssl/wafssl.go b/wafssl/wafssl.go index 78e8f1b..1adad86 100644 --- a/wafssl/wafssl.go +++ b/wafssl/wafssl.go @@ -11,19 +11,46 @@ import ( // LoadDnsProviderEnvInfo 初始化dns所需要的参数信息 func LoadDnsProviderEnvInfo(groupName string, belongCloud string) { - privateInfos, _, err := waf_service.WafPrivateInfoServiceApp.GetListByGroupAndBelongCloudPureApi(groupName, belongCloud) - if err == nil { - for _, info := range privateInfos { - err := os.Setenv(info.PrivateKey, info.PrivateValue) - if err != nil { - return - } else { - zlog.Info(fmt.Sprintf("Cloud `%s` Group `%s` ENV `%s` LOADED", belongCloud, groupName, info.PrivateKey)) - } - } + // 清除旧的 DNS provider 相关环境变量,防止污染 + dnsEnvKeys := []string{ + // Tencent Cloud + "TENCENTCLOUD_SECRET_ID", "TENCENTCLOUD_SECRET_KEY", "TENCENTCLOUD_REGION", + // Alibaba Cloud + "ALICLOUD_ACCESS_KEY", "ALICLOUD_SECRET_KEY", "ALICLOUD_REGION_ID", + // Huawei Cloud + "HUAWEICLOUD_DOMAIN_NAME", "HUAWEICLOUD_ACCESS_KEY_ID", "HUAWEICLOUD_ACCESS_KEY_SECRET", "HUAWEICLOUD_REGION", + // Cloudflare + "CLOUDFLARE_API_TOKEN", "CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID", + // Baidu Cloud + "BAIDUCLOUD_ACCESS_KEY", "BAIDUCLOUD_SECRET_ACCESS_KEY", "BAIDUCLOUD_REGION", } + for _, key := range dnsEnvKeys { + os.Unsetenv(key) + } + + privateInfos, _, err := waf_service.WafPrivateInfoServiceApp.GetListByGroupAndBelongCloudPureApi(groupName, belongCloud) + if err != nil { + zlog.Error(fmt.Sprintf("Failed to load DNS provider env info for cloud `%s` group `%s`: %v", belongCloud, groupName, err)) + return + } + if len(privateInfos) == 0 { + zlog.Warn(fmt.Sprintf("No DNS credentials found in DB for cloud `%s` group `%s` - DNS provider will have no auth", belongCloud, groupName)) + return + } + + for _, info := range privateInfos { + err := os.Setenv(info.PrivateKey, info.PrivateValue) + if err != nil { + zlog.Error(fmt.Sprintf("Failed to set env var `%s`: %v", info.PrivateKey, err)) + return + } + zlog.Info(fmt.Sprintf("Cloud `%s` Group `%s` ENV `%s` LOADED", belongCloud, groupName, info.PrivateKey)) + } + + zlog.Info(fmt.Sprintf("Successfully loaded %d credentials for cloud `%s` group `%s`", len(privateInfos), belongCloud, groupName)) + if global.GWAF_RELEASE == "false" { - fmt.Println("当前环境变量:") + zlog.Debug("Current DNS-related environment variables:") for _, env := range os.Environ() { parts := strings.SplitN(env, "=", 2) key := parts[0] @@ -31,7 +58,7 @@ func LoadDnsProviderEnvInfo(groupName string, belongCloud string) { if len(parts) > 1 { value = parts[1] } - fmt.Printf("%s = %s\n", key, value) + zlog.Debug(fmt.Sprintf("%s = %s", key, value)) } } }