diff --git a/internal/certdeploy/deployers/sp_tencentcloud_cdn.go b/internal/certdeploy/deployers/sp_tencentcloud_cdn.go index 343d78c06..6033c06d3 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_cdn.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_cdn.go @@ -17,10 +17,11 @@ func init() { } provider, err := tencentcloudcdn.NewSSLDeployerProvider(&tencentcloudcdn.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + MatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "matchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }); err != nil { diff --git a/internal/certdeploy/deployers/sp_tencentcloud_ecdn.go b/internal/certdeploy/deployers/sp_tencentcloud_ecdn.go index 8993578ec..5961e1362 100644 --- a/internal/certdeploy/deployers/sp_tencentcloud_ecdn.go +++ b/internal/certdeploy/deployers/sp_tencentcloud_ecdn.go @@ -17,10 +17,11 @@ func init() { } provider, err := tencentcloudecdn.NewSSLDeployerProvider(&tencentcloudecdn.SSLDeployerProviderConfig{ - SecretId: credentials.SecretId, - SecretKey: credentials.SecretKey, - Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + SecretId: credentials.SecretId, + SecretKey: credentials.SecretKey, + Endpoint: xmaps.GetString(options.ProviderExtendedConfig, "endpoint"), + MatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "matchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }); err != nil { diff --git a/internal/certdeploy/deployers/sp_volcengine_cdn.go b/internal/certdeploy/deployers/sp_volcengine_cdn.go index 8553338f2..b0bbaca49 100644 --- a/internal/certdeploy/deployers/sp_volcengine_cdn.go +++ b/internal/certdeploy/deployers/sp_volcengine_cdn.go @@ -19,6 +19,7 @@ func init() { provider, err := volcenginecdn.NewSSLDeployerProvider(&volcenginecdn.SSLDeployerProviderConfig{ AccessKeyId: credentials.AccessKeyId, AccessKeySecret: credentials.SecretAccessKey, + MatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "matchPattern"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_volcengine_live.go b/internal/certdeploy/deployers/sp_volcengine_live.go index 95bfdbd61..d09787e7f 100644 --- a/internal/certdeploy/deployers/sp_volcengine_live.go +++ b/internal/certdeploy/deployers/sp_volcengine_live.go @@ -19,6 +19,7 @@ func init() { provider, err := volcenginelive.NewSSLDeployerProvider(&volcenginelive.SSLDeployerProviderConfig{ AccessKeyId: credentials.AccessKeyId, AccessKeySecret: credentials.SecretAccessKey, + MatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "matchPattern"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/workflow/engine/executor_bizmonitor.go b/internal/workflow/engine/executor_bizmonitor.go index b3304e712..1d8f40515 100644 --- a/internal/workflow/engine/executor_bizmonitor.go +++ b/internal/workflow/engine/executor_bizmonitor.go @@ -85,10 +85,7 @@ func (ne *bizMonitorNodeExecutor) Execute(execCtx *NodeExecutionContext) (*NodeE now := time.Now() isCertPeriodValid := now.Before(cert.NotAfter) && now.After(cert.NotBefore) - isCertHostMatched := true - if err := cert.VerifyHostname(targetDomain); err != nil { - isCertHostMatched = false - } + isCertHostMatched := cert.VerifyHostname(targetDomain) == nil validated := isCertPeriodValid && isCertHostMatched daysLeft := int(math.Floor(time.Until(cert.NotAfter).Hours() / 24)) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/consts.go new file mode 100644 index 000000000..50ef02ff8 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/consts.go @@ -0,0 +1,10 @@ +package tencentcloudcdn + +const ( + // 匹配模式:精确匹配。 + MatchPatternExact = "exact" + // 匹配模式:通配符匹配。 + MatchPatternWildcard = "wildcard" + // 匹配模式:证书 SAN 匹配。 + MatchPatternCertSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go index 9af49f98a..c04c5d3aa 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -24,6 +25,9 @@ type SSLDeployerProviderConfig struct { SecretKey string `json:"secretKey"` // 腾讯云接口端点。 Endpoint string `json:"endpoint,omitempty"` + // 域名匹配模式。 + // 零值时默认值 [MatchPatternExact]。 + MatchPattern string `json:"matchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -95,17 +99,39 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } // 获取待部署的 CDN 实例 - // 如果是泛域名,根据证书匹配 CDN 实例 domains := make([]string, 0) - if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getDomainsByCertId(ctx, upres.CertId) - if err != nil { - return nil, err + switch d.config.MatchPattern { + case "", MatchPatternExact: + { + domains = append(domains, d.config.Domain) } - domains = temp - } else { - domains = append(domains, d.config.Domain) + case MatchPatternWildcard: + { + if strings.HasPrefix(d.config.Domain, "*.") { + temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err + } + + domains = temp + } else { + domains = append(domains, d.config.Domain) + } + } + + case MatchPatternCertSAN: + { + temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + if err != nil { + return nil, err + } + + domains = temp + } + + default: + return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.MatchPattern) } // 遍历更新域名证书 @@ -134,7 +160,55 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } -func (d *SSLDeployerProvider) getDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) { +func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { + domains := make([]string, 0) + + // 遍历查询域名基本信息,获取匹配的域名 + // REF: https://cloud.tencent.com/document/api/228/41118 + describeDomainsOffset := int64(0) + describeDomainsLimit := int64(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeDomainsReq := tccdn.NewDescribeDomainsRequest() + describeDomainsReq.Filters = []*tccdn.DomainFilter{ + { + Name: common.StringPtr("domain"), + Value: common.StringPtrs([]string{strings.TrimPrefix(wildcardDomain, "*.")}), + Fuzzy: common.BoolPtr(true), + }, + } + describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) + describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsResp, err := d.sdkClient.DescribeDomains(describeDomainsReq) + d.logger.Debug("sdk request 'cdn.DescribeDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomains': %w", err) + } + + if describeDomainsResp.Response.Domains != nil { + for _, domain := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domain.Product) == "cdn" && xcert.MatchHostname(wildcardDomain, lo.FromPtr(domain.Domain)) { + domains = append(domains, *domain.Domain) + } + } + } + + if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + break + } else { + describeDomainsOffset += describeDomainsLimit + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) { // 获取证书中的可用域名 // REF: https://cloud.tencent.com/document/api/228/42491 describeCertDomainsReq := tccdn.NewDescribeCertDomainsRequest() @@ -177,7 +251,10 @@ func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, d } domainConfig := describeDomainsConfigResp.Response.Domains[0] - if domainConfig.Https != nil && domainConfig.Https.CertInfo != nil && domainConfig.Https.CertInfo.CertId != nil && *domainConfig.Https.CertInfo.CertId == cloudCertId { + if domainConfig.Https != nil && + domainConfig.Https.CertInfo != nil && + domainConfig.Https.CertInfo.CertId != nil && + *domainConfig.Https.CertInfo.CertId == cloudCertId { // 已部署过此域名,跳过 return nil } @@ -188,9 +265,7 @@ func (d *SSLDeployerProvider) updateDomainHttpsServerCert(ctx context.Context, d updateDomainConfigReq.Domain = common.StringPtr(domain) updateDomainConfigReq.Https = domainConfig.Https if updateDomainConfigReq.Https == nil { - updateDomainConfigReq.Https = &tccdn.Https{ - Switch: common.StringPtr("on"), - } + updateDomainConfigReq.Https = &tccdn.Https{Switch: common.StringPtr("on")} } else { updateDomainConfigReq.Https.SslStatus = nil } diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/consts.go b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/consts.go new file mode 100644 index 000000000..0fc19885f --- /dev/null +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/consts.go @@ -0,0 +1,10 @@ +package tencentcloudecdn + +const ( + // 匹配模式:精确匹配。 + MatchPatternExact = "exact" + // 匹配模式:通配符匹配。 + MatchPatternWildcard = "wildcard" + // 匹配模式:证书 SAN 匹配。 + MatchPatternCertSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go index 29b0ce0e2..11e51ab42 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -15,6 +15,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/tencentcloud-ssl" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -24,6 +25,9 @@ type SSLDeployerProviderConfig struct { SecretKey string `json:"secretKey"` // 腾讯云接口端点。 Endpoint string `json:"endpoint,omitempty"` + // 域名匹配模式。 + // 零值时默认值 [MatchPatternExact]。 + MatchPattern string `json:"matchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -94,18 +98,40 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } - // 获取待部署的 CDN 实例 - // 如果是泛域名,根据证书匹配 CDN 实例 + // 获取待部署的 ECDN 实例 domains := make([]string, 0) - if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getDomainsByCertId(upres.CertId) - if err != nil { - return nil, err + switch d.config.MatchPattern { + case "", MatchPatternExact: + { + domains = append(domains, d.config.Domain) } - domains = temp - } else { - domains = append(domains, d.config.Domain) + case MatchPatternWildcard: + { + if strings.HasPrefix(d.config.Domain, "*.") { + temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err + } + + domains = temp + } else { + domains = append(domains, d.config.Domain) + } + } + + case MatchPatternCertSAN: + { + temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + if err != nil { + return nil, err + } + + domains = temp + } + + default: + return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.MatchPattern) } // 遍历更新域名证书 @@ -134,7 +160,55 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } -func (d *SSLDeployerProvider) getDomainsByCertId(cloudCertId string) ([]string, error) { +func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { + domains := make([]string, 0) + + // 遍历查询域名基本信息,获取匹配的域名 + // REF: https://cloud.tencent.com/document/api/228/41118 + describeDomainsOffset := int64(0) + describeDomainsLimit := int64(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + describeDomainsReq := tccdn.NewDescribeDomainsRequest() + describeDomainsReq.Filters = []*tccdn.DomainFilter{ + { + Name: common.StringPtr("domain"), + Value: common.StringPtrs([]string{strings.TrimPrefix(wildcardDomain, "*.")}), + Fuzzy: common.BoolPtr(true), + }, + } + describeDomainsReq.Offset = common.Int64Ptr(describeDomainsOffset) + describeDomainsReq.Limit = common.Int64Ptr(describeDomainsLimit) + describeDomainsResp, err := d.sdkClient.DescribeDomains(describeDomainsReq) + d.logger.Debug("sdk request 'cdn.DescribeDomains'", slog.Any("request", describeDomainsReq), slog.Any("response", describeDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeDomains': %w", err) + } + + if describeDomainsResp.Response.Domains != nil { + for _, domain := range describeDomainsResp.Response.Domains { + if lo.FromPtr(domain.Product) == "ecdn" && xcert.MatchHostname(wildcardDomain, lo.FromPtr(domain.Domain)) { + domains = append(domains, *domain.Domain) + } + } + } + + if len(describeDomainsResp.Response.Domains) < int(describeDomainsLimit) { + break + } else { + describeDomainsOffset += describeDomainsLimit + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) { // 获取证书中的可用域名 // REF: https://cloud.tencent.com/document/api/228/42491 describeCertDomainsReq := tccdn.NewDescribeCertDomainsRequest() diff --git a/pkg/core/ssl-deployer/providers/volcengine-cdn/consts.go b/pkg/core/ssl-deployer/providers/volcengine-cdn/consts.go new file mode 100644 index 000000000..dce96568e --- /dev/null +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/consts.go @@ -0,0 +1,10 @@ +package volcenginecdn + +const ( + // 匹配模式:精确匹配。 + MatchPatternExact = "exact" + // 匹配模式:通配符匹配。 + MatchPatternWildcard = "wildcard" + // 匹配模式:证书 SAN 匹配。 + MatchPatternCertSAN = "certsan" +) diff --git a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go index 8bb1eb647..7f4ce29ca 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -8,9 +8,11 @@ import ( "strings" vecdn "github.com/volcengine/volc-sdk-golang/service/cdn" + ve "github.com/volcengine/volcengine-go-sdk/volcengine" "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/volcengine-cdn" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -18,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 火山引擎 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [MatchPatternExact]。 + MatchPattern string `json:"matchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -79,44 +84,47 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的 CDN 实例 domains := make([]string, 0) - if strings.HasPrefix(d.config.Domain, "*.") { - // 获取指定证书可关联的域名 - // REF: https://www.volcengine.com/docs/6454/125711 - describeCertConfigReq := &vecdn.DescribeCertConfigRequest{ - CertId: upres.CertId, - } - describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) - d.logger.Debug("sdk request 'cdn.DescribeCertConfig'", slog.Any("request", describeCertConfigReq), slog.Any("response", describeCertConfigResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeCertConfig': %w", err) + switch d.config.MatchPattern { + case "", MatchPatternExact: + { + domains = append(domains, d.config.Domain) } - if describeCertConfigResp.Result.CertNotConfig != nil { - for i := range describeCertConfigResp.Result.CertNotConfig { - domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) - } - } + case MatchPatternWildcard: + { + if strings.HasPrefix(d.config.Domain, "*.") { + temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err + } - if describeCertConfigResp.Result.OtherCertConfig != nil { - for i := range describeCertConfigResp.Result.OtherCertConfig { - domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) - } - } - - if len(domains) == 0 { - if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { - // 所有可关联的域名都配置了该证书,跳过部署 - d.logger.Info("no domains to deploy") + domains = temp } else { - return nil, errors.New("domain not found") + domains = append(domains, d.config.Domain) } } - } else { - domains = append(domains, d.config.Domain) + + case MatchPatternCertSAN: + { + temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + if err != nil { + return nil, err + } + + domains = temp + } + + default: + return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.MatchPattern) } - if len(domains) > 0 { + // 遍历绑定证书 + if len(domains) == 0 { + d.logger.Info("no cdn domains to deploy") + } else { + d.logger.Info("found cdn domains to deploy", slog.Any("domains", domains)) var errs []error for _, domain := range domains { @@ -124,15 +132,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - // 关联证书与加速域名 - // REF: https://www.volcengine.com/docs/6454/125712 - batchDeployCertReq := &vecdn.BatchDeployCertRequest{ - CertId: upres.CertId, - Domain: domain, - } - batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) - d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) - if err != nil { + if err := d.bindCert(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -145,3 +145,98 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { + domains := make([]string, 0) + + // 遍历获取加速域名列表,获取匹配的域名 + // REF: https://www.volcengine.com/docs/6454/75269 + listCdnDomainsPageNum := int64(1) + listCdnDomainsPageSize := int64(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCdnDomainsReq := &vecdn.ListCdnDomainsRequest{ + Domain: ve.String(strings.TrimPrefix(wildcardDomain, "*.")), + Status: ve.String("online"), + PageNum: ve.Int64(listCdnDomainsPageNum), + PageSize: ve.Int64(listCdnDomainsPageSize), + } + listCdnDomainsResp, err := d.sdkClient.ListCdnDomains(listCdnDomainsReq) + d.logger.Debug("sdk request 'cdn.ListCdnDomains'", slog.Any("request", listCdnDomainsReq), slog.Any("response", listCdnDomainsResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListCdnDomains': %w", err) + } + + if listCdnDomainsResp.Result.Data != nil { + for _, domain := range listCdnDomainsResp.Result.Data { + if xcert.MatchHostname(wildcardDomain, domain.Domain) { + domains = append(domains, domain.Domain) + } + } + } + + if len(listCdnDomainsResp.Result.Data) < int(listCdnDomainsPageSize) { + break + } else { + listCdnDomainsPageSize++ + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) getMatchedDomainsByCertId(ctx context.Context, cloudCertId string) ([]string, error) { + domains := make([]string, 0) + + // 获取指定证书可关联的域名 + // REF: https://www.volcengine.com/docs/6454/125711 + describeCertConfigReq := &vecdn.DescribeCertConfigRequest{ + CertId: cloudCertId, + } + describeCertConfigResp, err := d.sdkClient.DescribeCertConfig(describeCertConfigReq) + d.logger.Debug("sdk request 'cdn.DescribeCertConfig'", slog.Any("request", describeCertConfigReq), slog.Any("response", describeCertConfigResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.DescribeCertConfig': %w", err) + } + + if describeCertConfigResp.Result.CertNotConfig != nil { + for i := range describeCertConfigResp.Result.CertNotConfig { + domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + } + } + + if describeCertConfigResp.Result.OtherCertConfig != nil { + for i := range describeCertConfigResp.Result.OtherCertConfig { + domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + } + } + + if len(domains) == 0 { + if len(describeCertConfigResp.Result.SpecifiedCertConfig) == 0 { + return nil, errors.New("domains not found") + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { + // 关联证书与加速域名 + // REF: https://www.volcengine.com/docs/6454/125712 + batchDeployCertReq := &vecdn.BatchDeployCertRequest{ + CertId: cloudCertId, + Domain: domain, + } + batchDeployCertResp, err := d.sdkClient.BatchDeployCert(batchDeployCertReq) + d.logger.Debug("sdk request 'cdn.BatchDeployCert'", slog.Any("request", batchDeployCertReq), slog.Any("response", batchDeployCertResp)) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/consts.go b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go new file mode 100644 index 000000000..825027a51 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/volcengine-live/consts.go @@ -0,0 +1,8 @@ +package volcenginelive + +const ( + // 匹配模式:精确匹配。 + MatchPatternExact = "exact" + // 匹配模式:通配符匹配。 + MatchPatternWildcard = "wildcard" +) diff --git a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go index 1737b1fe8..47fdecc95 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go +++ b/pkg/core/ssl-deployer/providers/volcengine-live/volcengine_live.go @@ -12,6 +12,7 @@ import ( "github.com/certimate-go/certimate/pkg/core" sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/volcengine-live" + xcert "github.com/certimate-go/certimate/pkg/utils/cert" ) type SSLDeployerProviderConfig struct { @@ -19,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 火山引擎 AccessKeySecret。 AccessKeySecret string `json:"accessKeySecret"` + // 域名匹配模式。 + // 零值时默认值 [MatchPatternExact]。 + MatchPattern string `json:"matchPattern,omitempty"` // 直播流域名(支持泛域名)。 Domain string `json:"domain"` } @@ -80,51 +84,37 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl certificate uploaded", slog.Any("result", upres)) } + // 获取待部署的直播实例 domains := make([]string, 0) - if strings.HasPrefix(d.config.Domain, "*.") { - listDomainDetailPageNum := int32(1) - listDomainDetailPageSize := int32(1000) - listDomainDetailTotal := 0 - for { - // 查询域名列表 - // REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8 - listDomainDetailReq := &velive.ListDomainDetailBody{ - PageNum: listDomainDetailPageNum, - PageSize: listDomainDetailPageSize, - } - listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq) - d.logger.Debug("sdk request 'live.ListDomainDetail'", slog.Any("request", listDomainDetailReq), slog.Any("response", listDomainDetailResp)) - if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'live.ListDomainDetail': %w", err) - } + switch d.config.MatchPattern { + case "", MatchPatternExact: + { + domains = append(domains, d.config.Domain) + } - if listDomainDetailResp.Result.DomainList != nil { - for _, item := range listDomainDetailResp.Result.DomainList { - // 仅匹配泛域名的下一级子域名 - wildcardDomain := strings.TrimPrefix(d.config.Domain, "*") - if strings.HasSuffix(item.Domain, wildcardDomain) && !strings.Contains(strings.TrimSuffix(item.Domain, wildcardDomain), ".") { - domains = append(domains, item.Domain) - } + case MatchPatternWildcard: + { + if strings.HasPrefix(d.config.Domain, "*.") { + temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err } - } - listDomainDetailLen := len(listDomainDetailResp.Result.DomainList) - if listDomainDetailLen < int(listDomainDetailPageSize) || int(listDomainDetailResp.Result.Total) <= listDomainDetailTotal+listDomainDetailLen { - break + domains = temp } else { - listDomainDetailPageNum++ - listDomainDetailTotal += listDomainDetailLen + domains = append(domains, d.config.Domain) } } - if len(domains) == 0 { - return nil, errors.New("domain not found") - } - } else { - domains = append(domains, d.config.Domain) + default: + return nil, fmt.Errorf("unsupported match pattern: '%s'", d.config.MatchPattern) } - if len(domains) > 0 { + // 遍历绑定证书 + if len(domains) == 0 { + d.logger.Info("no live domains to deploy") + } else { + d.logger.Info("found live domains to deploy", slog.Any("domains", domains)) var errs []error for _, domain := range domains { @@ -132,16 +122,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke case <-ctx.Done(): return nil, ctx.Err() default: - // 绑定证书 - // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 - bindCertReq := &velive.BindCertBody{ - ChainID: upres.CertId, - Domain: domain, - HTTPS: ve.Bool(true), - } - bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) - d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) - if err != nil { + if err := d.bindCert(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -154,3 +135,67 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return &core.SSLDeployResult{}, nil } + +func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, wildcardDomain string) ([]string, error) { + domains := make([]string, 0) + + // 遍历查询域名列表,获取匹配的域名 + // REF: https://www.volcengine.com/docs/6469/1186277#%E6%9F%A5%E8%AF%A2%E5%9F%9F%E5%90%8D%E5%88%97%E8%A1%A8 + listDomainDetailPageNum := int32(1) + listDomainDetailPageSize := int32(1000) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainDetailReq := &velive.ListDomainDetailBody{ + DomainStatusList: ve.Int32Slice([]int32{0}), + PageNum: listDomainDetailPageNum, + PageSize: listDomainDetailPageSize, + } + listDomainDetailResp, err := d.sdkClient.ListDomainDetail(ctx, listDomainDetailReq) + d.logger.Debug("sdk request 'live.ListDomainDetail'", slog.Any("request", listDomainDetailReq), slog.Any("response", listDomainDetailResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'live.ListDomainDetail': %w", err) + } + + if listDomainDetailResp.Result.DomainList != nil { + for _, domain := range listDomainDetailResp.Result.DomainList { + if xcert.MatchHostname(wildcardDomain, domain.Domain) { + domains = append(domains, domain.Domain) + } + } + } + + if len(listDomainDetailResp.Result.DomainList) < int(listDomainDetailPageSize) { + break + } else { + listDomainDetailPageNum++ + } + } + + if len(domains) == 0 { + return nil, errors.New("domain not found") + } + + return domains, nil +} + +func (d *SSLDeployerProvider) bindCert(ctx context.Context, domain string, cloudCertId string) error { + // 绑定证书 + // REF: https://www.volcengine.com/docs/6469/1186278#%E7%BB%91%E5%AE%9A%E8%AF%81%E4%B9%A6 + bindCertReq := &velive.BindCertBody{ + ChainID: cloudCertId, + Domain: domain, + HTTPS: ve.Bool(true), + } + bindCertResp, err := d.sdkClient.BindCert(ctx, bindCertReq) + d.logger.Debug("sdk request 'live.BindCert'", slog.Any("request", bindCertReq), slog.Any("response", bindCertResp)) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/utils/cert/hostname.go b/pkg/utils/cert/hostname.go new file mode 100644 index 000000000..af4ae5d9a --- /dev/null +++ b/pkg/utils/cert/hostname.go @@ -0,0 +1,33 @@ +package cert + +import ( + "crypto/x509" + "net" + "strings" +) + +// 检查目标主机名是否匹配待匹配主机名。 +// +// 入参: +// - match: 待匹配主机名。可以是泛域名,如 "*.example.com"。 +// - candidate: 目标主机名。如 "sub.example.com"。 +// +// 出参: +// - 是否匹配。 +func MatchHostname(match, candidate string) bool { + if match == "" || candidate == "" { + return false + } + + if !strings.Contains(match, "*") { + return strings.EqualFold(match, candidate) + } + + mockCert := &x509.Certificate{} + if ip := net.ParseIP(candidate); ip != nil { + mockCert.IPAddresses = []net.IP{ip} + } else { + mockCert.DNSNames = []string{match} + } + return mockCert.VerifyHostname(candidate) == nil +} diff --git a/pkg/utils/cert/hostname_test.go b/pkg/utils/cert/hostname_test.go new file mode 100644 index 000000000..85a2095c8 --- /dev/null +++ b/pkg/utils/cert/hostname_test.go @@ -0,0 +1,50 @@ +package cert_test + +import ( + "testing" + + xcert "github.com/certimate-go/certimate/pkg/utils/cert" +) + +func TestCertUtil_Hostname(t *testing.T) { + t.Run("MatchHostname", func(t *testing.T) { + testCases := []struct { + wildcard string + target string + expected bool + }{ + {"*.example.com", "sub.example.com", true}, + {"*.example.com", "sub.sub.example.com", false}, + {"*.example.com", "example.com", false}, + + {"*.*.example.com", "a.b.example.com", false}, + {"*.*.example.com", "a.example.com", false}, + {"*.*.example.com", "a.b.c.example.com", false}, + + {"example.com", "example.com", true}, + {"example.com", "wrong.com", false}, + + {"", "example.com", false}, + {"*.example.com", "", false}, + + {"*.sub.example.com", "a.sub.example.com", true}, + {"*.sub.example.com", "a.b.sub.example.com", false}, + {"*.sub.example.com", "sub.example.com", false}, + + {"*.Example.COM", "sub.example.com", true}, + {"*.EXAMPLE.COM", "SUB.EXAMPLE.COM", true}, + } + + for _, tc := range testCases { + result := xcert.MatchHostname(tc.wildcard, tc.target) + status := "✓" + pf := t.Logf + if result != tc.expected { + status = "✗" + pf = t.Errorf + } + + pf("%s Wildcard: %-20s Target: %-20s Expected: %-5v Got: %-5v\n", status, tc.wildcard, tc.target, tc.expected, result) + } + }) +} diff --git a/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx b/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx index 6441e14e3..725da3dd0 100644 --- a/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx +++ b/ui/src/components/access/forms/AccessConfigFieldsProvider1Panel.tsx @@ -49,12 +49,12 @@ const AccessConfigFormFieldsProvider1Panel = () => {