diff --git a/internal/certdeploy/deployers/sp_baiducloud_cdn.go b/internal/certdeploy/deployers/sp_baiducloud_cdn.go index a32670a5..aa7d02c7 100644 --- a/internal/certdeploy/deployers/sp_baiducloud_cdn.go +++ b/internal/certdeploy/deployers/sp_baiducloud_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := baiducloudcdn.NewSSLDeployerProvider(&baiducloudcdn.SSLDeployerProviderConfig{ - AccessKeyId: credentials.AccessKeyId, - SecretAccessKey: credentials.SecretAccessKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKeyId: credentials.AccessKeyId, + SecretAccessKey: credentials.SecretAccessKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go index b822db0a..af88bf1d 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -113,16 +113,20 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domainCandidates, err := d.getAllDomains(ctx) - if err != nil { - return nil, err - } + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } - domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { - return xcerthostname.IsMatch(d.config.Domain, domain) - }) - if len(domains) == 0 { - return nil, errors.New("no domains matched by wildcard") + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("no domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} } } diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go index c8e181b8..362a18df 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn.go @@ -5,11 +5,16 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" bcecdn "github.com/baidubce/bce-sdk-go/services/cdn" bcecdnapi "github.com/baidubce/bce-sdk-go/services/cdn/api" "github.com/certimate-go/certimate/pkg/core" + "github.com/samber/lo" + + xcert "github.com/certimate-go/certimate/pkg/utils/cert" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -17,6 +22,9 @@ type SSLDeployerProviderConfig struct { AccessKeyId string `json:"accessKeyId"` // 百度智能云 SecretAccessKey。 SecretAccessKey string `json:"secretAccessKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -55,14 +63,131 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - if d.config.Domain == "" { - return nil, errors.New("config `domain` is required") + // 获取待部署的域名列表 + var domains []string + switch d.config.DomainMatchPattern { + case "", DOMAIN_MATCH_PATTERN_EXACT: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + domains = []string{d.config.Domain} + } + + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return xcerthostname.IsMatch(d.config.Domain, domain) + }) + if len(domains) == 0 { + return nil, errors.New("no domains matched by wildcard") + } + } else { + domains = []string{d.config.Domain} + } + } + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + certX509, err := xcert.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + domainCandidates, err := d.getAllDomains(ctx) + if err != nil { + return nil, err + } + + domains = lo.Filter(domainCandidates, func(domain string, _ int) bool { + return certX509.VerifyHostname(domain) == nil + }) + if len(domains) == 0 { + return nil, errors.New("no domains matched by certificate") + } + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } + // 遍历更新域名证书 + 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 { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + if err := d.updateDomainCertificate(ctx, domain, certPEM, privkeyPEM); err != nil { + errs = append(errs, err) + } + } + } + + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + } + + return &core.SSLDeployResult{}, nil +} + +func (d *SSLDeployerProvider) getAllDomains(ctx context.Context) ([]string, error) { + domains := make([]string, 0) + + // 遍历查询域名列表 + // REF: https://cloud.baidu.com/doc/CDN/s/sjwvyewt1 + listDomainsMarker := "" + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listDomainsRespDomains, listDomainsNextMarker, err := d.sdkClient.ListDomains(listDomainsMarker) + d.logger.Debug("sdk request 'cdn.ListDomains'", slog.String("request.marker", listDomainsMarker), slog.Any("response.domains", listDomainsRespDomains)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'cdn.ListDomains': %w", err) + } + + domains = append(domains, listDomainsRespDomains...) + + if listDomainsNextMarker == "" { + break + } else { + listDomainsMarker = listDomainsNextMarker + } + } + + if len(domains) == 0 { + return nil, errors.New("no domains matched by wildcard") + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, certPEM, privkeyPEM string) error { // 修改域名证书 // REF: https://cloud.baidu.com/doc/CDN/s/qjzuz2hp8 putCertResp, err := d.sdkClient.PutCert( - d.config.Domain, + domain, &bcecdnapi.UserCertificate{ CertName: fmt.Sprintf("certimate-%d", time.Now().UnixMilli()), ServerData: certPEM, @@ -70,12 +195,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke }, "ON", ) - d.logger.Debug("sdk request 'cdn.PutCert'", slog.String("request.domain", d.config.Domain), slog.Any("response", putCertResp)) + d.logger.Debug("sdk request 'cdn.PutCert'", slog.String("request.domain", domain), slog.Any("response", putCertResp)) if err != nil { - return nil, fmt.Errorf("failed to execute sdk request 'cdn.PutCert': %w", err) + return fmt.Errorf("failed to execute sdk request 'cdn.PutCert': %w", err) } - return &core.SSLDeployResult{}, nil + return nil } func createSDKClient(accessKeyId, secretAccessKey string) (*bcecdn.Client, error) { diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go index 068e6fad..15ca0ec7 100644 --- a/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/baiducloud_cdn_test.go @@ -53,9 +53,10 @@ func TestDeploy(t *testing.T) { }, "\n")) deployer, err := provider.NewSSLDeployerProvider(&provider.SSLDeployerProviderConfig{ - AccessKeyId: fAccessKeyId, - SecretAccessKey: fSecretAccessKey, - Domain: fDomain, + AccessKeyId: fAccessKeyId, + SecretAccessKey: fSecretAccessKey, + DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT, + Domain: fDomain, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go b/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go new file mode 100644 index 00000000..cee9f270 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/baiducloud-cdn/consts.go @@ -0,0 +1,10 @@ +package baiducloudcdn + +const ( + // 匹配模式:精确匹配。 + DOMAIN_MATCH_PATTERN_EXACT = "exact" + // 匹配模式:通配符匹配。 + DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" + // 匹配模式:证书 SAN 匹配。 + DOMAIN_MATCH_PATTERN_CERTSAN = "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 059c2eaf..3e6fee8e 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-cdn/tencentcloud_cdn.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } case DOMAIN_MATCH_PATTERN_WILDCARD: @@ -108,25 +108,25 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } else { - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } } case DOMAIN_MATCH_PATTERN_CERTSAN: { - temp, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } default: 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 85a8ba9c..8e53534b 100644 --- a/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go +++ b/pkg/core/ssl-deployer/providers/tencentcloud-ecdn/tencentcloud_ecdn.go @@ -98,7 +98,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } case DOMAIN_MATCH_PATTERN_WILDCARD: @@ -108,14 +108,14 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke } if strings.HasPrefix(d.config.Domain, "*.") { - temp, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) if err != nil { return nil, err } - domains = temp + domains = domainCandidates } else { - domains = append(domains, d.config.Domain) + domains = []string{d.config.Domain} } } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx index dddbcd67..97886434 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBaiduCloudCDN.tsx @@ -1,12 +1,17 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input } from "antd"; +import { Form, Input, Radio } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import Show from "@/components/Show"; import { validDomainName } from "@/utils/validators"; import { useFormNestedFieldsContext } from "./_context"; +const DOMAIN_MATCH_PATTERN_EXACT = "exact" as const; +const DOMAIN_MATCH_PATTERN_WILDCARD = "wildcard" as const; +const DOMAIN_MATCH_PATTERN_CERTSAN = "certsan" as const; + const BizDeployNodeConfigFieldsProviderBaiduCloudCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderBaiduCloudCDN = () => { [parentNamePath]: getSchema({ i18n }), }); const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); const initialValues = getInitialValues(); + const fieldDomainMatchPattern = Form.useWatch([parentNamePath, "domainMatchPattern"], { form: formInst, preserve: true }); + return ( <>