diff --git a/internal/certdeploy/deployers/sp_byteplus_cdn.go b/internal/certdeploy/deployers/sp_byteplus_cdn.go index d3c814e2..13d491a6 100644 --- a/internal/certdeploy/deployers/sp_byteplus_cdn.go +++ b/internal/certdeploy/deployers/sp_byteplus_cdn.go @@ -17,9 +17,10 @@ func init() { } provider, err := bytepluscdn.NewSSLDeployerProvider(&bytepluscdn.SSLDeployerProviderConfig{ - AccessKey: credentials.AccessKey, - SecretKey: credentials.SecretKey, - Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), + AccessKey: credentials.AccessKey, + SecretKey: credentials.SecretKey, + DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"), + Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err }) diff --git a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go index b2c4dd5d..bc9eabcb 100644 --- a/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/byteplus_cdn.go @@ -8,9 +8,11 @@ import ( "strings" bpcdn "github.com/byteplus-sdk/byteplus-sdk-golang/service/cdn" + bp "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/byteplus-cdn" + xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname" ) type SSLDeployerProviderConfig struct { @@ -18,6 +20,9 @@ type SSLDeployerProviderConfig struct { AccessKey string `json:"accessKey"` // BytePlus SecretKey。 SecretKey string `json:"secretKey"` + // 域名匹配模式。 + // 零值时默认值 [DOMAIN_MATCH_PATTERN_EXACT]。 + DomainMatchPattern string `json:"domainMatchPattern,omitempty"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } @@ -75,61 +80,63 @@ 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, "*.") { - // 获取指定证书可关联的域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17 - describeCertConfigReq := &bpcdn.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) - } - - if describeCertConfigResp.Result.CertNotConfig != nil { - for i := range describeCertConfigResp.Result.CertNotConfig { - domains = append(domains, describeCertConfigResp.Result.CertNotConfig[i].Domain) + 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} } - if describeCertConfigResp.Result.OtherCertConfig != nil { - for i := range describeCertConfigResp.Result.OtherCertConfig { - domains = append(domains, describeCertConfigResp.Result.OtherCertConfig[i].Domain) + case DOMAIN_MATCH_PATTERN_WILDCARD: + { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") } - } - if len(domains) == 0 { - if len(describeCertConfigResp.Result.SpecifiedCertConfig) > 0 { - // 所有可关联的域名都配置了该证书,跳过部署 - d.logger.Info("no domains to deploy") + if strings.HasPrefix(d.config.Domain, "*.") { + domainCandidates, err := d.getMatchedDomainsByWildcard(ctx, d.config.Domain) + if err != nil { + return nil, err + } + + domains = domainCandidates } else { - return nil, errors.New("domain not found") + domains = []string{d.config.Domain} } } - } else { - domains = append(domains, d.config.Domain) + + case DOMAIN_MATCH_PATTERN_CERTSAN: + { + domainCandidates, err := d.getMatchedDomainsByCertId(ctx, upres.CertId) + if err != nil { + return nil, err + } + + domains = domainCandidates + } + + default: + return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern) } - 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 { select { case <-ctx.Done(): return nil, ctx.Err() - default: - // 关联证书与加速域名 - // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert - batchDeployCertReq := &bpcdn.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.updateDomainCertificate(ctx, domain, upres.CertId); err != nil { errs = append(errs, err) } } @@ -142,3 +149,96 @@ 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://docs.byteplus.com/en/docs/byteplus-cdn/ListCdnDomains_en-us + listCdnDomainsPageNum := int64(1) + listCdnDomainsPageSize := int64(100) + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listCdnDomainsReq := &bpcdn.ListCdnDomainsRequest{ + Domain: bp.String(strings.TrimPrefix(wildcardDomain, "*.")), + Status: bp.String("online"), + PageNum: bp.Int64(listCdnDomainsPageNum), + PageSize: bp.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) + } + + for _, domainInfo := range listCdnDomainsResp.Result.Data { + if xcerthostname.IsMatch(wildcardDomain, domainInfo.Domain) { + domains = append(domains, domainInfo.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://docs.byteplus.com/en/docs/byteplus-cdn/reference-describecertconfig-9ea17 + describeCertConfigReq := &bpcdn.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("no domains matched by certificate") + } + } + + return domains, nil +} + +func (d *SSLDeployerProvider) updateDomainCertificate(ctx context.Context, domain string, cloudCertId string) error { + // 关联证书与加速域名 + // REF: https://docs.byteplus.com/en/docs/byteplus-cdn/reference-batchdeploycert + batchDeployCertReq := &bpcdn.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 fmt.Errorf("failed to execute sdk request 'cdn.BatchDeployCert': %w", err) + } + + return nil +} diff --git a/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go b/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go new file mode 100644 index 00000000..81c2dab5 --- /dev/null +++ b/pkg/core/ssl-deployer/providers/byteplus-cdn/consts.go @@ -0,0 +1,10 @@ +package bytepluscdn + +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/ksyun-cdn/ksyun_cdn.go b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go index ead9dbc5..a09b800d 100644 --- a/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/ksyun-cdn/ksyun_cdn.go @@ -61,7 +61,7 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { - // // 如果原证书 ID 为空,则创建证书;否则更新证书。 + // 如果原证书 ID 为空,则创建证书;否则更新证书。 if d.config.CertificateId == "" { if d.config.Domain == "" { return nil, errors.New("config `domain` is required") 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 6c39e4dc..936458a1 100644 --- a/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go +++ b/pkg/core/ssl-deployer/providers/volcengine-cdn/volcengine_cdn.go @@ -115,12 +115,12 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke 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: @@ -194,6 +194,10 @@ func (d *SSLDeployerProvider) getMatchedDomainsByWildcard(ctx context.Context, w } } + if len(domains) == 0 { + return nil, errors.New("no domains matched by wildcard") + } + return domains, nil } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusCDN.tsx index b5b125fb..c81d79e4 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderBytePlusCDN.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 BizDeployNodeConfigFieldsProviderBytePlusCDN = () => { const { i18n, t } = useTranslation(); @@ -15,24 +20,52 @@ const BizDeployNodeConfigFieldsProviderBytePlusCDN = () => { [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 ( <>